diff --git a/assets/icons/arrow-up-circle.svg b/assets/icons/arrow-up-circle.svg deleted file mode 100644 index a098b52..0000000 --- a/assets/icons/arrow-up-circle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/icons/edit-fill.svg b/assets/icons/edit-fill.svg deleted file mode 100644 index e263643..0000000 --- a/assets/icons/edit-fill.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/icons/forward.svg b/assets/icons/forward.svg deleted file mode 100644 index 98c932b..0000000 --- a/assets/icons/forward.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/icons/search-fill.svg b/assets/icons/search-fill.svg deleted file mode 100644 index 3e8bb4e..0000000 --- a/assets/icons/search-fill.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/icons/signal.svg b/assets/icons/signal.svg new file mode 100644 index 0000000..5c46c01 --- /dev/null +++ b/assets/icons/signal.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/users-three-fill.svg b/assets/icons/users-three-fill.svg deleted file mode 100644 index 89b75f3..0000000 --- a/assets/icons/users-three-fill.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/icons/warning.svg b/assets/icons/warning.svg new file mode 100644 index 0000000..8ac94eb --- /dev/null +++ b/assets/icons/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/coop/src/actions.rs b/crates/coop/src/actions.rs index 5a33cfb..327525a 100644 --- a/crates/coop/src/actions.rs +++ b/crates/coop/src/actions.rs @@ -3,6 +3,7 @@ use std::sync::Mutex; use gpui::{actions, App}; actions!(coop, [DarkMode, Settings, Logout, Quit]); +actions!(sidebar, [Reload, RelayStatus]); pub fn load_embedded_fonts(cx: &App) { let asset_source = cx.asset_source(); diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index 1038382..c1d4b26 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -1313,7 +1313,7 @@ impl ChatSpace { ) }) .when(!self.has_nip17_relays, |this| { - this.child(setup_nip17_relay(t!("relays.button_label"))) + this.child(setup_nip17_relay(t!("relays.button"))) }) .child( Button::new("user") diff --git a/crates/coop/src/views/preferences.rs b/crates/coop/src/views/preferences.rs index e398080..251fb83 100644 --- a/crates/coop/src/views/preferences.rs +++ b/crates/coop/src/views/preferences.rs @@ -86,7 +86,6 @@ impl Preferences { } fn open_relays(&self, window: &mut Window, cx: &mut Context) { - let title = SharedString::new(t!("relays.modal_title")); let view = setup_relay::init(Kind::InboxRelays, window, cx); let weak_view = view.downgrade(); @@ -94,7 +93,7 @@ impl Preferences { let weak_view = weak_view.clone(); this.confirm() - .title(title.clone()) + .title(shared_t!("relays.modal")) .child(view.clone()) .button_props(ModalButtonProps::default().ok_text(t!("common.update"))) .on_ok(move |_, window, cx| { diff --git a/crates/coop/src/views/setup_relay.rs b/crates/coop/src/views/setup_relay.rs index 22361a9..586e0dd 100644 --- a/crates/coop/src/views/setup_relay.rs +++ b/crates/coop/src/views/setup_relay.rs @@ -45,7 +45,7 @@ where modal .confirm() - .title(shared_t!("relays.modal_title")) + .title(shared_t!("relays.modal")) .child(view.clone()) .button_props(ModalButtonProps::default().ok_text(t!("common.update"))) .on_ok(move |_, window, cx| { @@ -299,7 +299,7 @@ impl SetupRelay { .justify_center() .text_sm() .text_align(TextAlign::Center) - .child(shared_t!("relays.add_some_relays")) + .child(shared_t!("relays.help_text")) } } @@ -339,7 +339,7 @@ impl Render for SetupRelay { .text_xs() .font_semibold() .text_color(cx.theme().text_muted) - .child(shared_t!("relays.recommended")), + .child(shared_t!("common.recommended")), ) .child(h_flex().gap_1().children({ NIP17_RELAYS.iter().map(|&relay| { diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index 67bee8b..d170e10 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -6,15 +6,15 @@ use anyhow::{anyhow, Error}; use common::debounced_delay::DebouncedDelay; use common::display::{ReadableTimestamp, TextUtils}; use global::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS}; -use global::{nostr_client, UnwrappingStatus}; +use global::{css, nostr_client, UnwrappingStatus}; use gpui::prelude::FluentBuilder; use gpui::{ div, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, - Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString, Styled, - Subscription, Task, Window, + Focusable, InteractiveElement, IntoElement, ParentElement, Render, RetainAllImageCache, + SharedString, Styled, Subscription, Task, Window, }; use gpui_tokio::Tokio; -use i18n::t; +use i18n::{shared_t, t}; use itertools::Itertools; use list_item::RoomListItem; use nostr_sdk::prelude::*; @@ -26,8 +26,10 @@ use theme::ActiveTheme; use ui::button::{Button, ButtonRounded, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::input::{InputEvent, InputState, TextInput}; -use ui::popup_menu::PopupMenu; -use ui::{v_flex, ContextModal, IconName, Selectable, Sizable, StyledExt}; +use ui::popup_menu::{PopupMenu, PopupMenuExt}; +use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Selectable, Sizable, StyledExt}; + +use crate::actions::{RelayStatus, Reload}; mod list_item; @@ -69,17 +71,16 @@ impl Sidebar { let global_result = cx.new(|_| None); let cancel_handle = cx.new(|_| None); - let find_input = cx.new(|cx| { - InputState::new(window, cx).placeholder(t!("sidebar.find_or_start_conversation")) - }); + let find_input = + cx.new(|cx| InputState::new(window, cx).placeholder(t!("sidebar.search_label"))); - let chats = Registry::global(cx); + let registry = Registry::global(cx); let mut subscriptions = smallvec![]; subscriptions.push(cx.subscribe_in( - &chats, + ®istry, window, - move |this, _chats, event, _window, cx| { + move |this, _, event, _window, cx| { if let RegistryEvent::NewRequest(kind) = event { this.indicator.update(cx, |this, cx| { *this = Some(kind.to_owned()); @@ -519,6 +520,88 @@ impl Sidebar { }); } + fn on_reload(&mut self, _ev: &Reload, window: &mut Window, cx: &mut Context) { + Registry::global(cx).update(cx, |this, cx| { + this.load_rooms(window, cx); + }); + window.push_notification(t!("common.refreshed"), cx); + } + + fn on_manage(&mut self, _ev: &RelayStatus, window: &mut Window, cx: &mut Context) { + let task: Task, Error>> = cx.background_spawn(async move { + let client = nostr_client(); + let css = css(); + let subscription = client.subscription(&css.gift_wrap_sub_id).await; + let mut relays: Vec = vec![]; + + for (url, _filter) in subscription.into_iter() { + relays.push(client.pool().relay(url).await?); + } + + Ok(relays) + }); + + cx.spawn_in(window, async move |this, cx| { + if let Ok(relays) = task.await { + this.update_in(cx, |this, window, cx| { + this.manage_relays(relays, window, cx); + }) + .ok(); + } + }) + .detach(); + } + + fn manage_relays(&mut self, relays: Vec, window: &mut Window, cx: &mut Context) { + window.open_modal(cx, move |this, _window, cx| { + this.show_close(true) + .overlay_closable(true) + .keyboard(true) + .title(shared_t!("manage_relays.modal")) + .child(v_flex().pb_4().gap_2().children({ + let mut items = Vec::with_capacity(relays.len()); + + for relay in relays.clone().into_iter() { + let url = relay.url().to_string(); + let time = relay.stats().connected_at().to_ago(); + let connected = relay.is_connected(); + + items.push( + h_flex() + .h_8() + .px_2() + .justify_between() + .text_xs() + .bg(cx.theme().elevated_surface_background) + .rounded(cx.theme().radius) + .child( + h_flex() + .gap_1() + .font_semibold() + .child( + Icon::new(IconName::Signal) + .small() + .text_color(cx.theme().danger_active) + .when(connected, |this| { + this.text_color(gpui::green().alpha(0.75)) + }), + ) + .child(url), + ) + .child( + div() + .text_right() + .text_color(cx.theme().text_muted) + .child(shared_t!("manage_relays.time", t = time)), + ), + ); + } + + items + })) + }); + } + fn list_items( &self, rooms: &[Entity], @@ -610,6 +693,8 @@ impl Render for Sidebar { } v_flex() + .on_action(cx.listener(Self::on_reload)) + .on_action(cx.listener(Self::on_manage)) .image_cache(self.image_cache.clone()) .size_full() .relative() @@ -632,7 +717,7 @@ impl Render for Sidebar { .suffix( Button::new("find") .icon(IconName::Search) - .tooltip(t!("sidebar.press_enter_to_search")) + .tooltip(t!("sidebar.search_tooltip")) .transparent() .small(), ), @@ -693,6 +778,31 @@ impl Render for Sidebar { .on_click(cx.listener(|this, _, _, cx| { this.set_filter(RoomKind::default(), cx); })), + ) + .child( + h_flex() + .flex_1() + .w_full() + .justify_end() + .items_center() + .text_xs() + .child( + Button::new("option") + .icon(IconName::Ellipsis) + .xsmall() + .ghost() + .rounded(ButtonRounded::Full) + .popup_menu(move |this, _window, _cx| { + this.menu( + t!("sidebar.reload_menu"), + Box::new(Reload), + ) + .menu( + t!("sidebar.status_menu"), + Box::new(RelayStatus), + ) + }), + ), ), ) .child( diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index 9c9288d..19d0a7a 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -14,8 +14,6 @@ pub enum IconName { ArrowLeft, ArrowRight, ArrowUp, - ArrowUpCircle, - Bell, CaretUp, CaretDown, CaretDownFill, @@ -28,7 +26,6 @@ pub enum IconName { CloseCircleFill, Copy, Edit, - EditFill, Ellipsis, Eye, EyeOff, @@ -52,9 +49,8 @@ pub enum IconName { Reply, Report, Refresh, - Forward, + Signal, Search, - SearchFill, Settings, SortAscending, SortDescending, @@ -62,8 +58,8 @@ pub enum IconName { ThumbsDown, ThumbsUp, Upload, - UsersThreeFill, OpenUrl, + Warning, WindowClose, WindowMaximize, WindowMinimize, @@ -78,8 +74,6 @@ impl IconName { Self::ArrowLeft => "icons/arrow-left.svg", Self::ArrowRight => "icons/arrow-right.svg", Self::ArrowUp => "icons/arrow-up.svg", - Self::ArrowUpCircle => "icons/arrow-up-circle.svg", - Self::Bell => "icons/bell.svg", Self::CaretRight => "icons/caret-right.svg", Self::CaretUp => "icons/caret-up.svg", Self::CaretDown => "icons/caret-down.svg", @@ -92,7 +86,6 @@ impl IconName { Self::CloseCircleFill => "icons/close-circle-fill.svg", Self::Copy => "icons/copy.svg", Self::Edit => "icons/edit.svg", - Self::EditFill => "icons/edit-fill.svg", Self::Ellipsis => "icons/ellipsis.svg", Self::Eye => "icons/eye.svg", Self::EmojiFill => "icons/emoji-fill.svg", @@ -116,9 +109,8 @@ impl IconName { Self::Reply => "icons/reply.svg", Self::Report => "icons/report.svg", Self::Refresh => "icons/refresh.svg", - Self::Forward => "icons/forward.svg", + Self::Signal => "icons/signal.svg", Self::Search => "icons/search.svg", - Self::SearchFill => "icons/search-fill.svg", Self::Settings => "icons/settings.svg", Self::SortAscending => "icons/sort-ascending.svg", Self::SortDescending => "icons/sort-descending.svg", @@ -126,8 +118,8 @@ impl IconName { Self::ThumbsDown => "icons/thumbs-down.svg", Self::ThumbsUp => "icons/thumbs-up.svg", Self::Upload => "icons/upload.svg", - Self::UsersThreeFill => "icons/users-three-fill.svg", Self::OpenUrl => "icons/open-url.svg", + Self::Warning => "icons/warning.svg", Self::WindowClose => "icons/window-close.svg", Self::WindowMaximize => "icons/window-maximize.svg", Self::WindowMinimize => "icons/window-minimize.svg", diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 701650a..bdb74f5 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -30,14 +30,10 @@ pub enum NotificationType { impl NotificationType { fn icon(&self, cx: &App) -> Icon { match self { - Self::Info => Icon::new(IconName::Info).text_color(cx.theme().element_active), - Self::Warning => Icon::new(IconName::Report).text_color(cx.theme().warning_foreground), - Self::Success => { - Icon::new(IconName::CheckCircle).text_color(cx.theme().element_foreground) - } - Self::Error => { - Icon::new(IconName::CloseCircle).text_color(cx.theme().danger_foreground) - } + Self::Info => Icon::new(IconName::Info).text_color(cx.theme().element_foreground), + Self::Success => Icon::new(IconName::Info).text_color(cx.theme().secondary_foreground), + Self::Warning => Icon::new(IconName::Warning).text_color(cx.theme().warning_foreground), + Self::Error => Icon::new(IconName::Warning).text_color(cx.theme().danger_foreground), } } } diff --git a/locales/app.yml b/locales/app.yml index b7a66f7..23b61a1 100644 --- a/locales/app.yml +++ b/locales/app.yml @@ -45,6 +45,10 @@ common: en: "Ignore" relay: en: "Relay" + relay_invalid: + en: "Relay URL is not valid." + recommended: + en: "Recommended:" auto_update: updating: @@ -167,20 +171,22 @@ login: en: "Logging in..." relays: - button_label: + button: en: "Configure the Messaging Relays to receive messages" - modal_title: + modal: en: "Set Up Messaging Relays" description: en: "In order to receive messages from others, you need to set up at least one Messaging Relay." - add_some_relays: + help_text: en: "Please add some relays." - invalid: - en: "Relay URL is not valid." empty: - en: "You need to add at least 1 relay to receive messages." - recommended: - en: "Recommended:" + en: "You need to add at least 1 relay to receive messages from others." + +manage_relays: + modal: + en: "Messaging Relay Status" + time: + en: "Last activity: %{t}" subject: title: @@ -353,9 +359,13 @@ chat: en: "%{u} has not set up Messaging Relays, so they won't receive your message." sidebar: - find_or_start_conversation: + reload_menu: + en: "Reload" + status_menu: + en: "Relay Status" + search_label: en: "Find or start a conversation" - press_enter_to_search: + search_tooltip: en: "Press Enter to search" empty: en: "There are no users matching query %{query}"