diff options
author | Joscha <joscha@plugh.de> | 2022-09-09 00:01:47 +0200 |
---|---|---|
committer | Joscha <joscha@plugh.de> | 2022-09-09 00:02:02 +0200 |
commit | d7e19b5eca71c8fad73226f1e6e763383a9cfd5c (patch) | |
tree | 54ee626495d8473d31e4c18720d4983d913c09ad | |
parent | d92c7cb98e5e24a942f18b8e57c838d299b96762 (diff) |
Add message inspection popup
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | src/ui/euph.rs | 1 | ||||
-rw-r--r-- | src/ui/euph/inspect.rs | 94 | ||||
-rw-r--r-- | src/ui/euph/room.rs | 41 | ||||
-rw-r--r-- | src/vault/euph.rs | 67 |
5 files changed, 196 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cd71660..6fa35b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Procedure when bumping the version number: ### Added - Room deletion confirmation popup +- Message inspection popup ### Fixed - Cursor being visible through popups diff --git a/src/ui/euph.rs b/src/ui/euph.rs index 40c2778..9ca6d15 100644 --- a/src/ui/euph.rs +++ b/src/ui/euph.rs @@ -1,5 +1,6 @@ mod account; mod auth; +mod inspect; mod links; mod nick; mod nick_list; diff --git a/src/ui/euph/inspect.rs b/src/ui/euph/inspect.rs new file mode 100644 index 0000000..1327b6c --- /dev/null +++ b/src/ui/euph/inspect.rs @@ -0,0 +1,94 @@ +use crossterm::style::{ContentStyle, Stylize}; +use euphoxide::api::Message; +use toss::styled::Styled; + +use crate::ui::input::{key, InputEvent, KeyBindingsList}; +use crate::ui::widgets::popup::Popup; +use crate::ui::widgets::text::Text; +use crate::ui::widgets::BoxedWidget; + +macro_rules! line { + ( $text:ident, $name:expr, $val:expr ) => { + $text = $text + .then($name, ContentStyle::default().cyan()) + .then_plain(format!(" {}\n", $val)); + }; + ( $text:ident, $name:expr, $val:expr, debug ) => { + $text = $text + .then($name, ContentStyle::default().cyan()) + .then_plain(format!(" {:?}\n", $val)); + }; + ( $text:ident, $name:expr, $val:expr, optional ) => { + if let Some(val) = $val { + $text = $text + .then($name, ContentStyle::default().cyan()) + .then_plain(format!(" {val}\n")); + } else { + $text = $text + .then($name, ContentStyle::default().cyan()) + .then_plain(" ") + .then("none", ContentStyle::default().italic().grey()) + .then_plain("\n"); + } + }; + ( $text:ident, $name:expr, $val:expr, yes or no ) => { + $text = $text + .then($name, ContentStyle::default().cyan()) + .then_plain(if $val { " yes\n" } else { " no\n" }); + }; +} + +pub fn message_widget(msg: &Message) -> BoxedWidget { + let heading_style = ContentStyle::default().bold(); + + let mut text = Styled::new("Message", heading_style).then_plain("\n"); + line!(text, "id", msg.id); + line!(text, "parent", msg.parent, optional); + line!(text, "previous_edit_id", msg.previous_edit_id, optional); + line!(text, "time", msg.time.0); + line!(text, "encryption_key_id", &msg.encryption_key_id, optional); + line!(text, "edited", msg.edited.map(|t| t.0), optional); + line!(text, "deleted", msg.deleted.map(|t| t.0), optional); + line!(text, "truncated", msg.truncated, yes or no); + text = text.then_plain("\n"); + + text = text.then("Sender", heading_style).then_plain("\n"); + line!(text, "id", msg.sender.id); + line!(text, "name", msg.sender.name); + line!(text, "name (raw)", msg.sender.name, debug); + line!(text, "server_id", msg.sender.server_id); + line!(text, "server_era", msg.sender.server_era); + line!(text, "session_id", msg.sender.session_id); + line!(text, "is_staff", msg.sender.is_staff, yes or no); + line!(text, "is_manager", msg.sender.is_manager, yes or no); + line!( + text, + "client_address", + msg.sender.client_address.as_ref(), + optional + ); + line!( + text, + "real_client_address", + msg.sender.real_client_address.as_ref(), + optional + ); + + Popup::new(Text::new(text)).title("Inspect message").build() +} + +pub fn list_key_bindings(bindings: &mut KeyBindingsList) { + bindings.binding("esc", "close"); +} + +pub enum EventResult { + NotHandled, + Close, +} + +pub fn handle_input_event(event: &InputEvent) -> EventResult { + match event { + key!(Esc) => EventResult::Close, + _ => EventResult::NotHandled, + } +} diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index a671439..e0c2637 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; use std::sync::Arc; use crossterm::style::{ContentStyle, Stylize}; -use euphoxide::api::{Data, PacketType, Snowflake}; +use euphoxide::api::{Data, Message, PacketType, Snowflake}; use euphoxide::conn::{Joined, Joining, Status}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; @@ -30,15 +30,17 @@ use crate::vault::EuphVault; use super::account::{self, AccountUiState}; use super::links::{self, LinksState}; use super::popup::RoomPopup; -use super::{auth, nick, nick_list}; +use super::{auth, inspect, nick, nick_list}; +#[allow(clippy::large_enum_variant)] enum State { Normal, Auth(EditorState), Nick(EditorState), Account(AccountUiState), Links(LinksState), - // TODO Inspect messages and users + InspectMessage(Message), + // TODO Inspect users } #[allow(clippy::large_enum_variant)] @@ -225,6 +227,7 @@ impl EuphRoom { State::Nick(editor) => layers.push(nick::widget(editor)), State::Account(account) => layers.push(account.widget()), State::Links(links) => layers.push(links.widget()), + State::InspectMessage(message) => layers.push(inspect::message_widget(message)), } for popup in &self.popups { @@ -319,6 +322,7 @@ impl EuphRoom { false }; + bindings.binding("i", "inspect message"); bindings.binding("I", "show message links"); bindings.empty(); @@ -353,13 +357,24 @@ impl EuphRoom { } } - if let key!('I') = event { - if let Some(id) = self.chat.cursor().await { - if let Some(msg) = self.vault.msg(&id).await { - self.state = State::Links(LinksState::new(&msg.content)); + match event { + key!('i') => { + if let Some(id) = self.chat.cursor().await { + if let Some(msg) = self.vault.full_msg(id).await { + self.state = State::InspectMessage(msg); + } } + return true; } - return true; + key!('I') => { + if let Some(id) = self.chat.cursor().await { + if let Some(msg) = self.vault.msg(&id).await { + self.state = State::Links(LinksState::new(&msg.content)); + } + } + return true; + } + _ => {} } match status.ok().flatten() { @@ -425,6 +440,7 @@ impl EuphRoom { State::Nick(_) => nick::list_key_bindings(bindings), State::Account(account) => account.list_key_bindings(bindings), State::Links(links) => links.list_key_bindings(bindings), + State::InspectMessage(_) => inspect::list_key_bindings(bindings), } } @@ -442,6 +458,8 @@ impl EuphRoom { return false; } + // TODO Use a common EventResult + match &mut self.state { State::Normal => { self.handle_normal_input_event(terminal, crossterm_lock, event) @@ -494,6 +512,13 @@ impl EuphRoom { true } }, + State::InspectMessage(_) => match inspect::handle_input_event(event) { + inspect::EventResult::NotHandled => false, + inspect::EventResult::Close => { + self.state = State::Normal; + true + } + }, } } diff --git a/src/vault/euph.rs b/src/vault/euph.rs index 40655b4..48b1701 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -1,3 +1,5 @@ +// TODO Reduce code duplication (macro?) + use std::mem; use std::str::FromStr; @@ -146,6 +148,18 @@ impl EuphVault { rx.await.unwrap() } + pub async fn full_msg(&self, id: Snowflake) -> Option<Message> { + // TODO vault::Error + let (tx, rx) = oneshot::channel(); + let request = EuphRequest::GetFullMsg { + room: self.room.clone(), + id, + result: tx, + }; + let _ = self.vault.tx.send(request.into()); + rx.await.unwrap() + } + pub async fn chunk_at_offset(&self, amount: usize, offset: usize) -> Vec<Message> { // TODO vault::Error let (tx, rx) = oneshot::channel(); @@ -420,6 +434,11 @@ pub(super) enum EuphRequest { id: Snowflake, result: oneshot::Sender<Option<SmallMessage>>, }, + GetFullMsg { + room: String, + id: Snowflake, + result: oneshot::Sender<Option<Message>>, + }, GetTree { room: String, root: Snowflake, @@ -524,6 +543,7 @@ impl EuphRequest { Self::GetLastSpan { room, result } => Self::get_last_span(conn, room, result), Self::GetPath { room, id, result } => Self::get_path(conn, room, id, result), Self::GetMsg { room, id, result } => Self::get_msg(conn, room, id, result), + Self::GetFullMsg { room, id, result } => Self::get_full_msg(conn, room, id, result), Self::GetTree { room, root, result } => Self::get_tree(conn, room, root, result), Self::GetFirstTreeId { room, result } => Self::get_first_tree_id(conn, room, result), Self::GetLastTreeId { room, result } => Self::get_last_tree_id(conn, room, result), @@ -949,6 +969,53 @@ impl EuphRequest { Ok(()) } + fn get_full_msg( + conn: &Connection, + room: String, + id: Snowflake, + result: oneshot::Sender<Option<Message>>, + ) -> rusqlite::Result<()> { + let mut query = conn.prepare( + " + SELECT + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address + FROM euph_msgs + WHERE room = ? + AND id = ? + " + )?; + + let msg = query + .query_row(params![room, WSnowflake(id)], |row| { + Ok(Message { + id: row.get::<_, WSnowflake>(0)?.0, + parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| s.0), + previous_edit_id: row.get::<_, Option<WSnowflake>>(2)?.map(|s| s.0), + time: row.get::<_, WTime>(3)?.0, + content: row.get(4)?, + encryption_key_id: row.get(5)?, + edited: row.get::<_, Option<WTime>>(6)?.map(|t| t.0), + deleted: row.get::<_, Option<WTime>>(7)?.map(|t| t.0), + truncated: row.get(8)?, + sender: SessionView { + id: UserId(row.get(9)?), + name: row.get(10)?, + server_id: row.get(11)?, + server_era: row.get(12)?, + session_id: row.get(13)?, + is_staff: row.get(14)?, + is_manager: row.get(15)?, + client_address: row.get(16)?, + real_client_address: row.get(17)?, + }, + }) + }) + .optional()?; + let _ = result.send(msg); + Ok(()) + } + fn get_tree( conn: &Connection, room: String, |