diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/dialogs/compose.rs similarity index 100% rename from crates/coop/src/views/compose.rs rename to crates/coop/src/dialogs/compose.rs diff --git a/crates/coop/src/dialogs/mod.rs b/crates/coop/src/dialogs/mod.rs new file mode 100644 index 0000000..429c676 --- /dev/null +++ b/crates/coop/src/dialogs/mod.rs @@ -0,0 +1,2 @@ +pub mod compose; +pub mod screening; diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/dialogs/screening.rs similarity index 100% rename from crates/coop/src/views/screening.rs rename to crates/coop/src/dialogs/screening.rs diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index e946e6f..57a866b 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -12,10 +12,10 @@ use ui::Root; use crate::actions::Quit; mod actions; +mod dialogs; mod panels; mod sidebar; mod user; -mod views; mod workspace; fn main() { diff --git a/crates/coop/src/panels/greeter.rs b/crates/coop/src/panels/greeter.rs index ff8e889..f61077f 100644 --- a/crates/coop/src/panels/greeter.rs +++ b/crates/coop/src/panels/greeter.rs @@ -10,7 +10,7 @@ use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt}; -use crate::panels::{connect, import, profile, relay_list}; +use crate::panels::{connect, import, messaging_relays, profile, relay_list}; use crate::workspace::Workspace; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -166,7 +166,7 @@ impl Render for GreeterPanel { .small() .on_click(move |_ev, window, cx| { Workspace::add_panel( - import::init(window, cx), + messaging_relays::init(window, cx), DockPlacement::Center, window, cx, diff --git a/crates/coop/src/views/setup_relay.rs b/crates/coop/src/panels/messaging_relays.rs similarity index 51% rename from crates/coop/src/views/setup_relay.rs rename to crates/coop/src/panels/messaging_relays.rs index e237b80..d1a5f4a 100644 --- a/crates/coop/src/views/setup_relay.rs +++ b/crates/coop/src/panels/messaging_relays.rs @@ -2,11 +2,12 @@ use std::collections::HashSet; use std::time::Duration; use anyhow::{anyhow, Error}; +use dock::panel::{Panel, PanelEvent}; use gpui::prelude::FluentBuilder; use gpui::{ - div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement, - ParentElement, Render, SharedString, Styled, Subscription, Task, TextAlign, UniformList, - Window, + div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter, + FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + Styled, Subscription, Task, TextAlign, UniformList, Window, }; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; @@ -14,15 +15,21 @@ use state::NostrRegistry; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::input::{InputEvent, InputState, TextInput}; -use ui::{h_flex, v_flex, IconName, Sizable, WindowExtension}; +use ui::{divider, h_flex, v_flex, IconName, Sizable, StyledExt}; -pub fn init(window: &mut Window, cx: &mut App) -> Entity { - cx.new(|cx| SetupRelay::new(window, cx)) +pub fn init(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| MessagingRelayPanel::new(window, cx)) } #[derive(Debug)] -pub struct SetupRelay { +pub struct MessagingRelayPanel { + name: SharedString, + focus_handle: FocusHandle, + + /// Relay URL input input: Entity, + + /// Error message error: Option, // All relays @@ -35,13 +42,12 @@ pub struct SetupRelay { _tasks: SmallVec<[Task<()>; 1]>, } -impl SetupRelay { +impl MessagingRelayPanel { pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com")); let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com")); - let mut subscriptions = smallvec![]; let mut tasks = smallvec![]; @@ -64,18 +70,16 @@ impl SetupRelay { subscriptions.push( // Subscribe to user's input events - cx.subscribe_in( - &input, - window, - move |this: &mut Self, _, event, window, cx| { - if let InputEvent::PressEnter { .. } = event { - this.add(window, cx); - } - }, - ), + cx.subscribe_in(&input, window, move |this, _input, event, window, cx| { + if let InputEvent::PressEnter { .. } = event { + this.add(window, cx); + } + }), ); Self { + name: "Update Messaging Relays".into(), + focus_handle: cx.focus_handle(), input, relays: HashSet::new(), error: None, @@ -94,8 +98,7 @@ impl SetupRelay { .limit(1); if let Some(event) = client.database().query(filter).await?.first_owned() { - let urls = nip17::extract_owned_relay_list(event).collect(); - Ok(urls) + Ok(nip17::extract_owned_relay_list(event).collect()) } else { Err(anyhow!("Not found.")) } @@ -133,10 +136,9 @@ impl SetupRelay { self.error = Some(error.into()); cx.notify(); - // Clear the error message after a delay cx.spawn_in(window, async move |this, cx| { cx.background_executor().timer(Duration::from_secs(2)).await; - + // Clear the error message after a delay this.update(cx, |this, cx| { this.error = None; cx.notify(); @@ -148,11 +150,7 @@ impl SetupRelay { pub fn set_relays(&mut self, window: &mut Window, cx: &mut Context) { if self.relays.is_empty() { - self.set_error( - "You need to add at least 1 relay to receive messages from others.", - window, - cx, - ); + self.set_error("You need to add at least 1 relay", window, cx); return; }; @@ -160,7 +158,6 @@ impl SetupRelay { let client = nostr.read(cx).client(); let public_key = nostr.read(cx).identity().read(cx).public_key(); let write_relays = nostr.read(cx).write_relays(&public_key, cx); - let relays = self.relays.clone(); let task: Task> = cx.background_spawn(async move { @@ -192,10 +189,7 @@ impl SetupRelay { cx.spawn_in(window, async move |this, cx| { match task.await { Ok(_) => { - cx.update(|window, cx| { - window.close_modal(cx); - }) - .ok(); + // TODO } Err(e) => { this.update_in(cx, |this, window, cx| { @@ -219,107 +213,148 @@ impl SetupRelay { let mut items = Vec::new(); for ix in range { - if let Some(url) = relays.iter().nth(ix) { - items.push( - div() - .id(SharedString::from(url.to_string())) - .group("") - .w_full() - .h_9() - .py_0p5() - .child( - div() - .px_2() - .h_full() - .w_full() - .flex() - .items_center() - .justify_between() - .rounded(cx.theme().radius) - .bg(cx.theme().elevated_surface_background) - .text_xs() - .child(SharedString::from(url.to_string())) - .child( - Button::new("remove_{ix}") - .icon(IconName::Close) - .xsmall() - .ghost() - .invisible() - .group_hover("", |this| this.visible()) - .on_click({ - let url = url.to_owned(); - cx.listener(move |this, _ev, _window, cx| { - this.remove(&url, cx); - }) - }), - ), - ), - ) - } + let Some(url) = relays.iter().nth(ix) else { + continue; + }; + + items.push( + div() + .id(SharedString::from(url.to_string())) + .group("") + .w_full() + .h_9() + .py_0p5() + .child( + h_flex() + .px_2() + .flex() + .justify_between() + .rounded(cx.theme().radius) + .bg(cx.theme().elevated_surface_background) + .child( + div().text_sm().child(SharedString::from(url.to_string())), + ) + .child( + Button::new("remove_{ix}") + .icon(IconName::Close) + .xsmall() + .ghost() + .invisible() + .group_hover("", |this| this.visible()) + .on_click({ + let url = url.to_owned(); + cx.listener(move |this, _ev, _window, cx| { + this.remove(&url, cx); + }) + }), + ), + ), + ) } items }), ) - .w_full() - .min_h(px(200.)) + .h_full() } - fn render_empty(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + fn render_empty(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { h_flex() + .mt_2() .h_20() - .mb_2() .justify_center() + .border_2() + .border_dashed() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) .text_sm() .text_align(TextAlign::Center) .child(SharedString::from("Please add some relays.")) } } -impl Render for SetupRelay { +impl Panel for MessagingRelayPanel { + fn panel_id(&self) -> SharedString { + self.name.clone() + } + + fn title(&self, _cx: &App) -> AnyElement { + self.name.clone().into_any_element() + } +} + +impl EventEmitter for MessagingRelayPanel {} + +impl Focusable for MessagingRelayPanel { + fn focus_handle(&self, _: &App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for MessagingRelayPanel { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() - .gap_3() - .text_sm() + .size_full() + .items_center() + .justify_center() + .p_2() + .gap_10() .child( div() - .text_color(cx.theme().text_muted) - .child(SharedString::from("In order to receive messages from others, you need to set up at least one Messaging Relay.")), + .text_center() + .font_semibold() + .line_height(relative(1.25)) + .child(SharedString::from("Update Messaging Relays")), ) .child( v_flex() + .w_112() .gap_2() + .text_sm() .child( - h_flex() - .gap_1() - .w_full() - .child(TextInput::new(&self.input).small()) + v_flex() + .gap_1p5() .child( - Button::new("add") - .icon(IconName::Plus) - .label("Add") - .ghost() - .on_click(cx.listener(move |this, _, window, cx| { - this.add(window, cx); - })), - ), + h_flex() + .gap_1() + .w_full() + .child(TextInput::new(&self.input).small()) + .child( + Button::new("add") + .icon(IconName::Plus) + .label("Add") + .ghost() + .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().danger_foreground) + .child(error.clone()), + ) + }), ) - .when_some(self.error.as_ref(), |this, error| { - this.child( - div() - .italic() - .text_xs() - .text_color(cx.theme().danger_foreground) - .child(error.clone()), - ) - }), + .map(|this| { + if !self.relays.is_empty() { + this.child(self.render_list(window, cx)) + } else { + this.child(self.render_empty(window, cx)) + } + }) + .child(divider(cx)) + .child( + Button::new("submit") + .label("Update") + .primary() + .on_click(cx.listener(move |this, _ev, window, cx| { + this.set_relays(window, cx); + })), + ), ) - .map(|this| { - if !self.relays.is_empty() { - this.child(self.render_list(window, cx)) - } else { - this.child(self.render_empty(window, cx)) - } - }) } } diff --git a/crates/coop/src/panels/mod.rs b/crates/coop/src/panels/mod.rs index d88d325..7e6e20a 100644 --- a/crates/coop/src/panels/mod.rs +++ b/crates/coop/src/panels/mod.rs @@ -1,5 +1,6 @@ pub mod connect; pub mod greeter; pub mod import; +pub mod messaging_relays; pub mod profile; pub mod relay_list; diff --git a/crates/coop/src/panels/relay_list.rs b/crates/coop/src/panels/relay_list.rs index 111dca8..f7aef9c 100644 --- a/crates/coop/src/panels/relay_list.rs +++ b/crates/coop/src/panels/relay_list.rs @@ -100,7 +100,7 @@ impl RelayListPanel { let public_key = signer.get_public_key().await?; let filter = Filter::new() - .kind(Kind::InboxRelays) + .kind(Kind::RelayList) .author(public_key) .limit(1); diff --git a/crates/coop/src/sidebar/list_item.rs b/crates/coop/src/sidebar/list_item.rs index 5ad52fc..01d94eb 100644 --- a/crates/coop/src/sidebar/list_item.rs +++ b/crates/coop/src/sidebar/list_item.rs @@ -16,7 +16,7 @@ use ui::context_menu::ContextMenuExt; use ui::modal::ModalButtonProps; use ui::{h_flex, StyledExt, WindowExtension}; -use crate::views::screening; +use crate::dialogs::screening; #[derive(IntoElement)] pub struct RoomListItem { diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index 86b7081..2205137 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -25,7 +25,7 @@ use ui::input::{InputEvent, InputState, TextInput}; use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt, WindowExtension}; use crate::actions::{RelayStatus, Reload}; -use crate::views::compose::compose_button; +use crate::dialogs::compose::compose_button; mod list_item; diff --git a/crates/coop/src/views/mod.rs b/crates/coop/src/views/mod.rs deleted file mode 100644 index 0e68a13..0000000 --- a/crates/coop/src/views/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod compose; -pub mod preferences; -pub mod screening; -pub mod setup_relay; diff --git a/crates/coop/src/views/preferences.rs b/crates/coop/src/views/preferences.rs deleted file mode 100644 index 8c51dee..0000000 --- a/crates/coop/src/views/preferences.rs +++ /dev/null @@ -1,21 +0,0 @@ -use gpui::{div, App, AppContext, Context, Entity, IntoElement, Render, Window}; - -pub fn init(window: &mut Window, cx: &mut App) -> Entity { - cx.new(|cx| Preferences::new(window, cx)) -} - -pub struct Preferences { - // -} - -impl Preferences { - pub fn new(_window: &mut Window, _cx: &mut App) -> Self { - Self {} - } -} - -impl Render for Preferences { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div() - } -} diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index 7dda3fa..591fde5 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -13,17 +13,14 @@ use gpui::{ use nostr_connect::prelude::*; use person::PersonRegistry; use smallvec::{smallvec, SmallVec}; -use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry}; +use theme::{ActiveTheme, Theme, ThemeRegistry}; use ui::button::{Button, ButtonVariants}; use ui::modal::ModalButtonProps; use ui::{h_flex, v_flex, Root, Sizable, WindowExtension}; -use crate::actions::{ - reset, DarkMode, KeyringPopup, Logout, Settings, Themes, ViewProfile, ViewRelays, -}; +use crate::actions::{reset, KeyringPopup, Logout, Themes, ViewProfile}; use crate::panels::greeter; use crate::user::viewer; -use crate::views::{preferences, setup_relay}; use crate::{sidebar, user}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -153,17 +150,6 @@ impl Workspace { }); } - fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context) { - let view = preferences::init(window, cx); - - window.open_modal(cx, move |modal, _window, _cx| { - modal - .title(SharedString::from("Preferences")) - .width(px(520.)) - .child(view.clone()) - }); - } - fn on_profile(&mut self, _ev: &ViewProfile, window: &mut Window, cx: &mut Context) { let view = user::init(window, cx); let entity = view.downgrade(); @@ -211,38 +197,6 @@ impl Workspace { }); } - fn on_relays(&mut self, _ev: &ViewRelays, window: &mut Window, cx: &mut Context) { - let view = setup_relay::init(window, cx); - let entity = view.downgrade(); - - window.open_modal(cx, move |this, _window, _cx| { - let entity = entity.clone(); - - this.confirm() - .title(SharedString::from("Set Up Messaging Relays")) - .child(view.clone()) - .button_props(ModalButtonProps::default().ok_text("Update")) - .on_ok(move |_, window, cx| { - entity - .update(cx, |this, cx| { - this.set_relays(window, cx); - }) - .ok(); - - // false to keep the modal open - false - }) - }); - } - - fn on_dark_mode(&mut self, _ev: &DarkMode, window: &mut Window, cx: &mut Context) { - if cx.theme().mode.is_dark() { - Theme::change(ThemeMode::Light, Some(window), cx); - } else { - Theme::change(ThemeMode::Dark, Some(window), cx); - } - } - fn on_themes(&mut self, _ev: &Themes, window: &mut Window, cx: &mut Context) { window.open_modal(cx, move |this, _window, cx| { let registry = ThemeRegistry::global(cx); @@ -365,10 +319,7 @@ impl Render for Workspace { div() .id(SharedString::from("workspace")) - .on_action(cx.listener(Self::on_settings)) .on_action(cx.listener(Self::on_profile)) - .on_action(cx.listener(Self::on_relays)) - .on_action(cx.listener(Self::on_dark_mode)) .on_action(cx.listener(Self::on_themes)) .on_action(cx.listener(Self::on_sign_out)) .on_action(cx.listener(Self::on_open_pubkey)) diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 8311f1a..45fa413 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -488,6 +488,18 @@ impl NostrRegistry { match res { Ok(event) => { log::info!("Received relay list event: {event:?}"); + + // Construct a filter to continuously receive relay list events + let filter = Filter::new() + .kind(Kind::RelayList) + .author(public_key) + .since(Timestamp::now()); + + // Subscribe to the relay list events + client + .subscribe_to(BOOTSTRAP_RELAYS, vec![filter], None) + .await?; + return Ok(RelayState::Set); } Err(e) => { @@ -578,13 +590,23 @@ impl NostrRegistry { // Stream events from the write relays let mut stream = client - .stream_events_from(urls, vec![filter], Duration::from_secs(TIMEOUT)) + .stream_events_from(&urls, vec![filter], Duration::from_secs(TIMEOUT)) .await?; while let Some((_url, res)) = stream.next().await { match res { Ok(event) => { log::info!("Received messaging relays event: {event:?}"); + + // Construct a filter to continuously receive relay list events + let filter = Filter::new() + .kind(Kind::InboxRelays) + .author(public_key) + .since(Timestamp::now()); + + // Subscribe to the relay list events + client.subscribe_to(&urls, vec![filter], None).await?; + return Ok(RelayState::Set); } Err(e) => {