Skip to content

Commit

Permalink
Merge pull request #4 from dmackdev/selected-message-view
Browse files Browse the repository at this point in the history
Add selected message view
  • Loading branch information
dmackdev authored Oct 17, 2023
2 parents ed14c9b + c4f2b4d commit b268afb
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 91 deletions.
37 changes: 36 additions & 1 deletion pubsubman/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
exit_state::{ExitState, SubscriptionCleanupState},
notifications::Notifications,
settings::Settings,
ui::{render_topic_name, MessagesView, PublishView},
ui::{render_selected_message, render_topic_name, MessagesView, PublishView},
};

#[derive(Default, serde::Deserialize, serde::Serialize)]
Expand All @@ -35,6 +35,7 @@ pub struct App {
front_tx: Sender<FrontendMessage>,
back_rx: Receiver<BackendMessage>,
notifications: Notifications,
selected_message: Option<(TopicName, usize)>,
}

impl App {
Expand Down Expand Up @@ -66,6 +67,7 @@ impl App {
front_tx,
back_rx,
notifications: Notifications::default(),
selected_message: None,
}
}

Expand Down Expand Up @@ -165,6 +167,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) {
Expand All @@ -181,6 +185,16 @@ 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::TopBottomPanel::top("topic_view_top_panel")
.frame(egui::Frame::side_top_panel(&ctx.style()).inner_margin(8.0))
.show(ctx, |ui| {
Expand All @@ -189,6 +203,24 @@ impl App {
});
});

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 {
render_selected_message(
ctx,
ui,
&self.front_tx,
message,
selected_topic,
|| {
self.selected_message.take();
},
);
}
});

egui::TopBottomPanel::bottom("topic_view_bottom_panel")
.resizable(true)
.frame(egui::Frame::side_top_panel(&ctx.style()).inner_margin(8.0))
Expand Down Expand Up @@ -229,6 +261,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 => {
Expand Down
12 changes: 0 additions & 12 deletions pubsubman/src/column_settings.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
Expand All @@ -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");
});
});
}
}
38 changes: 38 additions & 0 deletions pubsubman/src/ui/json_ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use serde_json::Value;

pub fn show_json_context_menu(value: &Value) -> impl FnMut(egui::Response, &String) + '_ {
|response, pointer| {
response
.on_hover_cursor(egui::CursorIcon::ContextMenu)
.context_menu(|ui| {
show_json_context_menu_impl(ui, pointer, value);
});
}
}

fn show_json_context_menu_impl(ui: &mut egui::Ui, pointer: &String, value: &Value) {
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
ui.set_width(150.0);

if !pointer.is_empty()
&& ui
.add(egui::Button::new("Copy property path").frame(false))
.clicked()
{
ui.output_mut(|o| o.copied_text = pointer.clone());
ui.close_menu();
}

if ui
.add(egui::Button::new("Copy contents").frame(false))
.clicked()
{
if let Some(val) = value.pointer(pointer) {
if let Ok(pretty_str) = serde_json::to_string_pretty(val) {
ui.output_mut(|o| o.copied_text = pretty_str);
}
}
ui.close_menu();
}
});
}
94 changes: 17 additions & 77 deletions pubsubman/src/ui/messages_view.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use std::collections::HashMap;
use std::fmt::Write;

use chrono::{DateTime, Local};
use egui_json_tree::{DefaultExpand, JsonTree};
use pubsubman_backend::{
message::FrontendMessage,
model::{PubsubMessage, SubscriptionName, TopicName},
};
use serde_json::Value;
use tokio::sync::mpsc::Sender;
use tokio_util::sync::CancellationToken;

Expand All @@ -16,6 +12,8 @@ use crate::{
column_settings::ColumnSettings,
};

use super::show_json_context_menu;

#[derive(Default)]
pub struct MessagesView {
pub stream_messages_enabled: bool,
Expand All @@ -24,6 +22,7 @@ pub struct MessagesView {
}

impl MessagesView {
#[allow(clippy::too_many_arguments)]
pub fn show(
&mut self,
ui: &mut egui::Ui,
Expand All @@ -32,6 +31,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
Expand Down Expand Up @@ -155,6 +155,7 @@ impl MessagesView {
filtered_messages,
&search_query,
search_query_changed,
on_message_id_click,
);
});
});
Expand All @@ -170,37 +171,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<Item = &'a PubsubMessage>,
{
let ColumnSettings {
show_id,
show_published_at,
show_attributes,
} = *column_settings;
let ColumnSettings { show_published_at } = *column_settings;

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 },
);
let mut num_columns = 2; // ID and Data columns will always be shown.

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)
Expand All @@ -213,9 +206,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 {
Expand All @@ -226,19 +219,9 @@ 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| {
response
.on_hover_cursor(egui::CursorIcon::ContextMenu)
.context_menu(|ui| {
show_context_menu(ui, pointer, &message.data_json);
});
})
.response_callback(show_json_context_menu(&message.data_json))
.show(ui);

if search_query_changed {
Expand All @@ -249,46 +232,3 @@ fn render_messages_table<'a, I>(
}
});
}

fn show_context_menu(ui: &mut egui::Ui, pointer: &String, value: &Value) {
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
ui.set_width(150.0);

if !pointer.is_empty()
&& ui
.add(egui::Button::new("Copy property path").frame(false))
.clicked()
{
ui.output_mut(|o| o.copied_text = pointer.clone());
ui.close_menu();
}

if ui
.add(egui::Button::new("Copy contents").frame(false))
.clicked()
{
if let Some(val) = value.pointer(pointer) {
if let Ok(pretty_str) = serde_json::to_string_pretty(val) {
ui.output_mut(|o| o.copied_text = pretty_str);
}
}
ui.close_menu();
}
});
}

fn format_attributes(attributes: &HashMap<String, String>) -> String {
attributes
.iter()
.enumerate()
.fold(String::new(), |mut acc, (i, (k, v))| {
let _ = write!(
acc,
"{}:{}{}",
k,
v,
(if i == attributes.len() - 1 { "" } else { ", " })
);
acc
})
}
4 changes: 4 additions & 0 deletions pubsubman/src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
mod json_ui;
mod messages_view;
mod modal;
mod publish_view;
mod selected_message;
mod topic_name;
mod validity_frame;

pub use json_ui::show_json_context_menu;
pub use messages_view::MessagesView;
pub use modal::Modal;
pub use publish_view::PublishView;
pub use selected_message::render_selected_message;
pub use topic_name::render_topic_name;
Loading

0 comments on commit b268afb

Please sign in to comment.