Redesign for the v1 stable release #3

Merged
reya merged 30 commits from v1-redesign into master 2026-02-04 01:43:24 +00:00
14 changed files with 176 additions and 190 deletions
Showing only changes of commit 5d4481fb5f - Show all commits

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;
mod actions;
mod dialogs;
mod panels;
mod sidebar;
mod user;
mod views;
mod workspace;
fn main() {

View File

@@ -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<GreeterPanel> {
@@ -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,

View File

@@ -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<SetupRelay> {
cx.new(|cx| SetupRelay::new(window, cx))
pub fn init(window: &mut Window, cx: &mut App) -> Entity<MessagingRelayPanel> {
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<InputState>,
/// Error message
error: Option<SharedString>,
// 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>) -> 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| {
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<Self>) {
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<Result<(), Error>> = 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,7 +213,10 @@ impl SetupRelay {
let mut items = Vec::new();
for ix in range {
if let Some(url) = relays.iter().nth(ix) {
let Some(url) = relays.iter().nth(ix) else {
continue;
};
items.push(
div()
.id(SharedString::from(url.to_string()))
@@ -228,17 +225,15 @@ impl SetupRelay {
.h_9()
.py_0p5()
.child(
div()
h_flex()
.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(
div().text_sm().child(SharedString::from(url.to_string())),
)
.child(
Button::new("remove_{ix}")
.icon(IconName::Close)
@@ -256,39 +251,69 @@ impl SetupRelay {
),
)
}
}
items
}),
)
.w_full()
.min_h(px(200.))
.h_full()
}
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()
.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<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 {
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(
v_flex()
.gap_1p5()
.child(
h_flex()
.gap_1()
@@ -321,5 +346,15 @@ impl Render for SetupRelay {
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 greeter;
pub mod import;
pub mod messaging_relays;
pub mod profile;
pub mod relay_list;

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;

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 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<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>) {
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<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>) {
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))

View File

@@ -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) => {