diff --git a/pubsubman/src/app.rs b/pubsubman/src/app.rs index 555dfed..2f1cab3 100644 --- a/pubsubman/src/app.rs +++ b/pubsubman/src/app.rs @@ -1,10 +1,13 @@ use std::collections::{HashMap, HashSet}; +use chrono::{DateTime, Local}; +use egui_json_tree::{DefaultExpand, JsonTree}; use pubsubman_backend::{ message::{BackendMessage, FrontendMessage}, model::{PubsubMessage, SubscriptionName, TopicName}, Backend, }; +use serde_json::{Map, Value}; use tokio::sync::mpsc::{Receiver, Sender}; use crate::{ @@ -35,6 +38,7 @@ pub struct App { front_tx: Sender, back_rx: Receiver, notifications: Notifications, + selected_message: Option<(TopicName, usize)>, } impl App { @@ -66,6 +70,7 @@ impl App { front_tx, back_rx, notifications: Notifications::default(), + selected_message: None, } } @@ -165,6 +170,8 @@ impl App { cancel_token.cancel(); } + self.selected_message.take(); + self.selected_topic = Some(topic_name.clone()); if !self.memory.subscriptions.contains_key(topic_name) { @@ -181,6 +188,100 @@ impl App { fn render_central_panel(&mut self, ctx: &egui::Context) { match &self.selected_topic { Some(selected_topic) => { + let selected_message = + &self + .selected_message + .as_ref() + .and_then(|(topic_name, idx)| { + self.memory + .messages + .get(topic_name) + .and_then(|messages| messages.get(*idx)) + }); + + egui::SidePanel::right("selected_message") + .frame(egui::Frame::none()) + .resizable(true) + .show_animated(ctx, selected_message.is_some(), |ui| { + if let Some(message) = selected_message { + egui::TopBottomPanel::top("selected_message_top_panel") + .frame(egui::Frame::side_top_panel(&ctx.style()).inner_margin(8.0)) + .show_inside(ui, |ui| { + ui.with_layout( + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.heading(format!("Message {}", &message.id)); + ui.with_layout( + egui::Layout::right_to_left(egui::Align::Center), + |ui| { + if ui.button("✖").clicked() { + self.selected_message.take(); + } + }, + ); + }, + ); + }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + let publish_time = + if let Some(publish_time) = message.publish_time { + let local_publish_time: DateTime = + publish_time.into(); + local_publish_time.format("%d/%m/%Y %H:%M").to_string() + } else { + "".to_string() + }; + + ui.horizontal(|ui| { + ui.label("Publish Time: "); + ui.monospace(publish_time); + }); + + egui::CollapsingHeader::new("Data") + .id_source("selected_message_data_collapsing_header") + .default_open(false) + .show(ui, |ui| { + JsonTree::new( + format!( + "selected_message_data_json_{}", + &message.id + ), + &message.data_json, + ) + .default_expand(DefaultExpand::All) + .show(ui); + }); + + egui::CollapsingHeader::new("Attributes") + .id_source("selected_message_attributes_collapsing_header") + .default_open(false) + .show(ui, |ui| { + if message.attributes.is_empty() { + ui.monospace(""); + } else { + JsonTree::new( + format!( + "selected_message_attributes_json_{}", + &message.id + ), + &Value::Object(Map::from_iter( + message.attributes.iter().map(|(k, v)| { + (k.to_owned(), Value::String(v.clone())) + }), + )), + ) + .default_expand(egui_json_tree::DefaultExpand::All) + .show(ui); + } + }); + ui.allocate_space(ui.available_size()); + }); + }); + } + }); + egui::TopBottomPanel::top("topic_view_top_panel") .frame(egui::Frame::side_top_panel(&ctx.style()).inner_margin(8.0)) .show(ctx, |ui| { @@ -229,6 +330,9 @@ impl App { .entry(selected_topic.clone()) .or_default(), self.memory.messages.get(selected_topic).unwrap_or(&vec![]), + |idx| { + self.selected_message = Some((selected_topic.clone(), idx)) + }, ); } None => { diff --git a/pubsubman/src/column_settings.rs b/pubsubman/src/column_settings.rs index 6601212..dae110a 100644 --- a/pubsubman/src/column_settings.rs +++ b/pubsubman/src/column_settings.rs @@ -1,16 +1,12 @@ #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct ColumnSettings { - pub show_id: bool, pub show_published_at: bool, - pub show_attributes: bool, } impl Default for ColumnSettings { fn default() -> Self { Self { - show_id: true, show_published_at: true, - show_attributes: true, } } } @@ -19,17 +15,9 @@ impl ColumnSettings { pub fn show(&mut self, ui: &mut egui::Ui) { ui.visuals_mut().widgets.inactive.weak_bg_fill = egui::Color32::from_gray(32); ui.menu_button("Columns ⏷", |ui| { - ui.horizontal(|ui| { - ui.checkbox(&mut self.show_id, " ID"); - }); - ui.horizontal(|ui| { ui.checkbox(&mut self.show_published_at, " Published at"); }); - - ui.horizontal(|ui| { - ui.checkbox(&mut self.show_attributes, " Attributes"); - }); }); } } diff --git a/pubsubman/src/ui/messages_view.rs b/pubsubman/src/ui/messages_view.rs index 3dd3c16..74b93d9 100644 --- a/pubsubman/src/ui/messages_view.rs +++ b/pubsubman/src/ui/messages_view.rs @@ -1,6 +1,3 @@ -use std::collections::HashMap; -use std::fmt::Write; - use chrono::{DateTime, Local}; use egui_json_tree::{DefaultExpand, JsonTree}; use pubsubman_backend::{ @@ -32,6 +29,7 @@ impl MessagesView { sub_name: &SubscriptionName, column_settings: &mut ColumnSettings, messages: &[PubsubMessage], + on_message_id_click: impl FnMut(usize), ) { let search_query = self.search_query.to_ascii_lowercase(); let filtered_messages = messages @@ -155,6 +153,7 @@ impl MessagesView { filtered_messages, &search_query, search_query_changed, + on_message_id_click, ); }); }); @@ -170,37 +169,29 @@ fn render_messages_table<'a, I>( messages: I, search_term: &str, search_query_changed: bool, + mut on_message_id_click: impl FnMut(usize), ) where I: Iterator, { - let ColumnSettings { - show_id, - show_published_at, - show_attributes, - } = *column_settings; + let ColumnSettings { show_published_at } = *column_settings; + + let mut num_columns = 2; // ID and Data columns will always be shown. - let num_columns = [show_id, show_published_at, show_attributes].iter().fold( - 1, // Data column will always be present - |acc, col_enabled| if *col_enabled { acc + 1 } else { acc }, - ); + if show_published_at { + num_columns += 1; + } egui::Grid::new(&selected_topic.0) .striped(true) .num_columns(num_columns) .spacing((25.0, 8.0)) .show(ui, |ui| { - if show_id { - ui.label("ID"); - } + ui.label("ID"); if show_published_at { ui.label("Published at"); } - if show_attributes { - ui.label("Attributes"); - } - // Let Data column take up all remaining space. ui.with_layout( egui::Layout::left_to_right(egui::Align::Center) @@ -213,9 +204,9 @@ fn render_messages_table<'a, I>( ui.end_row(); - for message in messages { - if show_id { - ui.label(&message.id); + for (idx, message) in messages.enumerate() { + if ui.link(&message.id).clicked() { + on_message_id_click(idx); } if show_published_at { @@ -226,10 +217,6 @@ fn render_messages_table<'a, I>( } } - if show_attributes { - ui.label(format_attributes(&message.attributes)); - } - let response = JsonTree::new(&message.id, &message.data_json) .default_expand(DefaultExpand::SearchResults(search_term)) .response_callback(|response, pointer| { @@ -276,19 +263,3 @@ fn show_context_menu(ui: &mut egui::Ui, pointer: &String, value: &Value) { } }); } - -fn format_attributes(attributes: &HashMap) -> String { - attributes - .iter() - .enumerate() - .fold(String::new(), |mut acc, (i, (k, v))| { - let _ = write!( - acc, - "{}:{}{}", - k, - v, - (if i == attributes.len() - 1 { "" } else { ", " }) - ); - acc - }) -}