diff --git a/crates/app/src/states/chat.rs b/crates/app/src/states/chat.rs index 66ca45f..750b9fe 100644 --- a/crates/app/src/states/chat.rs +++ b/crates/app/src/states/chat.rs @@ -2,12 +2,42 @@ use gpui::*; use nostr_sdk::prelude::*; use serde::Deserialize; +use crate::utils::get_room_id; + #[derive(Clone, PartialEq, Eq, Deserialize)] pub struct Room { + pub id: SharedString, pub owner: PublicKey, pub members: Vec, pub last_seen: Timestamp, - pub title: Option, + pub title: Option, +} + +impl Room { + pub fn new(event: Event) -> Self { + let owner = event.pubkey; + let last_seen = event.created_at; + // Get all members from event's tag + let members: Vec = event.tags.public_keys().copied().collect(); + // Get title from event's tag + let title = if let Some(tag) = event.tags.find(TagKind::Title) { + tag.content().map(|s| s.to_owned().into()) + } else { + // TODO: create random name? + None + }; + + // Get unique id based on members + let id = get_room_id(&owner, &members).into(); + + Self { + id, + title, + members, + last_seen, + owner, + } + } } #[derive(Clone, Debug)] diff --git a/crates/app/src/utils.rs b/crates/app/src/utils.rs index d9d835a..7868e3d 100644 --- a/crates/app/src/utils.rs +++ b/crates/app/src/utils.rs @@ -27,6 +27,21 @@ pub fn get_keys_by_account(public_key: PublicKey) -> Result Ok(keys) } +pub fn get_room_id(owner: &PublicKey, public_keys: &[PublicKey]) -> String { + let hex: Vec = public_keys + .iter() + .map(|m| { + let hex = m.to_hex(); + let split = &hex[..6]; + + split.to_owned() + }) + .collect(); + let mems = hex.join("-"); + + format!("{}-{}", &owner.to_hex()[..6], mems) +} + pub fn show_npub(public_key: PublicKey, len: usize) -> String { let bech32 = public_key.to_bech32().unwrap_or_default(); let separator = " ... "; diff --git a/crates/app/src/views/dock/chat/mod.rs b/crates/app/src/views/dock/chat/mod.rs index 89b3aad..f9fda8f 100644 --- a/crates/app/src/views/dock/chat/mod.rs +++ b/crates/app/src/views/dock/chat/mod.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{Panel, PanelEvent, PanelState, TitleStyle}, + dock::{Panel, PanelEvent, PanelState}, popup_menu::PopupMenu, }; use gpui::*; @@ -18,11 +18,13 @@ pub struct ChatPanel { zoomable: bool, focus_handle: FocusHandle, // Room + id: SharedString, room: View, } impl ChatPanel { pub fn new(room: &Arc, cx: &mut WindowContext) -> View { + let id = room.id.clone(); let room = cx.new_view(|cx| { let view = ChatRoom::new(room, cx); // Load messages @@ -38,24 +40,21 @@ impl ChatPanel { closeable: true, zoomable: true, focus_handle: cx.focus_handle(), + id, room, }) } } impl Panel for ChatPanel { - fn panel_name(&self) -> &'static str { - "ChatPanel" + fn panel_name(&self) -> SharedString { + self.id.clone() } fn title(&self, _cx: &WindowContext) -> AnyElement { self.name.clone().into_any_element() } - fn title_style(&self, _cx: &WindowContext) -> Option { - None - } - fn closeable(&self, _cx: &WindowContext) -> bool { self.closeable } diff --git a/crates/app/src/views/dock/chat/room.rs b/crates/app/src/views/dock/chat/room.rs index d35043a..3e8fbe7 100644 --- a/crates/app/src/views/dock/chat/room.rs +++ b/crates/app/src/views/dock/chat/room.rs @@ -181,22 +181,20 @@ impl ChatRoom { cx.foreground_executor() .spawn({ let client = get_client(); + let owner = self.owner; let members = self.members.to_vec(); + let recv = Filter::new() + .kind(Kind::PrivateDirectMessage) + .author(owner) + .pubkeys(members.clone()); + + let send = Filter::new() + .kind(Kind::PrivateDirectMessage) + .authors(members) + .pubkey(owner); + async move { - let signer = client.signer().await.unwrap(); - let public_key = signer.get_public_key().await.unwrap(); - - let recv = Filter::new() - .kind(Kind::PrivateDirectMessage) - .authors(members.clone()) - .pubkey(public_key); - - let send = Filter::new() - .kind(Kind::PrivateDirectMessage) - .author(public_key) - .pubkeys(members); - let events = async_cx .background_executor() .spawn(async move { client.database().query(vec![recv, send]).await }) diff --git a/crates/app/src/views/dock/inbox/item.rs b/crates/app/src/views/dock/inbox/item.rs index 9ccb4ee..ae86ace 100644 --- a/crates/app/src/views/dock/inbox/item.rs +++ b/crates/app/src/views/dock/inbox/item.rs @@ -126,7 +126,7 @@ impl RenderOnce for Item { ) .on_click(move |_, cx| { cx.dispatch_action(Box::new(AddPanel { - room: self.room.clone(), + room: Arc::clone(&self.room), position: coop_ui::dock::DockPlacement::Center, })) }) @@ -136,31 +136,15 @@ impl RenderOnce for Item { pub struct InboxItem { room: Arc, metadata: Model>, - pub(crate) sender: PublicKey, } impl InboxItem { pub fn new(event: Event, cx: &mut ViewContext<'_, Self>) -> Self { - let sender = event.pubkey; - let last_seen = event.created_at; - - // Get all members from event's tag - let mut members: Vec = event.tags.public_keys().copied().collect(); - // Add sender to members - members.insert(0, sender); - - // Get title from event's tag - let title = if let Some(tag) = event.tags.find(TagKind::Title) { - tag.content().map(|s| s.to_string()) - } else { - // TODO: create random name? - None - }; - + let room = Arc::new(Room::new(event)); let metadata = cx.new_model(|_| None); // Request metadata - _ = cx.global::().tx.send(sender); + _ = cx.global::().tx.send(room.owner); // Reload when received metadata cx.observe_global::(|chat, cx| { @@ -168,22 +152,11 @@ impl InboxItem { }) .detach(); - let room = Arc::new(Room { - title, - members, - last_seen, - owner: sender, - }); - - Self { - room, - sender, - metadata, - } + Self { room, metadata } } pub fn load_metadata(&mut self, cx: &mut ViewContext) { - let public_key = self.sender; + let public_key = self.room.owner; let async_metadata = self.metadata.clone(); let mut async_cx = cx.to_async(); @@ -205,9 +178,13 @@ impl InboxItem { .detach(); } + pub fn id(&self) -> String { + self.room.id.clone().into() + } + fn render_item(&self, cx: &mut ViewContext) -> impl IntoElement { let metadata = self.metadata.read(cx).clone(); - let room = self.room.clone(); + let room = Arc::clone(&self.room); Item::new(room, metadata) } diff --git a/crates/app/src/views/dock/inbox/mod.rs b/crates/app/src/views/dock/inbox/mod.rs index b3f5b7a..f9bd42f 100644 --- a/crates/app/src/views/dock/inbox/mod.rs +++ b/crates/app/src/views/dock/inbox/mod.rs @@ -8,7 +8,7 @@ use nostr_sdk::prelude::*; use prelude::FluentBuilder; use std::cmp::Reverse; -use crate::{get_client, states::chat::ChatRegistry}; +use crate::{get_client, states::chat::ChatRegistry, utils::get_room_id}; pub mod item; @@ -35,15 +35,18 @@ impl Inbox { let new_messages = state.new_messages.clone(); // Get all current chats - let current: Vec = items - .iter() - .map(|item| item.model.read(cx).sender) - .collect(); + let current_rooms: Vec = + items.iter().map(|item| item.model.read(cx).id()).collect(); - // Create view for only new chats + // Create view for new chats only let new = new_messages .into_iter() - .filter(|m| current.iter().any(|pk| pk == &m.event.pubkey)) + .filter(|m| { + let keys = m.event.tags.public_keys().copied().collect::>(); + let nid = get_room_id(&m.event.pubkey, &keys); + + current_rooms.iter().any(|id| id == &nid) + }) .map(|m| cx.new_view(|cx| InboxItem::new(m.event, cx))) .collect::>(); diff --git a/crates/app/src/views/dock/left_dock.rs b/crates/app/src/views/dock/left_dock.rs index 0a08d9c..1df36c0 100644 --- a/crates/app/src/views/dock/left_dock.rs +++ b/crates/app/src/views/dock/left_dock.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{Panel, PanelEvent, PanelState, TitleStyle}, + dock::{Panel, PanelEvent, PanelState}, popup_menu::PopupMenu, scroll::ScrollbarAxis, StyledExt, @@ -40,18 +40,14 @@ impl LeftDock { } impl Panel for LeftDock { - fn panel_name(&self) -> &'static str { - "ChatPanel" + fn panel_name(&self) -> SharedString { + "LeftDock".into() } fn title(&self, _cx: &WindowContext) -> AnyElement { self.name.clone().into_any_element() } - fn title_style(&self, _cx: &WindowContext) -> Option { - None - } - fn closeable(&self, _cx: &WindowContext) -> bool { self.closeable } diff --git a/crates/app/src/views/dock/welcome.rs b/crates/app/src/views/dock/welcome.rs index d7a0077..2efa29a 100644 --- a/crates/app/src/views/dock/welcome.rs +++ b/crates/app/src/views/dock/welcome.rs @@ -1,6 +1,6 @@ use coop_ui::{ button::Button, - dock::{Panel, PanelEvent, PanelState, TitleStyle}, + dock::{Panel, PanelEvent, PanelState}, popup_menu::PopupMenu, theme::{ActiveTheme, Colorize}, StyledExt, @@ -30,18 +30,14 @@ impl WelcomePanel { } impl Panel for WelcomePanel { - fn panel_name(&self) -> &'static str { - "WelcomePanel" + fn panel_name(&self) -> SharedString { + "WelcomePanel".into() } fn title(&self, _cx: &WindowContext) -> AnyElement { self.name.clone().into_any_element() } - fn title_style(&self, _cx: &WindowContext) -> Option { - None - } - fn closeable(&self, _cx: &WindowContext) -> bool { self.closeable } diff --git a/crates/ui/src/dock/invalid_panel.rs b/crates/ui/src/dock/invalid_panel.rs index 7e514e3..f2496fe 100644 --- a/crates/ui/src/dock/invalid_panel.rs +++ b/crates/ui/src/dock/invalid_panel.rs @@ -22,8 +22,8 @@ impl InvalidPanel { } } impl Panel for InvalidPanel { - fn panel_name(&self) -> &'static str { - "InvalidPanel" + fn panel_name(&self) -> SharedString { + "InvalidPanel".into() } fn dump(&self, _cx: &AppContext) -> super::PanelState { diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index f51d54a..abbfed8 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -32,18 +32,13 @@ pub trait Panel: EventEmitter + FocusableView { /// /// This is used to identify the panel when deserializing the panel. /// Once you have defined a panel name, this must not be changed. - fn panel_name(&self) -> &'static str; + fn panel_name(&self) -> SharedString; /// The title of the panel fn title(&self, _cx: &WindowContext) -> AnyElement { SharedString::from("Untitled").into_any_element() } - /// The theme of the panel title, default is `None`. - fn title_style(&self, _cx: &WindowContext) -> Option { - None - } - /// Whether the panel can be closed, default is `true`. fn closeable(&self, _cx: &WindowContext) -> bool { true @@ -71,9 +66,8 @@ pub trait Panel: EventEmitter + FocusableView { } pub trait PanelView: 'static + Send + Sync { - fn panel_name(&self, _cx: &AppContext) -> &'static str; + fn panel_name(&self, cx: &WindowContext) -> SharedString; fn title(&self, _cx: &WindowContext) -> AnyElement; - fn title_style(&self, _cx: &WindowContext) -> Option; fn closeable(&self, cx: &WindowContext) -> bool; fn zoomable(&self, cx: &WindowContext) -> bool; fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; @@ -84,7 +78,7 @@ pub trait PanelView: 'static + Send + Sync { } impl PanelView for View { - fn panel_name(&self, cx: &AppContext) -> &'static str { + fn panel_name(&self, cx: &WindowContext) -> SharedString { self.read(cx).panel_name() } @@ -92,10 +86,6 @@ impl PanelView for View { self.read(cx).title(cx) } - fn title_style(&self, cx: &WindowContext) -> Option { - self.read(cx).title_style(cx) - } - fn closeable(&self, cx: &WindowContext) -> bool { self.read(cx).closeable(cx) } diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index a86f4a8..692e00f 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -1,8 +1,5 @@ -use gpui::{ - prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle, - FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, View, - ViewContext, VisualContext, WeakView, -}; +use gpui::*; +use prelude::FluentBuilder; use smallvec::SmallVec; use std::sync::Arc; @@ -28,8 +25,8 @@ pub struct StackPanel { } impl Panel for StackPanel { - fn panel_name(&self) -> &'static str { - "StackPanel" + fn panel_name(&self) -> SharedString { + "StackPanel".into() } fn title(&self, _cx: &gpui::WindowContext) -> gpui::AnyElement { diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 55a380a..40e2d5c 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -71,7 +71,6 @@ pub struct TabPanel { /// If this is true, the Panel closeable will follow the active panel's closeable, /// otherwise this TabPanel will not able to close pub(crate) closeable: bool, - tab_bar_scroll_handle: ScrollHandle, is_zoomed: bool, is_collapsed: bool, @@ -80,8 +79,8 @@ pub struct TabPanel { } impl Panel for TabPanel { - fn panel_name(&self) -> &'static str { - "TabPanel" + fn panel_name(&self) -> SharedString { + "TabPanel".into() } fn title(&self, cx: &WindowContext) -> gpui::AnyElement { @@ -180,25 +179,32 @@ impl TabPanel { active: bool, cx: &mut ViewContext, ) { - assert_ne!( - panel.panel_name(cx), - "StackPanel", - "can not allows add `StackPanel` to `TabPanel`" - ); - if self .panels .iter() - .any(|p| p.view().entity_id() == panel.view().entity_id()) + .any(|p| p.panel_name(cx) == panel.panel_name(cx)) { + // set the active panel to the matched panel + if active { + if let Some(ix) = self + .panels + .iter() + .position(|p| p.panel_name(cx) == panel.panel_name(cx)) + { + self.set_active_ix(ix, cx); + } + } + return; } self.panels.push(panel); + // set the active panel to the new panel if active { self.set_active_ix(self.panels.len() - 1, cx); } + cx.emit(PanelEvent::LayoutChanged); cx.notify(); } @@ -466,7 +472,6 @@ impl TabPanel { if self.panels.len() == 1 && panel_style == PanelStyle::Default { let panel = self.panels.first().unwrap(); - let title_style = panel.title_style(cx); return h_flex() .justify_between() @@ -477,9 +482,6 @@ impl TabPanel { .px_3() .when(left_dock_button.is_some(), |this| this.pl_2()) .when(right_dock_button.is_some(), |this| this.pr_2()) - .when_some(title_style, |this, theme| { - this.bg(theme.background).text_color(theme.foreground) - }) .when( left_dock_button.is_some() || bottom_dock_button.is_some(), |this| { diff --git a/crates/ui/src/dock/tiles.rs b/crates/ui/src/dock/tiles.rs index 199e227..08a6062 100644 --- a/crates/ui/src/dock/tiles.rs +++ b/crates/ui/src/dock/tiles.rs @@ -1,10 +1,4 @@ -use gpui::{ - canvas, div, point, px, size, AnyElement, AppContext, Bounds, DismissEvent, DragMoveEvent, - Entity, EntityId, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement, - IntoElement, MouseButton, MouseDownEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, - ScrollHandle, Size, StatefulInteractiveElement, Styled, ViewContext, VisualContext, WeakView, - WindowContext, -}; +use gpui::*; use std::{ cell::Cell, fmt::{Debug, Formatter}, @@ -92,8 +86,8 @@ pub struct Tiles { } impl Panel for Tiles { - fn panel_name(&self) -> &'static str { - "Tiles" + fn panel_name(&self) -> SharedString { + "Tiles".into() } fn title(&self, _cx: &WindowContext) -> AnyElement {