From 493223276c539ef39bf0abc2e04499c641171878 Mon Sep 17 00:00:00 2001 From: reya Date: Sun, 3 Aug 2025 20:34:35 +0700 Subject: [PATCH] chore: improve message fetching --- crates/coop/src/chatspace.rs | 29 +++++- crates/coop/src/main.rs | 105 +++++++++++++--------- crates/coop/src/views/chat/mod.rs | 39 ++++---- crates/coop/src/views/messaging_relays.rs | 24 ++--- crates/coop/src/views/sidebar/mod.rs | 4 +- crates/global/src/constants.rs | 6 +- crates/global/src/lib.rs | 16 +++- crates/global/src/paths.rs | 2 +- crates/identity/src/lib.rs | 53 ++++++----- crates/registry/src/lib.rs | 28 ++++-- crates/registry/src/room.rs | 15 +++- crates/ui/src/dock_area/mod.rs | 22 ++++- crates/ui/src/dock_area/tab_panel.rs | 5 ++ 13 files changed, 231 insertions(+), 117 deletions(-) diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index c953bf7..5067353 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -12,9 +12,10 @@ use gpui::{ }; use i18n::{shared_t, t}; use identity::Identity; +use itertools::Itertools; use nostr_connect::prelude::*; use nostr_sdk::prelude::*; -use registry::{Registry, RoomEmitter}; +use registry::{Registry, RegistrySignal}; use serde::Deserialize; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; @@ -194,7 +195,7 @@ impl ChatSpace { window, |this: &mut Self, _state, event, window, cx| { match event { - RoomEmitter::Open(room) => { + RegistrySignal::Open(room) => { if let Some(room) = room.upgrade() { this.dock.update(cx, |this, cx| { let panel = chat::init(room, window, cx); @@ -209,7 +210,7 @@ impl ChatSpace { window.push_notification(t!("common.room_error"), cx); } } - RoomEmitter::Close(..) => { + RegistrySignal::Close(..) => { this.dock.update(cx, |this, cx| { this.focus_tab_panel(window, cx); @@ -415,6 +416,28 @@ impl ChatSpace { } } } + + pub(crate) fn all_panels(window: &mut Window, cx: &mut App) -> Option> { + let Some(Some(root)) = window.root::() else { + return None; + }; + + let Ok(chatspace) = root.read(cx).view().clone().downcast::() else { + return None; + }; + + let ids = chatspace + .read(cx) + .dock + .read(cx) + .items + .panel_ids(cx) + .into_iter() + .filter_map(|panel| panel.parse::().ok()) + .collect_vec(); + + Some(ids) + } } impl Render for ChatSpace { diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index e0807ed..1a19210 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -4,23 +4,27 @@ use std::time::Duration; use anyhow::{anyhow, Error}; use assets::Assets; +use common::event::EventUtils; use global::constants::{ - ALL_MESSAGES_SUB_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, - METADATA_BATCH_TIMEOUT, NEW_MESSAGE_SUB_ID, SEARCH_RELAYS, + ALL_MESSAGES_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, + METADATA_BATCH_TIMEOUT, NEW_MESSAGE_ID, SEARCH_RELAYS, }; -use global::{nostr_client, NostrSignal}; +use global::{nostr_client, set_all_gift_wraps_fetched, NostrSignal}; use gpui::{ actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, }; use identity::Identity; +use itertools::Itertools; use nostr_sdk::prelude::*; use registry::Registry; use smol::channel::{self, Sender}; use theme::Theme; use ui::Root; +use crate::chatspace::ChatSpace; + pub(crate) mod chatspace; pub(crate) mod views; @@ -130,7 +134,7 @@ fn main() { let duration = smol::Timer::after(Duration::from_secs(30)); let recv = || async { - // prevent inline format + // no inline (event_rx.recv().await).ok() }; @@ -142,11 +146,10 @@ fn main() { match smol::future::or(recv(), timeout()).await { Some(event) => { // Process the gift wrap event unwrapping - let is_cached = - try_unwrap_event(client, &signal_tx, &mta_tx, &event, false).await; + let cached = try_unwrap_event(&signal_tx, &mta_tx, &event, false).await; // Increment the total messages counter if message is not from cache - if !is_cached { + if !cached { counter += 1; } @@ -158,7 +161,12 @@ fn main() { } } None => { + // Notify the UI that the processing is finished signal_tx.send(NostrSignal::Finish).await.ok(); + // Mark all gift wraps as fetched + // For the next time Coop only needs to process new gift wraps + set_all_gift_wraps_fetched().await; + break; } } @@ -239,7 +247,7 @@ fn main() { // Spawn a task to handle events from nostr channel cx.spawn_in(window, async move |_, cx| { - let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID); + let all_messages = SubscriptionId::new(ALL_MESSAGES_ID); while let Ok(signal) = signal_rx.recv().await { cx.update(|window, cx| { @@ -252,18 +260,26 @@ fn main() { registry.update(cx, |this, cx| { this.load_rooms(window, cx); this.set_loading(false, cx); + // Send a signal to refresh all opened rooms' messages + if let Some(ids) = ChatSpace::all_panels(window, cx) { + this.refresh_rooms(ids, cx); + } }); } // Load chat rooms without setting as finished NostrSignal::PartialFinish => { registry.update(cx, |this, cx| { this.load_rooms(window, cx); + // Send a signal to refresh all opened rooms' messages + if let Some(ids) = ChatSpace::all_panels(window, cx) { + this.refresh_rooms(ids, cx); + } }); } // Load chat rooms without setting as finished NostrSignal::Eose(subscription_id) => { // Only load chat rooms if the subscription ID matches the all_messages_sub_id - if subscription_id == all_messages_sub_id { + if subscription_id == all_messages { registry.update(cx, |this, cx| { this.load_rooms(window, cx); }); @@ -354,7 +370,7 @@ async fn handle_nostr_notifications( mta_tx: &Sender, event_tx: &Sender, ) -> Result<(), Error> { - let new_messages_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID); + let new_messages = SubscriptionId::new(NEW_MESSAGE_ID); let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let mut notifications = client.notifications(); @@ -379,9 +395,9 @@ async fn handle_nostr_notifications( match event.kind { Kind::GiftWrap => { - if *subscription_id == new_messages_sub_id { + if *subscription_id == new_messages { let event = event.as_ref(); - _ = try_unwrap_event(client, signal_tx, mta_tx, event, false).await; + _ = try_unwrap_event(signal_tx, mta_tx, event, false).await; } else { event_tx.send(event.into_owned()).await.ok(); } @@ -459,52 +475,56 @@ async fn sync_data_for_pubkeys(client: &Client, public_keys: BTreeSet } /// Stores an unwrapped event in local database with reference to original -async fn set_unwrapped(client: &Client, root: EventId, event: &Event) -> Result<(), Error> { - // Must be use the random generated keys to sign this event - let event = EventBuilder::new(Kind::ApplicationSpecificData, event.as_json()) - .tags(vec![Tag::identifier(root), Tag::event(root)]) +async fn set_unwrapped(root: EventId, unwrapped: &Event) -> Result<(), Error> { + let client = nostr_client(); + + // Save unwrapped event + client.database().save_event(unwrapped).await?; + + // Create a reference event pointing to the unwrapped event + let event = EventBuilder::new(Kind::ApplicationSpecificData, "") + .tags(vec![Tag::identifier(root), Tag::event(unwrapped.id)]) .sign(&Keys::generate()) .await?; - // Only save this event into the local database + // Save reference event client.database().save_event(&event).await?; Ok(()) } /// Retrieves a previously unwrapped event from local database -async fn get_unwrapped(client: &Client, target: EventId) -> Result { +async fn get_unwrapped(root: EventId) -> Result { + let client = nostr_client(); let filter = Filter::new() .kind(Kind::ApplicationSpecificData) - .identifier(target) - .event(target) + .identifier(root) .limit(1); if let Some(event) = client.database().query(filter).await?.first_owned() { - Ok(Event::from_json(event.content)?) + let target_id = event.tags.event_ids().collect_vec()[0]; + + if let Some(event) = client.database().event_by_id(target_id).await? { + Ok(event) + } else { + Err(anyhow!("Event not found.")) + } } else { - Err(anyhow!("Event is not cached yet")) + Err(anyhow!("Event is not cached yet.")) } } /// Unwraps a gift-wrapped event and processes its contents. -/// -/// # Arguments -/// * `event` - The gift-wrapped event to unwrap -/// * `incoming` - Whether this is a newly received event (true) or old -/// -/// # Returns -/// Returns `true` if the event was successfully loaded from cache or saved after unwrapping. async fn try_unwrap_event( - client: &Client, signal_tx: &Sender, mta_tx: &Sender, event: &Event, incoming: bool, ) -> bool { + let client = nostr_client(); let mut is_cached = false; - let event = match get_unwrapped(client, event.id).await { + let event = match get_unwrapped(event.id).await { Ok(event) => { is_cached = true; event @@ -512,31 +532,32 @@ async fn try_unwrap_event( Err(_) => { match client.unwrap_gift_wrap(event).await { Ok(unwrap) => { + // Sign the unwrapped event with a RANDOM KEYS let Ok(unwrapped) = unwrap.rumor.sign_with_keys(&Keys::generate()) else { + log::error!("Failed to sign event"); return false; }; // Save this event to the database for future use. - if let Err(e) = set_unwrapped(client, event.id, &unwrapped).await { - log::error!("Failed to save event: {e}") + if let Err(e) = set_unwrapped(event.id, &unwrapped).await { + log::warn!("Failed to cache unwrapped event: {e}") } unwrapped } - Err(_) => return false, + Err(e) => { + log::error!("Failed to unwrap event: {e}"); + return false; + } } } }; - // Save the event to the database, use for query directly. - if let Err(e) = client.database().save_event(&event).await { - log::error!("Failed to save event: {e}") - } + // Get all pubkeys from the event + let all_pubkeys = event.all_pubkeys(); - // Send all pubkeys to the batch to sync metadata - mta_tx.send(event.pubkey).await.ok(); - - for public_key in event.tags.public_keys().copied() { + // Send all pubkeys to the metadata batch to sync data + for public_key in all_pubkeys { mta_tx.send(public_key).await.ok(); } diff --git a/crates/coop/src/views/chat/mod.rs b/crates/coop/src/views/chat/mod.rs index 4a7b727..70c430c 100644 --- a/crates/coop/src/views/chat/mod.rs +++ b/crates/coop/src/views/chat/mod.rs @@ -21,7 +21,7 @@ use identity::Identity; use itertools::Itertools; use nostr_sdk::prelude::*; use registry::message::Message; -use registry::room::{Room, RoomKind, SendError}; +use registry::room::{Room, RoomKind, RoomSignal, SendError}; use registry::Registry; use serde::Deserialize; use settings::AppSettings; @@ -111,21 +111,28 @@ impl Chat { subscriptions.push(cx.subscribe_in( &room, window, - move |this, _, incoming, _window, cx| { - // Check if the incoming message is the same as the new message created by optimistic update - if this.prevent_duplicate_message(&incoming.0, cx) { - return; + move |this, _, signal, window, cx| { + match signal { + RoomSignal::NewMessage(event) => { + // Check if the incoming message is the same as the new message created by optimistic update + if this.prevent_duplicate_message(event, cx) { + return; + } + + let old_len = this.messages.read(cx).len(); + let message = event.clone().into_rc(); + + cx.update_entity(&this.messages, |this, cx| { + this.extend(vec![message]); + cx.notify(); + }); + + this.list_state.splice(old_len..old_len, 1); + } + RoomSignal::Refresh => { + this.load_messages(window, cx); + } } - - let old_len = this.messages.read(cx).len(); - let message = incoming.0.clone().into_rc(); - - cx.update_entity(&this.messages, |this, cx| { - this.extend(vec![message]); - cx.notify(); - }); - - this.list_state.splice(old_len..old_len, 1); }, )); @@ -142,10 +149,10 @@ impl Chat { }); Self { + id: room.read(cx).id.to_string().into(), image_cache: RetainAllImageCache::new(cx), focus_handle: cx.focus_handle(), uploading: false, - id: room.read(cx).id.to_string().into(), text_data: HashMap::new(), room, messages, diff --git a/crates/coop/src/views/messaging_relays.rs b/crates/coop/src/views/messaging_relays.rs index 8b88b5b..20c2ec5 100644 --- a/crates/coop/src/views/messaging_relays.rs +++ b/crates/coop/src/views/messaging_relays.rs @@ -1,7 +1,7 @@ use std::time::Duration; use anyhow::{anyhow, Error}; -use global::constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID, NIP17_RELAYS}; +use global::constants::{NEW_MESSAGE_ID, NIP17_RELAYS}; use global::nostr_client; use gpui::prelude::FluentBuilder; use gpui::{ @@ -10,6 +10,7 @@ use gpui::{ TextAlign, UniformList, Window, }; use i18n::{shared_t, t}; +use identity::Identity; use itertools::Itertools; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; @@ -207,28 +208,17 @@ impl MessagingRelays { _ = client.connect_relay(&relay).await; } - let all_msg_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID); - let new_msg_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID); - - let all_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); + let id = SubscriptionId::new(NEW_MESSAGE_ID); let new_messages = Filter::new() .kind(Kind::GiftWrap) .pubkey(public_key) .limit(0); // Close old subscriptions - client.unsubscribe(&all_msg_id).await; - client.unsubscribe(&new_msg_id).await; - - // Subscribe to all messages - client - .subscribe_with_id(all_msg_id, all_messages, None) - .await?; + client.unsubscribe(&id).await; // Subscribe to new messages - client - .subscribe_with_id(new_msg_id, new_messages, None) - .await?; + client.subscribe_with_id(id, new_messages, None).await?; Ok(()) }); @@ -237,6 +227,10 @@ impl MessagingRelays { match task.await { Ok(_) => { cx.update(|window, cx| { + Identity::global(cx).update(cx, |this, cx| { + this.verify_dm_relays(window, cx); + }); + // Close the current modal window.close_modal(cx); }) .ok(); diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index 6242fa2..c11cef1 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -20,7 +20,7 @@ use itertools::Itertools; use list_item::RoomListItem; use nostr_sdk::prelude::*; use registry::room::{Room, RoomKind}; -use registry::{Registry, RoomEmitter}; +use registry::{Registry, RegistrySignal}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use theme::ActiveTheme; @@ -83,7 +83,7 @@ impl Sidebar { &chats, window, move |this, _chats, event, _window, cx| { - if let RoomEmitter::Request(kind) = event { + if let RegistrySignal::NewRequest(kind) = event { this.indicator.update(cx, |this, cx| { *this = Some(kind.to_owned()); cx.notify(); diff --git a/crates/global/src/constants.rs b/crates/global/src/constants.rs index 04d39c8..8955ab0 100644 --- a/crates/global/src/constants.rs +++ b/crates/global/src/constants.rs @@ -37,9 +37,11 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app"; pub const NOSTR_CONNECT_TIMEOUT: u64 = 200; /// Unique ID for new message subscription. -pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwraps"; +pub const NEW_MESSAGE_ID: &str = "listen_new_giftwraps"; /// Unique ID for all messages subscription. -pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps"; +pub const ALL_MESSAGES_ID: &str = "listen_all_giftwraps"; +/// Unique ID for all newest messages subscription. +pub const ALL_NEWEST_MESSAGES_ID: &str = "listen_all_newest_giftwraps"; /// Total metadata requests will be grouped. pub const METADATA_BATCH_LIMIT: usize = 100; diff --git a/crates/global/src/lib.rs b/crates/global/src/lib.rs index 974f882..9b471b1 100644 --- a/crates/global/src/lib.rs +++ b/crates/global/src/lib.rs @@ -1,4 +1,3 @@ -use std::fs; use std::sync::OnceLock; use std::time::Duration; @@ -67,7 +66,7 @@ pub fn first_run() -> &'static bool { let flag = support_dir().join(format!(".{}-first_run", env!("CARGO_PKG_VERSION"))); if !flag.exists() { - if fs::write(&flag, "").is_err() { + if std::fs::write(&flag, "").is_err() { return false; } true // First run @@ -76,3 +75,16 @@ pub fn first_run() -> &'static bool { } }) } + +pub async fn set_all_gift_wraps_fetched() { + let flag = support_dir().join(".fetched"); + + if !flag.exists() && smol::fs::write(&flag, "").await.is_err() { + log::error!("Failed to create full run flag"); + } +} + +pub fn is_gift_wraps_fetch_complete() -> bool { + let flag = support_dir().join(".fetched"); + flag.exists() +} diff --git a/crates/global/src/paths.rs b/crates/global/src/paths.rs index 6860566..3017be2 100644 --- a/crates/global/src/paths.rs +++ b/crates/global/src/paths.rs @@ -60,5 +60,5 @@ pub fn support_dir() -> &'static PathBuf { /// Returns the path to the `nostr` file. pub fn nostr_file() -> &'static PathBuf { static NOSTR_FILE: OnceLock = OnceLock::new(); - NOSTR_FILE.get_or_init(|| support_dir().join("nostr")) + NOSTR_FILE.get_or_init(|| support_dir().join("nostr-db")) } diff --git a/crates/identity/src/lib.rs b/crates/identity/src/lib.rs index 2d8db36..6c872f1 100644 --- a/crates/identity/src/lib.rs +++ b/crates/identity/src/lib.rs @@ -4,10 +4,10 @@ use anyhow::{anyhow, Error}; use client_keys::ClientKeys; use common::handle_auth::CoopAuthUrlHandler; use global::constants::{ - ACCOUNT_D, ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID, NIP17_RELAYS, NIP65_RELAYS, + ACCOUNT_D, ALL_MESSAGES_ID, ALL_NEWEST_MESSAGES_ID, NEW_MESSAGE_ID, NIP17_RELAYS, NIP65_RELAYS, NOSTR_CONNECT_TIMEOUT, }; -use global::nostr_client; +use global::{is_gift_wraps_fetch_complete, nostr_client}; use gpui::prelude::FluentBuilder; use gpui::{ div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled, @@ -550,7 +550,7 @@ impl Identity { .detach(); } - fn verify_dm_relays(&self, window: &mut Window, cx: &mut Context) { + pub fn verify_dm_relays(&self, window: &mut Window, cx: &mut Context) { let Some(public_key) = self.public_key() else { return; }; @@ -632,22 +632,19 @@ impl Identity { } pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> { - let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID); - let new_messages_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID); + let all_messages = SubscriptionId::new(ALL_MESSAGES_ID); + let all_newest_messages = SubscriptionId::new(ALL_NEWEST_MESSAGES_ID); + let new_messages = SubscriptionId::new(NEW_MESSAGE_ID); + // Subscription options let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + // Get the gift wraps fetch status + let is_completed = is_gift_wraps_fetch_complete(); client .subscribe( Filter::new() .author(public_key) - .kinds(vec![ - Kind::Metadata, - Kind::ContactList, - Kind::MuteList, - Kind::SimpleGroups, - Kind::InboxRelays, - Kind::RelayList, - ]) + .kinds(vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]) .since(Timestamp::now()), None, ) @@ -665,15 +662,7 @@ impl Identity { client .subscribe_with_id( - all_messages_sub_id, - Filter::new().kind(Kind::GiftWrap).pubkey(public_key), - Some(opts), - ) - .await?; - - client - .subscribe_with_id( - new_messages_sub_id, + new_messages, Filter::new() .kind(Kind::GiftWrap) .pubkey(public_key) @@ -682,6 +671,26 @@ impl Identity { ) .await?; + if is_completed { + let week_ago: u64 = 7 * 24 * 60 * 60; + let since = Timestamp::from_secs(Timestamp::now().as_u64() - week_ago); + + let filter = Filter::new() + .pubkey(public_key) + .kind(Kind::GiftWrap) + .since(since); + + client + .subscribe_with_id(all_newest_messages, filter, Some(opts)) + .await?; + } else { + let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); + + client + .subscribe_with_id(all_messages, filter, Some(opts)) + .await?; + }; + log::info!("Getting all user's metadata and messages..."); Ok(()) diff --git a/crates/registry/src/lib.rs b/crates/registry/src/lib.rs index af454da..705de7d 100644 --- a/crates/registry/src/lib.rs +++ b/crates/registry/src/lib.rs @@ -31,10 +31,10 @@ struct GlobalRegistry(Entity); impl Global for GlobalRegistry {} #[derive(Debug)] -pub enum RoomEmitter { +pub enum RegistrySignal { Open(WeakEntity), Close(u64), - Request(RoomKind), + NewRequest(RoomKind), } /// Main registry for managing chat rooms and user profiles @@ -55,7 +55,7 @@ pub struct Registry { subscriptions: SmallVec<[Subscription; 2]>, } -impl EventEmitter for Registry {} +impl EventEmitter for Registry {} impl Registry { /// Retrieve the Global Registry state @@ -85,8 +85,10 @@ impl Registry { // When any Room is created, load members metadata subscriptions.push(cx.observe_new::(|this, _window, cx| { + let state = Self::global(cx); let task = this.load_metadata(cx); - Self::global(cx).update(cx, |this, cx| { + + state.update(cx, |this, cx| { this.set_persons_from_task(task, cx); }); })); @@ -207,7 +209,7 @@ impl Registry { /// Close a room. pub fn close_room(&mut self, id: u64, cx: &mut Context) { if self.rooms.iter().any(|r| r.read(cx).id == id) { - cx.emit(RoomEmitter::Close(id)); + cx.emit(RegistrySignal::Close(id)); } } @@ -330,7 +332,6 @@ impl Registry { .ok(); } Err(e) => { - // TODO: push notification log::error!("Failed to load rooms: {e}") } }; @@ -375,7 +376,18 @@ impl Registry { weak_room }; - cx.emit(RoomEmitter::Open(weak_room)); + cx.emit(RegistrySignal::Open(weak_room)); + } + + /// Refresh messages for a room in the global registry + pub fn refresh_rooms(&mut self, ids: Vec, cx: &mut Context) { + for room in self.rooms.iter() { + if ids.contains(&room.read(cx).id) { + room.update(cx, |this, cx| { + this.emit_refresh(cx); + }); + } + } } /// Parse a Nostr event into a Coop Message and push it to the belonging room @@ -420,7 +432,7 @@ impl Registry { // Notify the UI about the new room cx.defer_in(window, move |_this, _window, cx| { - cx.emit(RoomEmitter::Request(RoomKind::default())); + cx.emit(RegistrySignal::NewRequest(RoomKind::default())); }); } } diff --git a/crates/registry/src/room.rs b/crates/registry/src/room.rs index ef67163..d55bf18 100644 --- a/crates/registry/src/room.rs +++ b/crates/registry/src/room.rs @@ -20,7 +20,10 @@ pub(crate) const HOURS_IN_DAY: i64 = 24; pub(crate) const DAYS_IN_MONTH: i64 = 30; #[derive(Debug, Clone)] -pub struct Incoming(pub Message); +pub enum RoomSignal { + NewMessage(Message), + Refresh, +} #[derive(Debug, Clone, PartialEq, Eq)] pub struct SendError { @@ -69,7 +72,7 @@ impl PartialEq for Room { impl Eq for Room {} -impl EventEmitter for Room {} +impl EventEmitter for Room {} impl Room { pub fn new(event: &Event) -> Self { @@ -451,10 +454,16 @@ impl Room { .mentions(mentions) .build() { - cx.emit(Incoming(message)); + cx.emit(RoomSignal::NewMessage(message)); } } + /// Emits a signal to refresh the current room's messages. + pub fn emit_refresh(&mut self, cx: &mut Context) { + cx.emit(RoomSignal::Refresh); + log::info!("refresh room: {}", self.id); + } + /// Creates a temporary message for optimistic updates /// /// This constructs an unsigned message with the current user as the author, diff --git a/crates/ui/src/dock_area/mod.rs b/crates/ui/src/dock_area/mod.rs index ee3a8f6..a8aa865 100644 --- a/crates/ui/src/dock_area/mod.rs +++ b/crates/ui/src/dock_area/mod.rs @@ -4,7 +4,7 @@ use gpui::prelude::FluentBuilder; use gpui::{ actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, - ParentElement as _, Pixels, Render, Styled, Subscription, WeakEntity, Window, + ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; use crate::dock_area::dock::{Dock, DockPlacement}; @@ -195,6 +195,25 @@ impl DockItem { } } + /// Returns all panel ids + pub fn panel_ids(&self, cx: &App) -> Vec { + match self { + Self::Tabs { view, .. } => view.read(cx).panel_ids(cx), + Self::Split { items, .. } => { + let mut total = vec![]; + + for item in items.iter() { + if let DockItem::Tabs { view, .. } = item { + total.extend(view.read(cx).panel_ids(cx)); + } + } + + total + } + Self::Panel { .. } => vec![], + } + } + /// Returns the views of the dock item. pub fn view(&self) -> Arc { match self { @@ -252,6 +271,7 @@ impl DockItem { } } + /// Set the collapsed state of the dock area pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) { match self { DockItem::Tabs { view, .. } => { diff --git a/crates/ui/src/dock_area/tab_panel.rs b/crates/ui/src/dock_area/tab_panel.rs index d83ae12..d301751 100644 --- a/crates/ui/src/dock_area/tab_panel.rs +++ b/crates/ui/src/dock_area/tab_panel.rs @@ -384,6 +384,11 @@ impl TabPanel { }) } + /// Return all panel ids + pub fn panel_ids<'a>(&'a self, cx: &'a App) -> Vec { + self.panels.iter().map(|panel| panel.panel_id(cx)).collect() + } + /// Return true if the tab panel is draggable. /// /// E.g. if the parent and self only have one panel, it is not draggable.