commit a956d1799eada18fa0d82a6c9f01af5eca66fa3d
parent cc1bb85b257dbf308780b2721ea2d27d23261ca5
Author: triesap <tyson@radroots.org>
Date: Sun, 7 Jun 2026 15:11:57 -0700
ui: refine settings relay row controls
Diffstat:
6 files changed, 156 insertions(+), 44 deletions(-)
diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs
@@ -48,8 +48,8 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"account-settings-blossom-profile-farm-media",
"account-settings-relay-localhost-8080",
"account-settings-relay-localhost-8081",
- "account-settings-remove-relay-localhost-8080",
- "account-settings-remove-relay-localhost-8081",
+ "account-settings-relay-menu-localhost-8080",
+ "account-settings-relay-menu-localhost-8081",
"account-settings-reset-blossom",
"account-settings-reset-relays",
"account-settings-save",
@@ -458,7 +458,12 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[
"AppTextKey::AccountSettingsNostrRelaysHelper",
"AppTextKey::AccountSettingsRelayAccessReadWrite",
"AppTextKey::AccountSettingsRelayAccessReadOnly",
+ "AppTextKey::AccountSettingsRelayMenuAbout",
+ "AppTextKey::AccountSettingsRelayMenuView",
"AppTextKey::AccountSettingsRemoveRelayAction",
+ "AppTextKey::AccountSettingsRelayMenuCheckConnection",
+ "AppTextKey::AccountSettingsRelayMenuCopy",
+ "AppTextKey::AccountSettingsRelayMenuCopyShortcut",
"AppTextKey::AccountSettingsAddRelayLabel",
"AppTextKey::AccountSettingsAddRelayPlaceholder",
"AppTextKey::AccountSettingsAddRelayAction",
diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs
@@ -30,26 +30,26 @@ use radroots_app_sync::{
SyncConflictKind, SyncConflictResolutionStatus, SyncConflictSeverity,
};
use radroots_app_ui::{
- APP_UI_THEME, AppCheckboxFieldSpec, AppFormFieldSpec, AppIconButtonSpec,
+ APP_UI_THEME, AppCheckboxFieldSpec, AppFormFieldSpec,
AppSegmentButtonIconSpec as IconSegmentButtonSpec, AppUnderlineTabSpec, LabelValueRow,
SettingsPreferencesGeneralRowState, app_button_account_selector_row as account_selector_row,
app_button_card, app_button_choice as choice_button,
- app_button_compact as action_button_compact, app_button_icon as action_icon_button,
+ app_button_compact as action_button_compact, app_button_ellipsis_menu as action_ellipsis_menu,
app_button_list_row as list_row_button, app_button_primary as action_button_primary,
app_button_primary_disabled as action_button_primary_disabled,
app_button_primary_full_width as action_button_primary_full_width,
app_button_secondary as action_button, app_button_secondary_disabled as action_button_disabled,
app_button_secondary_full_width as action_button_full_width,
app_button_square_dropdown_secondary as action_dropdown_button, app_button_text as text_button,
- app_checkbox_field, app_cluster, app_detail_row, app_divider as section_divider,
- app_focused_detail_view, app_focused_task_view, app_form_field, app_form_input_text,
- app_form_section, app_heading_section, app_heading_view, app_input_text as app_text_input,
- app_scroll_panel, app_segment_button_icon as icon_segment_button, app_shared_label_text,
- app_shared_text, app_split_shell, app_stack_h, app_stack_v,
- app_status_indicator as status_indicator, app_surface_card,
- app_surface_card_section as home_card, app_surface_panel, app_surface_sidebar,
- app_surface_window as app_window_shell, app_text_badge as settings_badge_text,
- app_text_body_subtle as home_body_text, app_text_label,
+ app_checkbox_button as action_checkbox_button, app_checkbox_field, app_cluster, app_detail_row,
+ app_divider as section_divider, app_focused_detail_view, app_focused_task_view, app_form_field,
+ app_form_input_text, app_form_section, app_heading_section, app_heading_view,
+ app_input_text as app_text_input, app_scroll_panel,
+ app_segment_button_icon as icon_segment_button, app_shared_label_text, app_shared_text,
+ app_split_shell, app_stack_h, app_stack_v, app_status_indicator as status_indicator,
+ app_surface_card, app_surface_card_section as home_card, app_surface_panel,
+ app_surface_sidebar, app_surface_window as app_window_shell,
+ app_text_badge as settings_badge_text, app_text_body_subtle as home_body_text, app_text_label,
app_text_label as home_farm_setup_field_label, app_text_value, app_underline_tabs,
label_value_list, runtime_metadata_rows, settings_preferences_general_rows, utility_title_row,
};
@@ -9929,7 +9929,7 @@ fn account_settings_relay_list(cx: &mut Context<HomeView>) -> impl IntoElement {
.overflow_hidden()
.child(account_settings_relay_row(
"account-settings-relay-localhost-8080",
- "account-settings-remove-relay-localhost-8080",
+ "account-settings-relay-menu-localhost-8080",
ACCOUNT_SETTINGS_RELAY_LOCALHOST_8080,
AppTextKey::AccountSettingsRelayAccessReadWrite,
true,
@@ -9937,7 +9937,7 @@ fn account_settings_relay_list(cx: &mut Context<HomeView>) -> impl IntoElement {
))
.child(account_settings_relay_row(
"account-settings-relay-localhost-8081",
- "account-settings-remove-relay-localhost-8081",
+ "account-settings-relay-menu-localhost-8081",
ACCOUNT_SETTINGS_RELAY_LOCALHOST_8081,
AppTextKey::AccountSettingsRelayAccessReadOnly,
true,
@@ -9947,7 +9947,7 @@ fn account_settings_relay_list(cx: &mut Context<HomeView>) -> impl IntoElement {
fn account_settings_relay_row(
checkbox_id: &'static str,
- remove_id: &'static str,
+ menu_id: &'static str,
relay_url: &'static str,
access_key: AppTextKey,
enabled: bool,
@@ -9961,12 +9961,23 @@ fn account_settings_relay_row(
.flex()
.items_center()
.gap(px(APP_UI_THEME.shells.home_stack_gap_px))
- .child(div().flex_1().min_w_0().child(app_checkbox_field(
- AppCheckboxFieldSpec::new(checkbox_id, relay_url, Option::<SharedString>::None),
- enabled,
- cx,
- |_, _, _| {},
- )))
+ .child(
+ div()
+ .flex_1()
+ .min_w_0()
+ .flex()
+ .items_center()
+ .gap(px(APP_UI_THEME.foundation.spacing.medium_px))
+ .child(account_settings_checkbox(checkbox_id, enabled, cx))
+ .child(
+ div()
+ .flex_1()
+ .min_w_0()
+ .text_size(px(APP_UI_THEME.foundation.typography.settings_row_text_px))
+ .text_color(rgb(APP_UI_THEME.foundation.text.primary))
+ .child(relay_url),
+ ),
+ )
.child(
div()
.w(px(104.0))
@@ -9974,15 +9985,68 @@ fn account_settings_relay_row(
.text_color(rgb(APP_UI_THEME.foundation.text.secondary))
.child(app_shared_text(access_key)),
)
- .child(action_icon_button(
- AppIconButtonSpec::new(
- remove_id,
- app_shared_text(AppTextKey::AccountSettingsRemoveRelayAction),
- IconName::Delete,
- ),
- |_, _, _| {},
- cx,
- ))
+ .child(account_settings_relay_menu_button(menu_id, cx))
+}
+
+fn account_settings_checkbox(
+ id: &'static str,
+ checked: bool,
+ cx: &mut Context<HomeView>,
+) -> impl IntoElement {
+ action_checkbox_button(id, checked, cx, |_, _, _| {})
+}
+
+fn account_settings_relay_menu_button(
+ id: &'static str,
+ cx: &mut Context<HomeView>,
+) -> impl IntoElement {
+ action_ellipsis_menu(
+ id,
+ |menu, _, _| {
+ menu.item(PopupMenuItem::new(app_text(
+ AppTextKey::AccountSettingsRelayMenuAbout,
+ )))
+ .item(PopupMenuItem::new(app_text(
+ AppTextKey::AccountSettingsRelayMenuView,
+ )))
+ .item(
+ PopupMenuItem::new(app_text(AppTextKey::AccountSettingsRemoveRelayAction))
+ .on_click(|_, _, _| {}),
+ )
+ .item(PopupMenuItem::new(app_text(
+ AppTextKey::AccountSettingsRelayMenuCheckConnection,
+ )))
+ .separator()
+ .item(account_settings_copy_menu_item())
+ },
+ cx,
+ )
+}
+
+fn account_settings_copy_menu_item() -> PopupMenuItem {
+ PopupMenuItem::element(|_, _| {
+ div()
+ .w(px(180.0))
+ .flex()
+ .items_center()
+ .justify_between()
+ .gap(px(APP_UI_THEME.foundation.spacing.large_px))
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.foundation.typography.settings_row_text_px))
+ .text_color(rgb(APP_UI_THEME.foundation.text.primary))
+ .child(app_shared_text(AppTextKey::AccountSettingsRelayMenuCopy)),
+ )
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.foundation.typography.utility_title_text_px))
+ .text_color(rgb(APP_UI_THEME.foundation.text.secondary))
+ .child(app_shared_text(
+ AppTextKey::AccountSettingsRelayMenuCopyShortcut,
+ )),
+ )
+ })
+ .on_click(|_, _, _| {})
}
fn account_settings_add_relay_controls(
diff --git a/crates/i18n/src/keys.rs b/crates/i18n/src/keys.rs
@@ -113,7 +113,12 @@ define_app_text_keys! {
AccountSettingsNostrRelaysHelper => "account.settings.nostr_relays.helper",
AccountSettingsRelayAccessReadWrite => "account.settings.relay_access.read_write",
AccountSettingsRelayAccessReadOnly => "account.settings.relay_access.read_only",
+ AccountSettingsRelayMenuAbout => "account.settings.relay_menu.about",
+ AccountSettingsRelayMenuView => "account.settings.relay_menu.view",
AccountSettingsRemoveRelayAction => "account.settings.remove_relay.action",
+ AccountSettingsRelayMenuCheckConnection => "account.settings.relay_menu.check_connection",
+ AccountSettingsRelayMenuCopy => "account.settings.relay_menu.copy",
+ AccountSettingsRelayMenuCopyShortcut => "account.settings.relay_menu.copy_shortcut",
AccountSettingsAddRelayLabel => "account.settings.add_relay.label",
AccountSettingsAddRelayPlaceholder => "account.settings.add_relay.placeholder",
AccountSettingsAddRelayAction => "account.settings.add_relay.action",
diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs
@@ -7,16 +7,17 @@ mod theme;
pub use primitives::{
AppCheckboxFieldSpec, AppFormFieldSpec, AppIconButtonSpec, AppSegmentButtonIconSpec,
AppUnderlineTabSpec, LabelValueRow, app_button_account_selector_row, app_button_card,
- app_button_choice, app_button_compact, app_button_icon, app_button_list_row,
- app_button_primary, app_button_primary_disabled, app_button_primary_full_width,
- app_button_secondary, app_button_secondary_disabled, app_button_secondary_full_width,
- app_button_square_dropdown_secondary, app_button_text, app_checkbox_field, app_cluster,
- app_detail_row, app_divider, app_focused_detail_view, app_focused_task_view, app_form_field,
- app_form_input_text, app_form_section, app_heading_section, app_heading_view, app_input_text,
- app_scroll_panel, app_segment_button_icon, app_split_shell, app_stack_h, app_stack_v,
- app_status_indicator, app_surface_card, app_surface_card_section, app_surface_panel,
- app_surface_sidebar, app_surface_window, app_text_badge, app_text_body, app_text_body_subtle,
- app_text_label, app_text_value, app_underline_tabs, label_value_list, utility_title_row,
+ app_button_choice, app_button_compact, app_button_ellipsis_menu, app_button_icon,
+ app_button_list_row, app_button_primary, app_button_primary_disabled,
+ app_button_primary_full_width, app_button_secondary, app_button_secondary_disabled,
+ app_button_secondary_full_width, app_button_square_dropdown_secondary, app_button_text,
+ app_checkbox_button, app_checkbox_field, app_cluster, app_detail_row, app_divider,
+ app_focused_detail_view, app_focused_task_view, app_form_field, app_form_input_text,
+ app_form_section, app_heading_section, app_heading_view, app_input_text, app_scroll_panel,
+ app_segment_button_icon, app_split_shell, app_stack_h, app_stack_v, app_status_indicator,
+ app_surface_card, app_surface_card_section, app_surface_panel, app_surface_sidebar,
+ app_surface_window, app_text_badge, app_text_body, app_text_body_subtle, app_text_label,
+ app_text_value, app_underline_tabs, label_value_list, utility_title_row,
};
pub use text::{
SettingsPreferencesGeneralRowState, app_shared_label_text, app_shared_text,
diff --git a/crates/ui/src/primitives.rs b/crates/ui/src/primitives.rs
@@ -1,13 +1,13 @@
use gpui::{
- AnyElement, App, ClickEvent, Context, Div, ElementId, Entity, InteractiveElement, IntoElement,
- ParentElement, SharedString, StatefulInteractiveElement, Styled, Window, div,
+ AnyElement, App, ClickEvent, Context, Corner, Div, ElementId, Entity, InteractiveElement,
+ IntoElement, ParentElement, SharedString, StatefulInteractiveElement, Styled, Window, div,
prelude::FluentBuilder, px, relative, rgb, transparent_black,
};
use gpui_component::{
Icon, IconName, Sizable, Size,
button::{Button, ButtonCustomVariant, ButtonRounded, ButtonVariants, DropdownButton},
input::{Input, InputState},
- menu::PopupMenu,
+ menu::{DropdownMenu, PopupMenu},
tab::{Tab, TabBar},
};
use std::rc::Rc;
@@ -556,6 +556,15 @@ fn app_checkbox(
button.tab_stop(false)
}
+pub fn app_checkbox_button(
+ id: &'static str,
+ checked: bool,
+ cx: &App,
+ on_change: impl Fn(bool, &mut Window, &mut App) + 'static,
+) -> impl IntoElement {
+ app_checkbox(id, checked, cx, on_change)
+}
+
pub fn app_checkbox_field(
spec: AppCheckboxFieldSpec,
checked: bool,
@@ -861,6 +870,29 @@ pub fn app_button_square_dropdown_secondary(
)
}
+pub fn app_button_ellipsis_menu(
+ id: &'static str,
+ menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
+ _cx: &App,
+) -> impl IntoElement {
+ let sizing = APP_UI_THEME.components.app_button.sizing;
+
+ Button::new(id)
+ .ghost()
+ .rounded(ButtonRounded::Size(px(APP_UI_THEME
+ .foundation
+ .radii
+ .medium_px)))
+ .with_size(Size::Size(px(sizing.square_width_px)))
+ .tab_stop(false)
+ .child(
+ Icon::new(IconName::Ellipsis)
+ .with_size(Size::Size(px(sizing.icon_size_px)))
+ .text_color(rgb(APP_UI_THEME.foundation.text.secondary)),
+ )
+ .dropdown_menu_with_anchor(Corner::BottomRight, menu)
+}
+
fn app_button_label(
button: Button,
label: SharedString,
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -92,7 +92,12 @@
"account.settings.nostr_relays.helper": "Manage the relays used to publish and read events.",
"account.settings.relay_access.read_write": "Read & write",
"account.settings.relay_access.read_only": "Read only",
+ "account.settings.relay_menu.about": "About",
+ "account.settings.relay_menu.view": "View",
"account.settings.remove_relay.action": "Remove relay",
+ "account.settings.relay_menu.check_connection": "Check Connection",
+ "account.settings.relay_menu.copy": "Copy",
+ "account.settings.relay_menu.copy_shortcut": "Ctrl + C",
"account.settings.add_relay.label": "Add relay",
"account.settings.add_relay.placeholder": "wss://your-relay.example",
"account.settings.add_relay.action": "Add relay",