From ed8ce41583b4c0e50370496e729419a2308b7a29 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 30 Jul 2026 14:23:07 +0700 Subject: [PATCH] finish ui --- Cargo.toml | 2 +- crates/ui/src/styled.rs | 2 +- crates/workspace/src/lib.rs | 81 +++------- crates/workspace/src/panels/contact_list.rs | 144 +++++++++-------- crates/workspace/src/panels/greeter.rs | 15 +- .../workspace/src/panels/messaging_relays.rs | 1 - crates/workspace/src/panels/mod.rs | 1 - crates/workspace/src/panels/profile.rs | 1 - crates/workspace/src/panels/relay_list.rs | 1 - crates/workspace/src/panels/trash.rs | 152 ------------------ crates/workspace/src/sidebar/mod.rs | 5 +- web/src/lib.rs | 2 + 12 files changed, 109 insertions(+), 298 deletions(-) delete mode 100644 crates/workspace/src/panels/trash.rs diff --git a/Cargo.toml b/Cargo.toml index 3ac4266..980975d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "ni # Others anyhow = "1.0.44" -chrono = "0.4.38" +chrono = { version = "0.4.38", features = ["wasmbind"] } futures = "0.3" itertools = "0.13.0" log = "0.4" diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index 8f3f981..45d04b6 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -87,7 +87,7 @@ pub trait StyledExt: Styled + Sized { .border_1() .border_color(cx.theme().border) .shadow_md() - .rounded(cx.theme().radius) + .rounded(cx.theme().radius_lg) } } diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 1159f54..cd26935 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -8,8 +8,7 @@ use device::{DeviceEvent, DeviceRegistry}; use gpui::prelude::FluentBuilder; use gpui::{ Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement, - Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window, div, - image_cache, px, relative, + Render, SharedString, Styled, Subscription, Task, Window, div, image_cache, px, }; use nostr_sdk::prelude::*; use person::{PersonRegistry, shorten_pubkey}; @@ -27,7 +26,7 @@ use ui::{Icon, IconName, Root, Sizable, TitleBar, WindowExtension, h_flex, v_fle use crate::dialogs::import::ImportIdentity; use crate::dialogs::restore::RestoreEncryption; use crate::dialogs::settings; -use crate::panels::{backup, contact_list, greeter, messaging_relays, profile, relay_list, trash}; +use crate::panels::{backup, contact_list, greeter, messaging_relays, profile, relay_list}; use crate::sidebar::Sidebar; mod dialogs; @@ -277,7 +276,7 @@ impl Workspace { self.dock.update(cx, |this, cx| { this.add_panel( Arc::new(profile::init(public_key, window, cx)), - DockPlacement::Right, + DockPlacement::Left, window, cx, ); @@ -288,7 +287,7 @@ impl Workspace { self.dock.update(cx, |this, cx| { this.add_panel( Arc::new(contact_list::init(window, cx)), - DockPlacement::Right, + DockPlacement::Left, window, cx, ); @@ -298,7 +297,7 @@ impl Workspace { self.dock.update(cx, |this, cx| { this.add_panel( Arc::new(backup::init(window, cx)), - DockPlacement::Right, + DockPlacement::Left, window, cx, ); @@ -308,7 +307,7 @@ impl Workspace { self.dock.update(cx, |this, cx| { this.add_panel( Arc::new(messaging_relays::init(window, cx)), - DockPlacement::Right, + DockPlacement::Left, window, cx, ); @@ -435,10 +434,11 @@ impl Workspace { let import = cx.new(|cx| ImportIdentity::new(window, cx)); window.open_modal(cx, move |this, _window, _cx| { - this.width(px(420.)) + this.width(px(450.)) .show_close(false) .overlay_closable(false) - .title("Import Identity") + .keyboard(false) + .title("Onboarding") .child(import.clone()) }); } @@ -598,9 +598,7 @@ impl Workspace { fn titlebar_right(&mut self, cx: &mut Context) -> impl IntoElement { let chat = ChatRegistry::global(cx); - let trash_messages = chat.read(cx).count_trash_messages(cx); - - let is_nip4e_enabled = AppSettings::get_nip4e(cx); + let nip4e_enabled = AppSettings::get_nip4e(cx); let nostr = NostrRegistry::global(cx); let Some(public_key) = nostr.read(cx).current_user() else { @@ -614,39 +612,7 @@ impl Workspace { h_flex() .when(!cx.theme().platform.is_mac(), |this| this.pr_2()) .gap_2() - .when(trash_messages > 0, |this| { - this.child( - h_flex() - .id("trash-messages") - .h_6() - .px_1() - .gap_1() - .rounded(cx.theme().radius) - .hover(|this| this.bg(cx.theme().ghost_element_hover)) - .child( - Icon::new(IconName::Warning) - .small() - .text_color(cx.theme().text_danger), - ) - .child( - div() - .text_xs() - .line_height(relative(1.)) - .child(format!("{trash_messages}")), - ) - .on_click(move |_ev, window, cx| { - cx.stop_propagation(); - // Add the trash panel to the center workspace - Self::add_panel( - trash::init(window, cx), - DockPlacement::Center, - window, - cx, - ); - }), - ) - }) - .when(is_nip4e_enabled, |this| { + .when(nip4e_enabled, |this| { this.child( Button::new("key") .icon(IconName::UserKey) @@ -738,17 +704,7 @@ impl Workspace { .w_full() .text_sm() .justify_between() - .child( - h_flex() - .gap_2() - .child( - div() - .size_1p5() - .rounded_full() - .bg(cx.theme().icon_accent), - ) - .child(url.clone()), - ) + .child(url.clone()) .child( div() .text_xs() @@ -760,11 +716,6 @@ impl Workspace { // Footer menu.separator() - .menu_with_icon( - "Reload", - IconName::Refresh, - Box::new(Command::RefreshMessagingRelays), - ) .menu_with_icon( "Manage gossip relays", IconName::Relay, @@ -772,9 +723,15 @@ impl Workspace { ) .menu_with_icon( "Manage messaging relays", - IconName::Settings, + IconName::Relay, Box::new(Command::ShowMessaging), ) + .separator() + .menu_with_icon( + "Reload", + IconName::Refresh, + Box::new(Command::RefreshMessagingRelays), + ) }), ) } diff --git a/crates/workspace/src/panels/contact_list.rs b/crates/workspace/src/panels/contact_list.rs index 6136af5..1152688 100644 --- a/crates/workspace/src/panels/contact_list.rs +++ b/crates/workspace/src/panels/contact_list.rs @@ -17,6 +17,7 @@ use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; use ui::dock::{Panel, PanelEvent}; use ui::input::{Input, InputEvent, InputState}; +use ui::scroll::ScrollableElement; use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -220,8 +221,7 @@ impl ContactListPanel { .px_2() .justify_between() .rounded(cx.theme().radius) - .bg(cx.theme().secondary_background) - .text_color(cx.theme().secondary_foreground) + .hover(|this| this.bg(cx.theme().ghost_element_hover)) .child( h_flex() .gap_2() @@ -283,74 +283,78 @@ impl Focusable for ContactListPanel { impl Render for ContactListPanel { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - v_flex().p_3().gap_3().w_full().child( - v_flex() - .gap_2() - .flex_1() - .w_full() - .text_sm() - .child( - div() - .text_xs() - .font_semibold() - .text_color(cx.theme().text_muted) - .child(SharedString::from("New contact:")), - ) - .child( - v_flex() - .gap_1() - .child( - h_flex() - .gap_1() - .w_full() - .child(Input::new(&self.input).small().cleanable(true)) - .child( - Button::new("add") - .icon(IconName::Plus) - .tooltip("Add contact") - .ghost() - .size(rems(2.)) - .on_click(cx.listener(move |this, _, window, cx| { - this.add(window, cx); - })), - ), - ) - .when_some(self.error.as_ref(), |this, error| { - this.child( - div() - .italic() - .text_xs() - .text_color(cx.theme().text_danger) - .child(error.clone()), + v_flex() + .p_3() + .gap_3() + .w_full() + .overflow_y_scrollbar() + .child( + v_flex() + .gap_2() + .flex_1() + .w_full() + .text_sm() + .child( + div() + .text_xs() + .font_semibold() + .text_color(cx.theme().text_muted) + .child("New contact:"), + ) + .child( + v_flex() + .gap_1() + .child( + h_flex() + .gap_1() + .w_full() + .child(Input::new(&self.input).small().cleanable(true)) + .child( + Button::new("add") + .icon(IconName::Plus) + .tooltip("Add contact") + .ghost() + .size(rems(2.)) + .on_click(cx.listener(move |this, _, window, cx| { + this.add(window, cx); + })), + ), ) - }), - ) - .map(|this| { - if self.contacts.is_empty() { - this.child(self.render_empty(window, cx)) - } else { - this.child( - v_flex() - .gap_1() - .flex_1() - .w_full() - .children(self.render_list_items(cx)), - ) - } - }) - .child( - Button::new("submit") - .icon(IconName::CheckCircle) - .label("Update") - .primary() - .small() - .font_semibold() - .loading(self.updating) - .disabled(self.updating) - .on_click(cx.listener(move |this, _ev, window, cx| { - this.update(window, cx); - })), - ), - ) + .when_some(self.error.as_ref(), |this, error| { + this.child( + div() + .italic() + .text_xs() + .text_color(cx.theme().text_danger) + .child(error.clone()), + ) + }), + ) + .map(|this| { + if self.contacts.is_empty() { + this.child(self.render_empty(window, cx)) + } else { + this.child( + v_flex() + .gap_1() + .flex_1() + .w_full() + .children(self.render_list_items(cx)), + ) + } + }) + .child( + Button::new("submit") + .icon(IconName::CheckCircle) + .label("Update") + .primary() + .font_semibold() + .loading(self.updating) + .disabled(self.updating) + .on_click(cx.listener(move |this, _ev, window, cx| { + this.update(window, cx); + })), + ), + ) } } diff --git a/crates/workspace/src/panels/greeter.rs b/crates/workspace/src/panels/greeter.rs index 3be32d2..7e6dccd 100644 --- a/crates/workspace/src/panels/greeter.rs +++ b/crates/workspace/src/panels/greeter.rs @@ -9,8 +9,8 @@ use ui::button::{Button, ButtonVariants}; use ui::dock::{DockPlacement, Panel, PanelEvent}; use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; -use crate::Workspace; use crate::panels::profile; +use crate::{Command, Workspace}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| GreeterPanel::new(window, cx)) @@ -146,17 +146,20 @@ impl Render for GreeterPanel { .ghost() .small() .justify_start() - .on_click(cx.listener(move |this, _ev, window, cx| { + .on_click(cx.listener(move |this, _, window, cx| { this.add_profile_panel(window, cx) })), ) .child( - Button::new("invite") - .icon(Icon::new(IconName::Invite)) - .label("Invite friends") + Button::new("theme") + .icon(Icon::new(IconName::Moon)) + .label("Change theme") .ghost() .small() - .justify_start(), + .justify_start() + .on_click(cx.listener(move |_, _, _, cx| { + cx.dispatch_action(&Command::ToggleTheme); + })), ), ), ), diff --git a/crates/workspace/src/panels/messaging_relays.rs b/crates/workspace/src/panels/messaging_relays.rs index 91f5249..9b40de3 100644 --- a/crates/workspace/src/panels/messaging_relays.rs +++ b/crates/workspace/src/panels/messaging_relays.rs @@ -360,7 +360,6 @@ impl Render for MessagingRelayPanel { .icon(IconName::CheckCircle) .label("Update") .primary() - .small() .font_semibold() .loading(self.updating) .disabled(self.updating) diff --git a/crates/workspace/src/panels/mod.rs b/crates/workspace/src/panels/mod.rs index e223f2f..bb47e07 100644 --- a/crates/workspace/src/panels/mod.rs +++ b/crates/workspace/src/panels/mod.rs @@ -4,4 +4,3 @@ pub mod greeter; pub mod messaging_relays; pub mod profile; pub mod relay_list; -pub mod trash; diff --git a/crates/workspace/src/panels/profile.rs b/crates/workspace/src/panels/profile.rs index f8dfb52..a944c9c 100644 --- a/crates/workspace/src/panels/profile.rs +++ b/crates/workspace/src/panels/profile.rs @@ -419,7 +419,6 @@ impl Render for ProfilePanel { .icon(IconName::CheckCircle) .label("Update") .primary() - .small() .font_semibold() .loading(self.updating) .disabled(self.updating) diff --git a/crates/workspace/src/panels/relay_list.rs b/crates/workspace/src/panels/relay_list.rs index 27b7b7e..6b51cbf 100644 --- a/crates/workspace/src/panels/relay_list.rs +++ b/crates/workspace/src/panels/relay_list.rs @@ -429,7 +429,6 @@ impl Render for RelayListPanel { .icon(IconName::CheckCircle) .label("Update") .primary() - .small() .font_semibold() .loading(self.updating) .disabled(self.updating) diff --git a/crates/workspace/src/panels/trash.rs b/crates/workspace/src/panels/trash.rs deleted file mode 100644 index 7da5b10..0000000 --- a/crates/workspace/src/panels/trash.rs +++ /dev/null @@ -1,152 +0,0 @@ -use chat::ChatRegistry; -use gpui::{ - AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, - Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ParentElement, Render, - SharedString, Styled, Window, div, list, px, relative, -}; -use theme::ActiveTheme; -use ui::button::{Button, ButtonVariants}; -use ui::dock::{Panel, PanelEvent}; -use ui::scroll::Scrollbar; -use ui::{Icon, IconName, Sizable, h_flex, v_flex}; - -pub fn init(window: &mut Window, cx: &mut App) -> Entity { - cx.new(|cx| TrashPanel::new(window, cx)) -} - -pub struct TrashPanel { - name: SharedString, - focus_handle: FocusHandle, - - /// List state for messages - list_state: ListState, -} - -impl TrashPanel { - fn new(_window: &mut Window, cx: &mut App) -> Self { - let chat = ChatRegistry::global(cx); - let count = chat.read(cx).count_trash_messages(cx); - let list_state = ListState::new(count, ListAlignment::Bottom, px(1024.)); - - Self { - name: "Trash".into(), - focus_handle: cx.focus_handle(), - list_state, - } - } - - fn copy(&self, ix: usize, cx: &App) { - let chat = ChatRegistry::global(cx); - let trash_events = chat.read(cx).trash(); - - if let Some(message) = trash_events.read(cx).iter().nth(ix) { - let item = ClipboardItem::new_string(message.raw_event.to_string()); - cx.write_to_clipboard(item); - } - } - - fn render_list_item( - &mut self, - ix: usize, - _window: &mut Window, - cx: &mut Context, - ) -> AnyElement { - let chat = ChatRegistry::global(cx); - let trash_events = chat.read(cx).trash(); - - if let Some(message) = trash_events.read(cx).iter().nth(ix) { - v_flex() - .id(ix) - .p_2() - .w_full() - .child( - v_flex() - .p_2() - .w_full() - .gap_1() - .rounded(cx.theme().radius_lg) - .bg(cx.theme().surface_background) - .text_sm() - .child( - div() - .text_color(cx.theme().text_danger) - .child(message.reason.clone()), - ) - .child( - h_flex() - .h_10() - .w_full() - .px_2() - .justify_between() - .bg(cx.theme().elevated_surface_background) - .border_1() - .border_color(cx.theme().border) - .rounded(cx.theme().radius) - .child( - div() - .truncate() - .text_ellipsis() - .text_xs() - .line_height(relative(1.)) - .child(message.raw_event.clone()), - ) - .child( - Button::new(format!("copy-{ix}")) - .icon(IconName::Copy) - .ghost() - .small() - .on_click(cx.listener(move |this, _ev, _window, cx| { - this.copy(ix, cx); - })), - ), - ), - ) - .into_any_element() - } else { - div().id(ix).into_any_element() - } - } -} - -impl Panel for TrashPanel { - fn panel_id(&self) -> SharedString { - self.name.clone() - } - - fn title(&self, _cx: &App) -> AnyElement { - h_flex() - .gap_1() - .text_sm() - .child(Icon::new(IconName::Warning).small()) - .child("Errors") - .into_any_element() - } -} - -impl EventEmitter for TrashPanel {} - -impl Focusable for TrashPanel { - fn focus_handle(&self, _: &App) -> gpui::FocusHandle { - self.focus_handle.clone() - } -} - -impl Render for TrashPanel { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - v_flex().size_full().relative().child( - v_flex() - .flex_1() - .relative() - .child( - list( - self.list_state.clone(), - cx.processor(move |this, ix, window, cx| { - this.render_list_item(ix, window, cx) - }), - ) - .size_full(), - ) - .child(Scrollbar::vertical(&self.list_state)), - ) - } -} diff --git a/crates/workspace/src/sidebar/mod.rs b/crates/workspace/src/sidebar/mod.rs index df959d9..587c8f7 100644 --- a/crates/workspace/src/sidebar/mod.rs +++ b/crates/workspace/src/sidebar/mod.rs @@ -691,15 +691,16 @@ impl Render for Sidebar { this.child( div() .absolute() - .bottom_0() + .bottom_2() .left_0() .h_9() .w_full() - .p_4() + .px_4() .child( Button::new("create") .label(button_label) .primary() + .rounded() .shadow_md() .on_click(cx.listener(move |this, _ev, window, cx| { this.create_room(window, cx); diff --git a/web/src/lib.rs b/web/src/lib.rs index e9e4d72..d2a02a4 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -31,8 +31,10 @@ pub fn run() -> Result<(), JsValue> { #[cfg(target_family = "wasm")] gpui_platform::web_init(); + #[cfg(not(target_family = "wasm"))] let app = gpui_platform::application(); + #[cfg(target_family = "wasm")] let app = { let app = gpui_platform::single_threaded_web();