From 2d61148d93d41ec198198ddb557fd51d8d693abf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Nov 2025 11:02:11 +0000 Subject: [PATCH] Remove hardcoded default relays and add user relay prompt Instead of automatically connecting to hardcoded relays (wss://nos.lol, wss://relay.damus.io, wss://relay.nostr.band), the app now prompts users to enter a relay URL when no relays are configured. Changes: - Remove hardcoded relays from RelayConfig::default_relays() - Add RelayDialog UI component for relay input - Automatically show relay dialog on startup when no relays exist - Save user-entered relay to relay_config.json - Connect to relay pool after user adds it This gives users full control over which relays they connect to from the start. --- crates/notedeck_columns/src/app.rs | 46 +++++ crates/notedeck_columns/src/relay_config.rs | 15 +- crates/notedeck_columns/src/ui/mod.rs | 2 + .../notedeck_columns/src/ui/relay_dialog.rs | 159 ++++++++++++++++++ 4 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 crates/notedeck_columns/src/ui/relay_dialog.rs diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index 441339b..578dae2 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -51,10 +51,14 @@ pub struct Damus { /// Flag to defer account redirect until after first frame to avoid egui layer conflicts need_account_redirect: bool, + /// Flag to show relay dialog if no relays are configured + need_relay_prompt: bool, + pub decks_cache: DecksCache, pub channels_cache: crate::channels::ChannelsCache, pub relay_config: crate::relay_config::RelayConfig, pub channel_dialog: ui::ChannelDialog, + pub relay_dialog: ui::RelayDialog, pub thread_panel: ui::ThreadPanel, pub view_state: ViewState, pub drafts: Drafts, @@ -277,6 +281,13 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con } DamusState::Initialized => { + // Perform deferred relay prompt if needed + if damus.need_relay_prompt { + damus.need_relay_prompt = false; + info!("Opening relay dialog - no relays configured"); + damus.relay_dialog.open(); + } + // Perform deferred account redirect if needed if damus.need_account_redirect { damus.need_account_redirect = false; @@ -552,6 +563,31 @@ fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui:: } } + // Show relay dialog and handle relay addition + if let Some(dialog_action) = damus.relay_dialog.show(ui.ctx(), app_ctx.i18n) { + match dialog_action { + ui::RelayDialogAction::Add { url } => { + info!("User adding relay: {}", url); + // Add relay to config + damus.relay_config.add_relay(url.clone()); + + // Save relay config to disk + storage::save_relay_config(app_ctx.path, &damus.relay_config); + + // Add relay to pool + let wakeup = || {}; // Wakeup closure for relay events + if let Err(e) = app_ctx.pool.add_url(url.clone(), wakeup) { + error!("Failed to add relay {}: {}", url, e); + } else { + info!("Successfully connected to relay: {}", url); + } + } + ui::RelayDialogAction::Cancel => { + // Dialog was canceled, nothing to do + } + } + } + // Show thread panel if open if damus.thread_panel.is_open { let mut note_context = notedeck::NoteContext { @@ -797,6 +833,12 @@ impl Damus { crate::relay_config::RelayConfig::default() }; + // Check if we need to prompt user for a relay + let need_relay_prompt = relay_config.is_empty(); + if need_relay_prompt { + info!("No relays configured, will prompt user to add relay"); + } + // Apply relay config to pool - connect to configured relays for relay_url in relay_config.get_relays() { let wakeup = || {}; // Wakeup closure for relay events @@ -813,6 +855,7 @@ impl Damus { drafts: Drafts::default(), state: DamusState::Initializing, need_account_redirect: false, + need_relay_prompt, note_options, options, //frame_history: FrameHistory::default(), @@ -822,6 +865,7 @@ impl Damus { channels_cache, relay_config, channel_dialog: ui::ChannelDialog::default(), + relay_dialog: ui::RelayDialog::default(), thread_panel: ui::ThreadPanel::default(), unrecognized_args, jobs, @@ -871,6 +915,7 @@ impl Damus { drafts: Drafts::default(), state: DamusState::Initializing, need_account_redirect: false, + need_relay_prompt: relay_config.is_empty(), note_options: NoteOptions::default(), //frame_history: FrameHistory::default(), view_state: ViewState::default(), @@ -880,6 +925,7 @@ impl Damus { channels_cache, relay_config, channel_dialog: ui::ChannelDialog::default(), + relay_dialog: ui::RelayDialog::default(), thread_panel: ui::ThreadPanel::default(), unrecognized_args: BTreeSet::default(), jobs: JobsCache::default(), diff --git a/crates/notedeck_columns/src/relay_config.rs b/crates/notedeck_columns/src/relay_config.rs index 0de62f7..cf1bcb8 100644 --- a/crates/notedeck_columns/src/relay_config.rs +++ b/crates/notedeck_columns/src/relay_config.rs @@ -17,16 +17,10 @@ impl RelayConfig { } } - /// Create a default configuration with some popular relays + /// Create an empty default configuration + /// Users should be prompted to add relays if none exist pub fn default_relays() -> Self { - let mut relays = BTreeSet::new(); - - // Add some default public relays - relays.insert("wss://relay.damus.io".to_string()); - relays.insert("wss://relay.nostr.band".to_string()); - relays.insert("wss://nos.lol".to_string()); - - Self { relays } + Self::new() } pub fn add_relay(&mut self, url: String) -> bool { @@ -88,7 +82,6 @@ mod tests { #[test] fn test_default_relays() { let config = RelayConfig::default_relays(); - assert!(!config.is_empty()); - assert!(config.has_relay("wss://relay.damus.io")); + assert!(config.is_empty()); } } diff --git a/crates/notedeck_columns/src/ui/mod.rs b/crates/notedeck_columns/src/ui/mod.rs index 0ad158a..6b9fddb 100644 --- a/crates/notedeck_columns/src/ui/mod.rs +++ b/crates/notedeck_columns/src/ui/mod.rs @@ -15,6 +15,7 @@ pub mod post; pub mod preview; pub mod profile; pub mod relay; +pub mod relay_dialog; pub mod repost; pub mod search; pub mod settings; @@ -35,6 +36,7 @@ pub use note::{PostReplyView, PostView}; pub use preview::{Preview, PreviewApp, PreviewConfig}; pub use profile::ProfileView; pub use relay::RelayView; +pub use relay_dialog::{RelayDialog, RelayDialogAction}; pub use settings::SettingsView; pub use side_panel::{DesktopSidePanel, SidePanelAction}; pub use thread::ThreadView; diff --git a/crates/notedeck_columns/src/ui/relay_dialog.rs b/crates/notedeck_columns/src/ui/relay_dialog.rs new file mode 100644 index 0000000..dc8927f --- /dev/null +++ b/crates/notedeck_columns/src/ui/relay_dialog.rs @@ -0,0 +1,159 @@ +use egui::{RichText, TextEdit, Vec2}; + +use notedeck::{tr, Localization}; + +pub struct RelayDialog { + pub relay_url: String, + pub is_open: bool, + pub focus_requested: bool, +} + +pub enum RelayDialogAction { + Add { url: String }, + Cancel, +} + +impl RelayDialog { + pub fn new() -> Self { + Self { + relay_url: String::new(), + is_open: false, + focus_requested: false, + } + } + + pub fn open(&mut self) { + self.is_open = true; + self.relay_url.clear(); + self.focus_requested = false; + } + + pub fn close(&mut self) { + self.is_open = false; + } + + pub fn show( + &mut self, + ctx: &egui::Context, + i18n: &mut Localization, + ) -> Option { + if !self.is_open { + return None; + } + + let mut action: Option = None; + + let title = tr!(i18n, "Add Relay", "Dialog title for adding a relay"); + + egui::Window::new(title) + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) + .fixed_size(Vec2::new(450.0, 200.0)) + .show(ctx, |ui| { + ui.vertical(|ui| { + ui.add_space(16.0); + + // Relay URL input + ui.label( + RichText::new(tr!(i18n, "Relay URL", "Label for relay URL input")) + .size(14.0) + .strong(), + ); + ui.add_space(4.0); + ui.label( + RichText::new(tr!( + i18n, + "Enter a relay URL to connect to the nostr network", + "Help text for relay URL input" + )) + .size(12.0) + .color(ui.visuals().weak_text_color()), + ); + ui.add_space(8.0); + + let url_response = ui.add( + TextEdit::singleline(&mut self.relay_url) + .hint_text(tr!( + i18n, + "wss://relay.example.com", + "Placeholder for relay URL" + )) + .desired_width(f32::INFINITY), + ); + + // Auto-focus on URL field when first opened + if !self.focus_requested { + url_response.request_focus(); + self.focus_requested = true; + } + + // Handle Escape and Enter keys + let escape_pressed = ui.input(|i| i.key_pressed(egui::Key::Escape)); + let enter_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter)); + + if escape_pressed { + action = Some(RelayDialogAction::Cancel); + } + + if enter_pressed && !self.relay_url.trim().is_empty() { + action = Some(RelayDialogAction::Add { + url: self.relay_url.trim().to_string(), + }); + } + + ui.add_space(24.0); + + // Buttons + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + // Add button + let button_enabled = !self.relay_url.trim().is_empty(); + + let button = egui::Button::new( + RichText::new(tr!(i18n, "Add", "Button to add relay")) + .size(14.0), + ) + .min_size(Vec2::new(80.0, 32.0)); + + let button_response = ui.add_enabled(button_enabled, button); + + if button_response.clicked() { + action = Some(RelayDialogAction::Add { + url: self.relay_url.trim().to_string(), + }); + } + + ui.add_space(8.0); + + // Cancel button + let cancel_button = egui::Button::new( + RichText::new(tr!(i18n, "Cancel", "Button to cancel")) + .size(14.0) + .color(ui.visuals().weak_text_color()), + ) + .frame(false) + .min_size(Vec2::new(80.0, 32.0)); + + if ui.add(cancel_button).clicked() { + action = Some(RelayDialogAction::Cancel); + } + }); + }); + }); + }); + + // Close dialog if action was taken + if action.is_some() { + self.close(); + } + + action + } +} + +impl Default for RelayDialog { + fn default() -> Self { + Self::new() + } +}