diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index abbfdf2..cd89cd0 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -87,6 +87,9 @@ pub struct ChatRegistry { /// Tracking events seen on which relays in the current session seens: Arc>>>, + /// Mapping of unwrapped event ids to their gift wrap event ids + event_map: Arc>>, + /// Tracking the status of unwrapping gift wrap events. tracking_flag: Arc, @@ -150,6 +153,7 @@ impl ChatRegistry { rooms: vec![], trashes: cx.new(|_| BTreeSet::default()), seens: Arc::new(RwLock::new(HashMap::default())), + event_map: Arc::new(RwLock::new(HashMap::default())), tracking_flag: Arc::new(AtomicBool::new(false)), signal_rx: rx, signal_tx: tx, @@ -165,6 +169,7 @@ impl ChatRegistry { let signer = nostr.read(cx).signer(); let status = self.tracking_flag.clone(); let seens = self.seens.clone(); + let event_map = self.event_map.clone(); let trashes = self.trashes.downgrade(); let initialized_at = Timestamp::now(); @@ -206,6 +211,13 @@ impl ChatRegistry { // Extract the rumor from the gift wrap event match extract_rumor(&client, &signer, event.as_ref()).await { Ok(rumor) => { + // Map the rumor id to the gift wrap event id for later lookup + { + let mut event_map = event_map.write().await; + event_map.insert(rumor.id.unwrap(), event.id); + } + + // Check if the rumor has a recipient if rumor.tags.is_empty() { let signal = Signal::error(event.as_ref(), "Recipient is missing"); @@ -214,6 +226,7 @@ impl ChatRegistry { continue; } + // Check if the rumor was created after the chat was initialized (for detecting new messages) if rumor.created_at >= initialized_at { let signal = Signal::message(event.id, rumor); tx.send_async(signal).await?; @@ -455,7 +468,15 @@ impl ChatRegistry { self.trashes.clone() } - /// Get the relays that have seen a given message. + /// Get the relays that have seen a given rumor id. + pub fn rumor_seen_on(&self, id: &EventId) -> Option> { + self.event_map + .read_blocking() + .get(id) + .map(|id| self.seen_on(id)) + } + + /// Get the relays that have seen a given gift wrap id. pub fn seen_on(&self, id: &EventId) -> HashSet { self.seens .read_blocking() diff --git a/crates/chat_ui/src/actions.rs b/crates/chat_ui/src/actions.rs index 25ab534..091b365 100644 --- a/crates/chat_ui/src/actions.rs +++ b/crates/chat_ui/src/actions.rs @@ -13,4 +13,5 @@ pub enum Command { Copy(PublicKey), Relays(PublicKey), Njump(PublicKey), + Trace(EventId), } diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 750a871..e81de04 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::Arc; pub use actions::*; use anyhow::{Context as AnyhowContext, Error}; -use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus}; +use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus}; use common::RenderedTimestamp; use gpui::prelude::FluentBuilder; use gpui::{ @@ -658,9 +658,56 @@ impl ChatPanel { Command::Njump(public_key) => { self.open_njump(public_key, cx); } + Command::Trace(id) => { + self.open_trace(id, window, cx); + } } } + fn open_trace(&mut self, id: &EventId, window: &mut Window, cx: &mut Context) { + let chat = ChatRegistry::global(cx); + let seen_on = chat.read(cx).rumor_seen_on(id); + + window.open_modal(cx, move |this, _window, cx| { + this.title("Seen on").show_close(true).child( + v_flex() + .gap_1() + .when_none(&seen_on, |this| { + this.child( + h_flex() + .h_10() + .justify_center() + .text_sm() + .bg(cx.theme().elevated_surface_background) + .rounded(cx.theme().radius) + .child("Message isn't traced yet"), + ) + }) + .when_some(seen_on.as_ref(), |this, relays| { + this.children({ + let mut items = vec![]; + + for url in relays.iter() { + items.push( + h_flex() + .h_7() + .px_2() + .gap_2() + .bg(cx.theme().elevated_surface_background) + .rounded(cx.theme().radius) + .text_sm() + .child(div().size_1p5().rounded_full().bg(gpui::green())) + .child(SharedString::from(url.to_string())), + ); + } + + items + }) + }), + ) + }); + } + fn open_relays(&mut self, public_key: &PublicKey, window: &mut Window, cx: &mut Context) { let profile = self.profile(public_key, cx); @@ -1131,15 +1178,10 @@ impl ChatPanel { .ghost() .dropdown_menu({ let public_key = *public_key; - let _id = *id; + let id = *id; move |this, _window, _cx| { this.menu("Copy author", Box::new(Command::Copy(public_key))) - /* - .menu( - "Trace", - Box::new(Command::Trace(id)), - ) - */ + .menu("Seen on", Box::new(Command::Trace(id))) } }), )