summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoscha <joscha@plugh.de>2022-09-09 00:01:47 +0200
committerJoscha <joscha@plugh.de>2022-09-09 00:02:02 +0200
commitd7e19b5eca71c8fad73226f1e6e763383a9cfd5c (patch)
tree54ee626495d8473d31e4c18720d4983d913c09ad
parentd92c7cb98e5e24a942f18b8e57c838d299b96762 (diff)
Add message inspection popup
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/ui/euph.rs1
-rw-r--r--src/ui/euph/inspect.rs94
-rw-r--r--src/ui/euph/room.rs41
-rw-r--r--src/vault/euph.rs67
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,