.
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m48s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m37s

This commit is contained in:
2026-01-28 14:11:13 +07:00
parent 8e62f30c05
commit 5d4481fb5f
14 changed files with 176 additions and 190 deletions

View File

@@ -0,0 +1,2 @@
pub mod compose;
pub mod screening;

View File

@@ -12,10 +12,10 @@ use ui::Root;
use crate::actions::Quit; use crate::actions::Quit;
mod actions; mod actions;
mod dialogs;
mod panels; mod panels;
mod sidebar; mod sidebar;
mod user; mod user;
mod views;
mod workspace; mod workspace;
fn main() { fn main() {

View File

@@ -10,7 +10,7 @@ use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt}; 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; use crate::workspace::Workspace;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> { pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> {
@@ -166,7 +166,7 @@ impl Render for GreeterPanel {
.small() .small()
.on_click(move |_ev, window, cx| { .on_click(move |_ev, window, cx| {
Workspace::add_panel( Workspace::add_panel(
import::init(window, cx), messaging_relays::init(window, cx),
DockPlacement::Center, DockPlacement::Center,
window, window,
cx, cx,

View File

@@ -2,11 +2,12 @@ use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement, div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter,
ParentElement, Render, SharedString, Styled, Subscription, Task, TextAlign, UniformList, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
Window, Styled, Subscription, Task, TextAlign, UniformList, Window,
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@@ -14,15 +15,21 @@ use state::NostrRegistry;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput}; 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<SetupRelay> { pub fn init(window: &mut Window, cx: &mut App) -> Entity<MessagingRelayPanel> {
cx.new(|cx| SetupRelay::new(window, cx)) cx.new(|cx| MessagingRelayPanel::new(window, cx))
} }
#[derive(Debug)] #[derive(Debug)]
pub struct SetupRelay { pub struct MessagingRelayPanel {
name: SharedString,
focus_handle: FocusHandle,
/// Relay URL input
input: Entity<InputState>, input: Entity<InputState>,
/// Error message
error: Option<SharedString>, error: Option<SharedString>,
// All relays // All relays
@@ -35,13 +42,12 @@ pub struct SetupRelay {
_tasks: SmallVec<[Task<()>; 1]>, _tasks: SmallVec<[Task<()>; 1]>,
} }
impl SetupRelay { impl MessagingRelayPanel {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com"));
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); 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 subscriptions = smallvec![];
let mut tasks = smallvec![]; let mut tasks = smallvec![];
@@ -64,18 +70,16 @@ impl SetupRelay {
subscriptions.push( subscriptions.push(
// Subscribe to user's input events // Subscribe to user's input events
cx.subscribe_in( cx.subscribe_in(&input, window, move |this, _input, event, window, cx| {
&input,
window,
move |this: &mut Self, _, event, window, cx| {
if let InputEvent::PressEnter { .. } = event { if let InputEvent::PressEnter { .. } = event {
this.add(window, cx); this.add(window, cx);
} }
}, }),
),
); );
Self { Self {
name: "Update Messaging Relays".into(),
focus_handle: cx.focus_handle(),
input, input,
relays: HashSet::new(), relays: HashSet::new(),
error: None, error: None,
@@ -94,8 +98,7 @@ impl SetupRelay {
.limit(1); .limit(1);
if let Some(event) = client.database().query(filter).await?.first_owned() { if let Some(event) = client.database().query(filter).await?.first_owned() {
let urls = nip17::extract_owned_relay_list(event).collect(); Ok(nip17::extract_owned_relay_list(event).collect())
Ok(urls)
} else { } else {
Err(anyhow!("Not found.")) Err(anyhow!("Not found."))
} }
@@ -133,10 +136,9 @@ impl SetupRelay {
self.error = Some(error.into()); self.error = Some(error.into());
cx.notify(); cx.notify();
// Clear the error message after a delay
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(2)).await; cx.background_executor().timer(Duration::from_secs(2)).await;
// Clear the error message after a delay
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.error = None; this.error = None;
cx.notify(); cx.notify();
@@ -148,11 +150,7 @@ impl SetupRelay {
pub fn set_relays(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub fn set_relays(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.relays.is_empty() { if self.relays.is_empty() {
self.set_error( self.set_error("You need to add at least 1 relay", window, cx);
"You need to add at least 1 relay to receive messages from others.",
window,
cx,
);
return; return;
}; };
@@ -160,7 +158,6 @@ impl SetupRelay {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key(); let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx); let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let relays = self.relays.clone(); let relays = self.relays.clone();
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
@@ -192,10 +189,7 @@ impl SetupRelay {
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
match task.await { match task.await {
Ok(_) => { Ok(_) => {
cx.update(|window, cx| { // TODO
window.close_modal(cx);
})
.ok();
} }
Err(e) => { Err(e) => {
this.update_in(cx, |this, window, cx| { this.update_in(cx, |this, window, cx| {
@@ -219,7 +213,10 @@ impl SetupRelay {
let mut items = Vec::new(); let mut items = Vec::new();
for ix in range { for ix in range {
if let Some(url) = relays.iter().nth(ix) { let Some(url) = relays.iter().nth(ix) else {
continue;
};
items.push( items.push(
div() div()
.id(SharedString::from(url.to_string())) .id(SharedString::from(url.to_string()))
@@ -228,17 +225,15 @@ impl SetupRelay {
.h_9() .h_9()
.py_0p5() .py_0p5()
.child( .child(
div() h_flex()
.px_2() .px_2()
.h_full()
.w_full()
.flex() .flex()
.items_center()
.justify_between() .justify_between()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().elevated_surface_background)
.text_xs() .child(
.child(SharedString::from(url.to_string())) div().text_sm().child(SharedString::from(url.to_string())),
)
.child( .child(
Button::new("remove_{ix}") Button::new("remove_{ix}")
.icon(IconName::Close) .icon(IconName::Close)
@@ -256,39 +251,69 @@ impl SetupRelay {
), ),
) )
} }
}
items items
}), }),
) )
.w_full() .h_full()
.min_h(px(200.))
} }
fn render_empty(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { fn render_empty(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex() h_flex()
.mt_2()
.h_20() .h_20()
.mb_2()
.justify_center() .justify_center()
.border_2()
.border_dashed()
.border_color(cx.theme().border)
.rounded(cx.theme().radius_lg)
.text_sm() .text_sm()
.text_align(TextAlign::Center) .text_align(TextAlign::Center)
.child(SharedString::from("Please add some relays.")) .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<PanelEvent> 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<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.gap_3() .size_full()
.text_sm() .items_center()
.justify_center()
.p_2()
.gap_10()
.child( .child(
div() div()
.text_color(cx.theme().text_muted) .text_center()
.child(SharedString::from("In order to receive messages from others, you need to set up at least one Messaging Relay.")), .font_semibold()
.line_height(relative(1.25))
.child(SharedString::from("Update Messaging Relays")),
) )
.child( .child(
v_flex() v_flex()
.w_112()
.gap_2() .gap_2()
.text_sm()
.child(
v_flex()
.gap_1p5()
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
@@ -321,5 +346,15 @@ impl Render for SetupRelay {
this.child(self.render_empty(window, cx)) 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);
})),
),
)
} }
} }

View File

@@ -1,5 +1,6 @@
pub mod connect; pub mod connect;
pub mod greeter; pub mod greeter;
pub mod import; pub mod import;
pub mod messaging_relays;
pub mod profile; pub mod profile;
pub mod relay_list; pub mod relay_list;

View File

@@ -100,7 +100,7 @@ impl RelayListPanel {
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let filter = Filter::new() let filter = Filter::new()
.kind(Kind::InboxRelays) .kind(Kind::RelayList)
.author(public_key) .author(public_key)
.limit(1); .limit(1);

View File

@@ -16,7 +16,7 @@ use ui::context_menu::ContextMenuExt;
use ui::modal::ModalButtonProps; use ui::modal::ModalButtonProps;
use ui::{h_flex, StyledExt, WindowExtension}; use ui::{h_flex, StyledExt, WindowExtension};
use crate::views::screening; use crate::dialogs::screening;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct RoomListItem { pub struct RoomListItem {

View File

@@ -25,7 +25,7 @@ use ui::input::{InputEvent, InputState, TextInput};
use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt, WindowExtension}; use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt, WindowExtension};
use crate::actions::{RelayStatus, Reload}; use crate::actions::{RelayStatus, Reload};
use crate::views::compose::compose_button; use crate::dialogs::compose::compose_button;
mod list_item; mod list_item;

View File

@@ -1,4 +0,0 @@
pub mod compose;
pub mod preferences;
pub mod screening;
pub mod setup_relay;

View File

@@ -1,21 +0,0 @@
use gpui::{div, App, AppContext, Context, Entity, IntoElement, Render, Window};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
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<Self>) -> impl IntoElement {
div()
}
}

View File

@@ -13,17 +13,14 @@ use gpui::{
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use person::PersonRegistry; use person::PersonRegistry;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry}; use theme::{ActiveTheme, Theme, ThemeRegistry};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::modal::ModalButtonProps; use ui::modal::ModalButtonProps;
use ui::{h_flex, v_flex, Root, Sizable, WindowExtension}; use ui::{h_flex, v_flex, Root, Sizable, WindowExtension};
use crate::actions::{ use crate::actions::{reset, KeyringPopup, Logout, Themes, ViewProfile};
reset, DarkMode, KeyringPopup, Logout, Settings, Themes, ViewProfile, ViewRelays,
};
use crate::panels::greeter; use crate::panels::greeter;
use crate::user::viewer; use crate::user::viewer;
use crate::views::{preferences, setup_relay};
use crate::{sidebar, user}; use crate::{sidebar, user};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> { pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
@@ -153,17 +150,6 @@ impl Workspace {
}); });
} }
fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context<Self>) {
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<Self>) { fn on_profile(&mut self, _ev: &ViewProfile, window: &mut Window, cx: &mut Context<Self>) {
let view = user::init(window, cx); let view = user::init(window, cx);
let entity = view.downgrade(); let entity = view.downgrade();
@@ -211,38 +197,6 @@ impl Workspace {
}); });
} }
fn on_relays(&mut self, _ev: &ViewRelays, window: &mut Window, cx: &mut Context<Self>) {
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<Self>) {
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<Self>) { fn on_themes(&mut self, _ev: &Themes, window: &mut Window, cx: &mut Context<Self>) {
window.open_modal(cx, move |this, _window, cx| { window.open_modal(cx, move |this, _window, cx| {
let registry = ThemeRegistry::global(cx); let registry = ThemeRegistry::global(cx);
@@ -365,10 +319,7 @@ impl Render for Workspace {
div() div()
.id(SharedString::from("workspace")) .id(SharedString::from("workspace"))
.on_action(cx.listener(Self::on_settings))
.on_action(cx.listener(Self::on_profile)) .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_themes))
.on_action(cx.listener(Self::on_sign_out)) .on_action(cx.listener(Self::on_sign_out))
.on_action(cx.listener(Self::on_open_pubkey)) .on_action(cx.listener(Self::on_open_pubkey))

View File

@@ -488,6 +488,18 @@ impl NostrRegistry {
match res { match res {
Ok(event) => { Ok(event) => {
log::info!("Received relay list event: {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); return Ok(RelayState::Set);
} }
Err(e) => { Err(e) => {
@@ -578,13 +590,23 @@ impl NostrRegistry {
// Stream events from the write relays // Stream events from the write relays
let mut stream = client 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?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {
match res { match res {
Ok(event) => { Ok(event) => {
log::info!("Received messaging relays event: {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); return Ok(RelayState::Set);
} }
Err(e) => { Err(e) => {