Skip to content

Commit

Permalink
feat(tui): key mappings for JSON Payload widget
Browse files Browse the repository at this point in the history
EdJoPaTo committed Nov 15, 2020
1 parent 62be44e commit 0e9e95f
Showing 4 changed files with 150 additions and 27 deletions.
117 changes: 102 additions & 15 deletions src/interactive/app.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
use crate::mqtt_history::{self, HistoryArc};
use crate::topic;
use crate::topic_view;
use crate::mqtt_history::HistoryArc;
use crate::{format, json_view, mqtt_history, topic, topic_view};
use json::JsonValue;
use std::collections::HashSet;
use std::error::Error;
use tui_tree_widget::{flatten, TreeState};

#[derive(Debug, PartialEq)]
pub enum ElementInFocus {
TopicOverview,
JsonPayload,
}

pub struct App<'a> {
pub host: &'a str,
pub port: u16,
pub subscribe_topic: &'a str,
pub history: HistoryArc,

pub focus: ElementInFocus,
pub json_view_state: TreeState,
pub opened_topics: HashSet<String>,
pub selected_topic: Option<String>,
@@ -26,6 +33,7 @@ impl<'a> App<'a> {
subscribe_topic,
history,

focus: ElementInFocus::TopicOverview,
json_view_state: TreeState::default(),
opened_topics: HashSet::new(),
selected_topic: None,
@@ -77,37 +85,116 @@ impl<'a> App<'a> {
Ok(())
}

fn get_json_of_current_topic(&self) -> Result<Option<JsonValue>, Box<dyn Error>> {
let history = self
.history
.lock()
.map_err(|err| format!("failed to aquire lock of mqtt history: {}", err))?;

let json = self
.selected_topic
.as_ref()
.and_then(|topic| history.get(topic))
.map(|o| o.last().expect("History always has at least one entry"))
.and_then(|value| format::payload_as_json(value.packet.payload.to_vec()));

Ok(json)
}

fn change_selected_json_property(&mut self, down: bool) -> Result<(), Box<dyn Error>> {
let json = self.get_json_of_current_topic()?.unwrap_or(JsonValue::Null);
let tree_items = json_view::root_tree_items_from_json(&json);

let visible = flatten(&self.json_view_state.get_all_opened(), &tree_items);
let current_identifier = self.json_view_state.selected();
let current_index = visible
.iter()
.position(|o| o.identifier == current_identifier);
let new_index = current_index.map_or(0, |current_index| {
if down {
current_index.saturating_add(1)
} else {
current_index.saturating_sub(1)
}
.min(visible.len() - 1)
});
let new_identifier = visible.get(new_index).unwrap().identifier.to_owned();
self.json_view_state.select(new_identifier);
Ok(())
}

pub fn on_up(&mut self) -> Result<(), Box<dyn Error>> {
let increase = false;
self.change_selected_topic(increase)
match self.focus {
ElementInFocus::TopicOverview => self.change_selected_topic(increase),
ElementInFocus::JsonPayload => self.change_selected_json_property(increase),
}
}

pub fn on_down(&mut self) -> Result<(), Box<dyn Error>> {
let increase = true;
self.change_selected_topic(increase)
match self.focus {
ElementInFocus::TopicOverview => self.change_selected_topic(increase),
ElementInFocus::JsonPayload => self.change_selected_json_property(increase),
}
}

pub fn on_right(&mut self) {
if let Some(topic) = &self.selected_topic {
self.opened_topics.insert(topic.to_owned());
match self.focus {
ElementInFocus::TopicOverview => {
if let Some(topic) = &self.selected_topic {
self.opened_topics.insert(topic.to_owned());
}
}
ElementInFocus::JsonPayload => {
self.json_view_state.open(self.json_view_state.selected());
}
}
}

pub fn on_left(&mut self) {
if let Some(topic) = &self.selected_topic {
if let false = self.opened_topics.remove(topic) {
self.selected_topic = topic::get_parent(topic).map(std::borrow::ToOwned::to_owned);
match self.focus {
ElementInFocus::TopicOverview => {
if let Some(topic) = &self.selected_topic {
if let false = self.opened_topics.remove(topic) {
self.selected_topic =
topic::get_parent(topic).map(std::borrow::ToOwned::to_owned);
}
}
}
ElementInFocus::JsonPayload => {
let selected = self.json_view_state.selected();
if !self.json_view_state.close(&selected) {
let (head, _) = tui_tree_widget::identifier::get_without_leaf(&selected);
self.json_view_state.select(head);
}
}
}
}

pub fn on_toggle(&mut self) {
if let Some(topic) = &self.selected_topic {
if self.opened_topics.contains(topic) {
self.opened_topics.remove(topic);
} else {
self.opened_topics.insert(topic.to_owned());
if ElementInFocus::TopicOverview == self.focus {
if let Some(topic) = &self.selected_topic {
if self.opened_topics.contains(topic) {
self.opened_topics.remove(topic);
} else {
self.opened_topics.insert(topic.to_owned());
}
}
}
}

pub fn on_tab(&mut self) -> Result<(), Box<dyn Error>> {
let is_json_on_topic = self.get_json_of_current_topic()?.is_some();
self.focus = if is_json_on_topic {
match self.focus {
ElementInFocus::TopicOverview => ElementInFocus::JsonPayload,
ElementInFocus::JsonPayload => ElementInFocus::TopicOverview,
}
} else {
ElementInFocus::TopicOverview
};

Ok(())
}
}
1 change: 1 addition & 0 deletions src/interactive/mod.rs
Original file line number Diff line number Diff line change
@@ -87,6 +87,7 @@ pub fn show(
'l' => app.on_right(),
_ => {}
},
KeyCode::Tab | KeyCode::BackTab => app.on_tab()?,
KeyCode::Enter => app.on_toggle(),
KeyCode::Left => app.on_left(),
KeyCode::Up => app.on_up()?,
57 changes: 46 additions & 11 deletions src/interactive/ui.rs
Original file line number Diff line number Diff line change
@@ -13,13 +13,21 @@ use tui::{
use tui_tree_widget::{Tree, TreeState};

use crate::format;
use crate::interactive::app::App;
use crate::interactive::app::{App, ElementInFocus};
use crate::json_view::root_tree_items_from_json;
use crate::mqtt_history::{self, HistoryEntry};
use crate::topic_view::{self, TopicTreeEntry};

mod history;

fn focus_color(has_focus: bool) -> Color {
if has_focus {
Color::LightGreen
} else {
Color::Gray
}
}

pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) -> Result<(), Box<dyn Error>> {
let chunks = Layout::default()
.constraints([Constraint::Length(2 + 3), Constraint::Min(8)].as_ref())
@@ -87,7 +95,13 @@ where
.direction(Direction::Horizontal)
.split(area);

draw_details(f, chunks[1], topic_history, &mut app.json_view_state);
draw_details(
f,
chunks[1],
topic_history,
app.focus == ElementInFocus::JsonPayload,
&mut app.json_view_state,
);

chunks[0]
} else {
@@ -99,6 +113,7 @@ where
overview_area,
topics.len(),
&tree_items,
app.focus == ElementInFocus::TopicOverview,
&mut app.topic_overview_state,
);
Ok(())
@@ -109,6 +124,7 @@ fn draw_overview<B>(
area: Rect,
topic_amount: usize,
tree_items: &[TopicTreeEntry],
has_focus: bool,
state: &mut TreeState,
) where
B: Backend,
@@ -117,16 +133,23 @@ fn draw_overview<B>(

let tree_items = topic_view::tree_items_from_tmlp_tree(&tree_items);

let focus_color = focus_color(has_focus);
let widget = Tree::new(tree_items)
.block(Block::default().borders(Borders::ALL).title(title))
.highlight_style(Style::default().fg(Color::Black).bg(Color::LightGreen));
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(focus_color))
.title(title),
)
.highlight_style(Style::default().fg(Color::Black).bg(focus_color));
f.render_stateful_widget(widget, area, state);
}

fn draw_details<B>(
f: &mut Frame<B>,
area: Rect,
topic_history: &[HistoryEntry],
json_payload_has_focus: bool,
json_view_state: &mut TreeState,
) where
B: Backend,
@@ -148,7 +171,14 @@ fn draw_details<B>(
)
.split(area);

draw_payload_json(f, chunks[0], payload_length, &json, json_view_state);
draw_payload_json(
f,
chunks[0],
payload_length,
&json,
json_payload_has_focus,
json_view_state,
);
chunks[1]
} else {
let payload = format::payload_as_utf8(last.packet.payload.to_vec());
@@ -179,9 +209,7 @@ where
{
let title = format!("Payload (Bytes: {})", bytes);
let items = payload.lines().map(ListItem::new).collect::<Vec<_>>();
let widget = List::new(items)
.block(Block::default().borders(Borders::ALL).title(title))
.highlight_style(Style::default().fg(Color::Black).bg(Color::LightGreen));
let widget = List::new(items).block(Block::default().borders(Borders::ALL).title(title));
f.render_widget(widget, area);
}

@@ -190,14 +218,21 @@ fn draw_payload_json<B>(
area: Rect,
bytes: usize,
json: &JsonValue,
has_focus: bool,
view_state: &mut TreeState,
) where
B: Backend,
{
let title = format!("JSON Payload (Bytes: {})", bytes);
let title = format!("JSON Payload (Bytes: {}) (TAB to switch)", bytes);
let items = root_tree_items_from_json(json);
let focus_color = focus_color(has_focus);
let widget = Tree::new(items)
.block(Block::default().borders(Borders::ALL).title(title))
.highlight_style(Style::default().fg(Color::Black).bg(Color::LightGreen));
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(focus_color))
.title(title),
)
.highlight_style(Style::default().fg(Color::Black).bg(focus_color));
f.render_stateful_widget(widget, area, view_state);
}
2 changes: 1 addition & 1 deletion src/json_view.rs
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ pub fn root_tree_items_from_json<'a>(root: &'a JsonValue) -> Vec<TreeItem<'a>> {
}
}

pub fn tree_items_from_json<'a>(key: &str, value: &'a JsonValue) -> TreeItem<'a> {
fn tree_items_from_json<'a>(key: &str, value: &'a JsonValue) -> TreeItem<'a> {
match value {
JsonValue::Null => TreeItem::new_leaf(format!("{}: Null", key)),
JsonValue::Short(short_string) => TreeItem::new_leaf(format!("{}: {}", key, short_string)),

0 comments on commit 0e9e95f

Please sign in to comment.