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