update sidebar for onboarding

This commit is contained in:
2026-09-20 08:35:45 +07:00
parent 974f8302a1
commit 8401628412
6 changed files with 142 additions and 34 deletions
+25 -1
View File
@@ -87,6 +87,9 @@ pub struct NostrRegistry {
/// Current user's public key
current_user: Option<PublicKey>,
/// Whether the initial credential check has concluded
ready: bool,
/// Tasks for asynchronous operations
tasks: Vec<Task<Result<(), Error>>>,
}
@@ -140,6 +143,7 @@ impl NostrRegistry {
this.connect_bootstrap_relays(cx);
if cfg!(target_arch = "wasm32") {
this.mark_ready(cx);
cx.emit(StateEvent::NoSigner);
} else if let Some(secret) = cli_key {
// Use CLI-provided key -- same path as get_user_credential
@@ -156,6 +160,7 @@ impl NostrRegistry {
client,
signer,
current_user: None,
ready: false,
tasks: vec![],
}
}
@@ -175,6 +180,20 @@ impl NostrRegistry {
self.current_user
}
/// Whether the initial credential check has concluded
pub fn ready(&self) -> bool {
self.ready
}
fn mark_ready(&mut self, cx: &mut Context<Self>) {
if self.ready {
return;
}
self.ready = true;
cx.notify();
}
/// Update the signer
pub fn set_signer<T>(&mut self, new_signer: T, cx: &mut Context<Self>)
where
@@ -189,6 +208,7 @@ impl NostrRegistry {
this.update(cx, |this, cx| {
this.signer.swap_inner(new_signer);
this.current_user = Some(public_key);
this.mark_ready(cx);
cx.emit(StateEvent::SignerChanged);
cx.notify();
})?;
@@ -275,12 +295,16 @@ impl NostrRegistry {
} else if content == "proxy" {
#[cfg(not(target_arch = "wasm32"))]
this.update(cx, |this, cx| {
this.mark_ready(cx);
this.connect_proxy(cx);
})?;
} else {
this.update(cx, |this, cx| this.mark_ready(cx))?;
}
}
_ => {
this.update(cx, |_, cx| {
this.update(cx, |this, cx| {
this.mark_ready(cx);
cx.emit(StateEvent::NoSigner);
})?;
}
+15 -2
View File
@@ -1,8 +1,8 @@
use anyhow::{Error, anyhow};
use gpui::prelude::FluentBuilder;
use gpui::{
AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, div,
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, div, px,
};
use instant::Duration;
use nostr_connect::prelude::*;
@@ -12,6 +12,19 @@ use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState};
use ui::{Disableable, StyledExt, WindowExtension, divider, v_flex};
pub fn open(window: &mut Window, cx: &mut App) {
let import = cx.new(|cx| ImportIdentity::new(window, cx));
window.open_modal(cx, move |this, _window, _cx| {
this.width(px(450.))
.show_close(false)
.overlay_closable(false)
.keyboard(false)
.title("Onboarding")
.child(import.clone())
});
}
#[derive(Debug)]
pub struct ImportIdentity {
/// Secret key input
+4 -24
View File
@@ -24,7 +24,6 @@ use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::notification::{Notification, NotificationKind};
use ui::{Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
use crate::dialogs::import::ImportIdentity;
use crate::dialogs::restore::RestoreEncryption;
use crate::dialogs::{new_chat, new_community, settings};
use crate::panels::{
@@ -97,16 +96,10 @@ impl Workspace {
subscriptions.push(
// Subscribe to the nostr events
cx.subscribe_in(&nostr, window, move |this, _state, event, window, cx| {
match event {
StateEvent::SignerChanged => {
window.close_all_modals(cx);
}
StateEvent::NoSigner => {
this.import_identity(window, cx);
}
_ => {}
};
cx.subscribe_in(&nostr, window, move |_this, _state, event, window, cx| {
if let StateEvent::SignerChanged = event {
window.close_all_modals(cx);
}
}),
);
@@ -463,19 +456,6 @@ impl Workspace {
});
}
fn import_identity(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let import = cx.new(|cx| ImportIdentity::new(window, cx));
window.open_modal(cx, move |this, _window, _cx| {
this.width(px(450.))
.show_close(false)
.overlay_closable(false)
.keyboard(false)
.title("Onboarding")
.child(import.clone())
});
}
fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
window.open_modal(cx, move |this, _window, cx| {
let registry = ThemeRegistry::global(cx);
+24 -3
View File
@@ -31,6 +31,7 @@ use ui::{
use crate::Command;
mod onboarding;
mod tab;
mod tree;
@@ -44,13 +45,14 @@ pub struct Sidebar {
active_tab: SidebarTab,
/// Whether there are new chat requests
new_requests: bool,
_subscriptions: SmallVec<[Subscription; 2]>,
_subscriptions: SmallVec<[Subscription; 3]>,
}
impl Sidebar {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let chat = ChatRegistry::global(cx);
let communities = CommunityRegistry::global(cx);
let nostr = NostrRegistry::global(cx);
let mut subscriptions = smallvec![];
@@ -74,6 +76,8 @@ impl Sidebar {
},
));
subscriptions.push(cx.observe(&nostr, |_this, _nostr, cx| cx.notify()));
Self {
focus_handle: cx.focus_handle(),
scroll_handles: [
@@ -441,10 +445,26 @@ impl Focusable for Sidebar {
impl Render for Sidebar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let nostr = NostrRegistry::global(cx);
let logged_in = nostr.read(cx).current_user().is_some();
let (logged_in, ready) = {
let nostr = nostr.read(cx);
(nostr.current_user().is_some(), nostr.ready())
};
if !logged_in {
if !ready {
return v_flex()
.size_full()
.bg(cx.theme().surface_background)
.border_r_1()
.border_color(cx.theme().border_variant)
.into_any_element();
}
return onboarding::render(window, cx).into_any_element();
}
let chat = ChatRegistry::global(cx);
let loading = chat.read(cx).loading() && logged_in;
let loading = chat.read(cx).loading();
let sidebar = cx.entity().downgrade();
let active_tab = self.active_tab;
@@ -592,5 +612,6 @@ impl Render for Sidebar {
),
)
})
.into_any_element()
}
}
@@ -0,0 +1,74 @@
use gpui::{App, InteractiveElement, IntoElement, ParentElement, Styled, Window, div, svg};
use theme::{ActiveTheme, TABBAR_HEIGHT};
use ui::button::{Button, ButtonVariants};
use ui::{StyledExt, h_flex, title_bar_drag_handlers, v_flex};
use crate::dialogs::import;
const TITLE: &str = "Welcome to Coop!";
const DESCRIPTION: &str = "Chat Freely, Stay Private on Nostr.";
pub(super) fn render(window: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex()
.size_full()
.relative()
.bg(cx.theme().surface_background)
.child(title_bar_drag_handlers(
div()
.id("onboarding-drag")
.absolute()
.top_0()
.left_0()
.h(TABBAR_HEIGHT)
.w_full(),
window,
cx,
))
.child(
v_flex()
.size_full()
.justify_end()
.gap_4()
.p_4()
.child(
h_flex()
.gap_2()
.child(
svg()
.path("brand/coop.svg")
.size_8()
.text_color(cx.theme().icon_muted),
)
.child(
v_flex().child(div().font_semibold().child(TITLE)).child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(DESCRIPTION),
),
),
)
.child(
v_flex()
.gap_2()
.w_full()
.child(
Button::new("join-now")
.label("Join now")
.primary()
.font_semibold()
.h_8()
.w_full(),
)
.child(
Button::new("import-identity")
.label("Import identity")
.secondary()
.font_semibold()
.h_8()
.w_full()
.on_click(|_event, window, cx| import::open(window, cx)),
),
),
)
}
-4
View File
@@ -48,10 +48,6 @@ impl SidebarTab {
}
}
pub fn recents(self) -> bool {
matches!(self, Self::Recents)
}
pub fn chat(self) -> bool {
matches!(self, Self::Chats)
}