diff --git a/assets/icons/group.svg b/assets/icons/group.svg new file mode 100644 index 0000000..c150fe3 --- /dev/null +++ b/assets/icons/group.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 9225b5d..c599c61 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -691,14 +691,12 @@ impl ChatRegistry { } /// Trigger a refresh of the opened chat rooms by their IDs - pub fn refresh_rooms(&mut self, ids: Option>, cx: &mut Context) { - if let Some(ids) = ids { - for room in self.rooms.iter() { - if ids.contains(&room.read(cx).id) { - room.update(cx, |this, cx| { - this.emit_refresh(cx); - }); - } + pub fn refresh_rooms(&mut self, ids: &[u64], 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); + }); } } } diff --git a/crates/coop/src/dialogs/import.rs b/crates/coop/src/dialogs/import.rs index 5b24f30..0f861bd 100644 --- a/crates/coop/src/dialogs/import.rs +++ b/crates/coop/src/dialogs/import.rs @@ -103,7 +103,7 @@ impl ImportKey { // Update the signer nostr.update(cx, |this, cx| { - this.set_signer(keys, cx); + this.add_key_signer(&keys, cx); }); } else { self.set_error("Invalid key", cx); diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index be74034..a88da6c 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -16,7 +16,7 @@ use nostr_sdk::prelude::*; use person::PersonRegistry; use smallvec::{smallvec, SmallVec}; use state::{NostrRegistry, FIND_DELAY}; -use theme::{ActiveTheme, TABBAR_HEIGHT}; +use theme::{ActiveTheme, SIDEBAR_WIDTH, TABBAR_HEIGHT}; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::indicator::Indicator; @@ -585,10 +585,11 @@ impl Render for Sidebar { ) .when(!show_find_panel && !loading && total_rooms == 0, |this| { this.child( - div().px_2().child( + div().px_2().w(SIDEBAR_WIDTH).child( v_flex() .p_3() .h_24() + .w_full() .border_2() .border_dashed() .border_color(cx.theme().border_variant) diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index 9139e59..43153d7 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -11,7 +11,7 @@ use gpui::{ use person::PersonRegistry; use serde::Deserialize; use smallvec::{smallvec, SmallVec}; -use state::{NostrRegistry, RelayState}; +use state::{NostrRegistry, RelayState, SignerEvent}; use theme::{ActiveTheme, Theme, ThemeRegistry, SIDEBAR_WIDTH}; use title_bar::TitleBar; use ui::avatar::Avatar; @@ -42,6 +42,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity { #[action(namespace = workspace, no_json)] enum Command { ToggleTheme, + ToggleAccount, RefreshEncryption, RefreshRelayList, @@ -85,19 +86,19 @@ impl Workspace { ); subscriptions.push( - // Observe the nostr entity - cx.observe_in(&nostr, window, move |this, nostr, window, cx| { - if nostr.read(cx).connected { - this.set_layout(window, cx); + // Observe the npubs entity + cx.observe_in(&npubs, window, move |this, npubs, window, cx| { + if !npubs.read(cx).is_empty() { + this.account_selector(window, cx); } }), ); subscriptions.push( - // Observe the npubs entity - cx.observe_in(&npubs, window, move |this, npubs, window, cx| { - if !npubs.read(cx).is_empty() { - this.account_selector(window, cx); + // Subscribe to the signer events + cx.subscribe_in(&nostr, window, move |this, _state, event, window, cx| { + if let SignerEvent::Set = event { + this.set_center_layout(window, cx); } }), ); @@ -141,11 +142,16 @@ impl Workspace { let ids = this.panel_ids(cx); chat.update(cx, |this, cx| { - this.refresh_rooms(ids, cx); + this.refresh_rooms(&ids, cx); }); }), ); + // Set the layout at the end of cycle + cx.defer_in(window, |this, window, cx| { + this.set_layout(window, cx); + }); + Self { titlebar, dock, @@ -170,49 +176,40 @@ impl Workspace { } /// Get all panel ids - fn panel_ids(&self, cx: &App) -> Option> { - let ids: Vec = self - .dock + fn panel_ids(&self, cx: &App) -> Vec { + self.dock .read(cx) .items .panel_ids(cx) .into_iter() .filter_map(|panel| panel.parse::().ok()) - .collect(); - - Some(ids) + .collect() } /// Set the dock layout fn set_layout(&mut self, window: &mut Window, cx: &mut Context) { - let weak_dock = self.dock.downgrade(); - - // Sidebar let left = DockItem::panel(Arc::new(sidebar::init(window, cx))); - // Main workspace - let center = DockItem::split_with_sizes( - Axis::Vertical, - vec![DockItem::tabs( - vec![Arc::new(greeter::init(window, cx))], - None, - &weak_dock, - window, - cx, - )], - vec![None], - &weak_dock, - window, - cx, - ); - - // Update the dock layout + // Update the dock layout with sidebar on the left self.dock.update(cx, |this, cx| { this.set_left_dock(left, Some(SIDEBAR_WIDTH), true, window, cx); + }); + } + + /// Set the center dock layout + fn set_center_layout(&mut self, window: &mut Window, cx: &mut Context) { + let dock = self.dock.downgrade(); + let greeeter = Arc::new(greeter::init(window, cx)); + let tabs = DockItem::tabs(vec![greeeter], None, &dock, window, cx); + let center = DockItem::split(Axis::Vertical, vec![tabs], &dock, window, cx); + + // Update the layout with center dock + self.dock.update(cx, |this, cx| { this.set_center(center, window, cx); }); } + /// Handle command events fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context) { match command { Command::ShowSettings => { @@ -305,6 +302,9 @@ impl Workspace { Command::ToggleTheme => { self.theme_selector(window, cx); } + Command::ToggleAccount => { + self.account_selector(window, cx); + } } } @@ -508,6 +508,11 @@ impl Workspace { Box::new(Command::ToggleTheme), ) .separator() + .menu_with_icon( + "Accounts", + IconName::Group, + Box::new(Command::ToggleAccount), + ) .menu_with_icon( "Settings", IconName::Settings, @@ -516,19 +521,6 @@ impl Workspace { }), ) }) - .when(nostr.read(cx).creating, |this| { - this.child(div().text_xs().text_color(cx.theme().text_muted).child( - SharedString::from("Coop is creating a new identity for you..."), - )) - }) - .when(!nostr.read(cx).connected, |this| { - this.child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Connecting...")), - ) - }) } fn titlebar_right(&mut self, _window: &mut Window, cx: &Context) -> impl IntoElement { diff --git a/crates/state/src/constants.rs b/crates/state/src/constants.rs index fba1990..5548121 100644 --- a/crates/state/src/constants.rs +++ b/crates/state/src/constants.rs @@ -40,10 +40,9 @@ pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"]; pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"]; /// Default bootstrap relays -pub const BOOTSTRAP_RELAYS: [&str; 4] = [ - "wss://nos.lol", - "wss://relay.damus.io", +pub const BOOTSTRAP_RELAYS: [&str; 3] = [ "wss://relay.primal.net", + "wss://indexer.coracle.social", "wss://user.kindpag.es", ]; diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 359a718..3fd900e 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -42,16 +42,6 @@ struct GlobalNostrRegistry(Entity); impl Global for GlobalNostrRegistry {} -/// Signer event. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum SignerEvent { - /// A new signer has been set - Set, - - /// An error occurred - Error(String), -} - /// Nostr Registry #[derive(Debug)] pub struct NostrRegistry { @@ -75,12 +65,6 @@ pub struct NostrRegistry { /// Relay list state pub relay_list_state: RelayState, - /// Whether Coop is connected to all bootstrap relays - pub connected: bool, - - /// Whether Coop is creating a new signer - pub creating: bool, - /// Tasks for asynchronous operations tasks: Vec>>, } @@ -140,8 +124,6 @@ impl NostrRegistry { app_keys, gossip, relay_list_state: RelayState::Idle, - connected: false, - creating: false, tasks: vec![], } } @@ -161,12 +143,6 @@ impl NostrRegistry { self.npubs.clone() } - /// Set the connected status of the client - fn set_connected(&mut self, cx: &mut Context) { - self.connected = true; - cx.notify(); - } - /// Connect to the bootstrapping relays fn connect(&mut self, cx: &mut Context) { let client = self.client(); @@ -185,13 +161,12 @@ impl NostrRegistry { } // Connect to all added relays - client.connect().and_wait(Duration::from_secs(5)).await; + client.connect().and_wait(Duration::from_secs(2)).await; }) .await; // Update the state this.update(cx, |this, cx| { - this.set_connected(cx); this.get_npubs(cx); })?; @@ -317,12 +292,6 @@ impl NostrRegistry { })); } - /// Set whether Coop is creating a new signer - fn set_creating(&mut self, creating: bool, cx: &mut Context) { - self.creating = creating; - cx.notify(); - } - /// Create a new identity fn create_identity(&mut self, cx: &mut Context) { let client = self.client(); @@ -335,9 +304,6 @@ impl NostrRegistry { // Create a write credential task let write_credential = cx.write_credentials(&username, &username, &secret); - // Set the creating signer status - self.set_creating(true, cx); - // Run async tasks in background let task: Task> = cx.background_spawn(async move { let signer = async_keys.into_nostr_signer(); @@ -394,8 +360,8 @@ impl NostrRegistry { // Wait for the task to complete task.await?; + // Set signer this.update(cx, |this, cx| { - this.set_creating(false, cx); this.set_signer(keys, cx); })?; @@ -416,8 +382,8 @@ impl NostrRegistry { cx.spawn(async move |_cx| { let (_, secret) = read_credential .await - .map_err(|_| anyhow!("Failed to get signer"))? - .ok_or_else(|| anyhow!("Failed to get signer"))?; + .map_err(|_| anyhow!("Failed to get signer. Please re-import the secret key"))? + .ok_or_else(|| anyhow!("Failed to get signer. Please re-import the secret key"))?; // Try to parse as a direct secret key first if let Ok(secret_key) = SecretKey::from_slice(&secret) { @@ -507,7 +473,7 @@ impl NostrRegistry { let secret = keys.secret_key().to_secret_bytes(); // Write the credential to the keyring - let write_credential = cx.write_credentials(&username, &username, &secret); + let write_credential = cx.write_credentials(&username, "keys", &secret); self.tasks.push(cx.spawn(async move |this, cx| { match write_credential.await { @@ -546,7 +512,7 @@ impl NostrRegistry { Ok((public_key, uri)) => { let username = public_key.to_bech32().unwrap(); let write_credential = this.read_with(cx, |_this, cx| { - cx.write_credentials(&username, &username, uri.to_string().as_bytes()) + cx.write_credentials(&username, "nostrconnect", uri.to_string().as_bytes()) })?; match write_credential.await { @@ -1020,6 +986,16 @@ fn default_messaging_relays() -> Vec { ] } +/// Signer event. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum SignerEvent { + /// A new signer has been set + Set, + + /// An error occurred + Error(String), +} + #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum RelayState { #[default] diff --git a/crates/ui/src/dock_area/mod.rs b/crates/ui/src/dock_area/mod.rs index 2312740..395983e 100644 --- a/crates/ui/src/dock_area/mod.rs +++ b/crates/ui/src/dock_area/mod.rs @@ -2,10 +2,11 @@ use std::sync::Arc; use gpui::prelude::FluentBuilder; use gpui::{ - actions, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, - EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, ParentElement as _, - Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, + actions, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Decorations, + Edges, Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, + ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; +use theme::CLIENT_SIDE_DECORATION_ROUNDING; use crate::dock_area::dock::{Dock, DockPlacement}; use crate::dock_area::panel::{Panel, PanelEvent, PanelStyle, PanelView}; @@ -202,19 +203,16 @@ 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![], + Self::Tabs { view, .. } => view.read(cx).panel_ids(cx), + Self::Split { items, .. } => items + .iter() + .filter_map(|item| match item { + DockItem::Tabs { view, .. } => Some(view.read(cx).panel_ids(cx)), + _ => None, + }) + .flatten() + .collect(), } } @@ -745,6 +743,7 @@ impl EventEmitter for DockArea {} impl Render for DockArea { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity().clone(); + let decorations = window.window_decorations(); div() .id("dock-area") @@ -754,7 +753,17 @@ impl Render for DockArea { .on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds)) .map(|this| { if let Some(zoom_view) = self.zoom_view.clone() { - this.child(zoom_view) + this.map(|this| match decorations { + Decorations::Server => this, + Decorations::Client { tiling } => this + .when(!(tiling.top || tiling.right), |div| { + div.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING) + }) + .when(!(tiling.top || tiling.left), |div| { + div.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING) + }), + }) + .child(zoom_view) } else { // render dock this.child( diff --git a/crates/ui/src/dock_area/tab_panel.rs b/crates/ui/src/dock_area/tab_panel.rs index 6e412fa..252e8e5 100644 --- a/crates/ui/src/dock_area/tab_panel.rs +++ b/crates/ui/src/dock_area/tab_panel.rs @@ -1080,8 +1080,10 @@ impl TabPanel { window: &mut Window, cx: &mut Context, ) { - if let Some(panel) = self.active_panel(cx) { - self.remove_panel(&panel, window, cx); + if self.panels.len() > 1 { + if let Some(panel) = self.active_panel(cx) { + self.remove_panel(&panel, window, cx); + } } } } diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index 8797aa6..2e293a4 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -60,6 +60,7 @@ pub enum IconName { Sun, Ship, Shield, + Group, UserKey, Upload, Usb, @@ -133,6 +134,7 @@ impl IconNamed for IconName { Self::UserKey => "icons/user-key.svg", Self::Upload => "icons/upload.svg", Self::Usb => "icons/usb.svg", + Self::Group => "icons/group.svg", Self::PanelLeft => "icons/panel-left.svg", Self::PanelLeftOpen => "icons/panel-left-open.svg", Self::PanelRight => "icons/panel-right.svg", diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 0eab87e..b3ae977 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -249,7 +249,6 @@ impl Render for Root { div() .id("window") .size_full() - .bg(gpui::transparent_black()) .map(|div| match decorations { Decorations::Server => div, Decorations::Client { tiling } => div