feat: add relay group creation #62
@@ -2,19 +2,21 @@ use anyhow::Error;
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Anchor, App, AppContext, Context, Div, Entity, InteractiveElement as _, IntoElement,
|
Anchor, App, AppContext, Context, Div, Entity, InteractiveElement as _, IntoElement,
|
||||||
ParentElement, Render, SharedString, StatefulInteractiveElement as _, Styled, Subscription,
|
ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement as _,
|
||||||
Task, WeakEntity, Window, div, px,
|
Styled, Subscription, Task, WeakEntity, Window, div, px,
|
||||||
};
|
};
|
||||||
use nip29::{GroupCreateOptions, GroupKey, GroupsEvent, GroupsRegistry};
|
use nip29::{GroupCreateOptions, GroupKey, GroupsEvent, GroupsRegistry};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use state::NostrRegistry;
|
use settings::AppSettings;
|
||||||
|
use state::{NostrRegistry, upload};
|
||||||
use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
|
use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
|
||||||
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{DockArea, DockPlacement, PanelHandle};
|
use ui::dock::{DockArea, DockPlacement, PanelHandle};
|
||||||
use ui::input::{Input, InputEvent, InputState, Textarea, TextareaState};
|
use ui::input::{Input, InputEvent, InputState, Textarea, TextareaState};
|
||||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||||
use ui::switch::Switch;
|
use ui::switch::Switch;
|
||||||
use ui::{Disableable, StyledExt, WindowExtension, h_flex, v_flex};
|
use ui::{Disableable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
const SUGGESTED_RELAY: &str = "wss://relay.ditto.pub";
|
const SUGGESTED_RELAY: &str = "wss://relay.ditto.pub";
|
||||||
const PRIVATE_DESC: &str = "Only members can read group messages";
|
const PRIVATE_DESC: &str = "Only members can read group messages";
|
||||||
@@ -47,6 +49,10 @@ struct NewGroup {
|
|||||||
selected_relay: Option<RelayUrl>,
|
selected_relay: Option<RelayUrl>,
|
||||||
/// Whether the optional custom group id input is shown.
|
/// Whether the optional custom group id input is shown.
|
||||||
show_custom_id: bool,
|
show_custom_id: bool,
|
||||||
|
/// The uploaded group picture, ready to be published with the metadata.
|
||||||
|
picture: Option<SharedString>,
|
||||||
|
/// Whether a picture upload is in flight.
|
||||||
|
uploading: bool,
|
||||||
private: bool,
|
private: bool,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
restricted: bool,
|
restricted: bool,
|
||||||
@@ -126,6 +132,8 @@ impl NewGroup {
|
|||||||
relays,
|
relays,
|
||||||
selected_relay,
|
selected_relay,
|
||||||
show_custom_id: false,
|
show_custom_id: false,
|
||||||
|
picture: None,
|
||||||
|
uploading: false,
|
||||||
private: false,
|
private: false,
|
||||||
closed: false,
|
closed: false,
|
||||||
restricted: false,
|
restricted: false,
|
||||||
@@ -206,7 +214,7 @@ impl NewGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create(&mut self, cx: &mut Context<Self>) {
|
fn create(&mut self, cx: &mut Context<Self>) {
|
||||||
if self.creating {
|
if self.creating || self.uploading {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +252,7 @@ impl NewGroup {
|
|||||||
let options = GroupCreateOptions {
|
let options = GroupCreateOptions {
|
||||||
name,
|
name,
|
||||||
about: (!about.is_empty()).then_some(about),
|
about: (!about.is_empty()).then_some(about),
|
||||||
|
picture: self.picture.as_ref().map(|picture| picture.to_string()),
|
||||||
private: self.private,
|
private: self.private,
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
restricted: self.restricted,
|
restricted: self.restricted,
|
||||||
@@ -282,6 +291,50 @@ impl NewGroup {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_uploading(&mut self, uploading: bool, cx: &mut Context<Self>) {
|
||||||
|
self.uploading = uploading;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let server = AppSettings::get_file_server(cx);
|
||||||
|
|
||||||
|
let path = cx.prompt_for_paths(PathPromptOptions {
|
||||||
|
files: true,
|
||||||
|
directories: false,
|
||||||
|
multiple: false,
|
||||||
|
prompt: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
// Selecting no file means the prompt was cancelled
|
||||||
|
let Some(path) = path.await??.and_then(|mut paths| paths.pop()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_uploading(true, cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match upload(server, path, cx).await {
|
||||||
|
Ok(url) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.picture = Some(url.to_string().into());
|
||||||
|
this.set_uploading(false, cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_uploading(false, cx);
|
||||||
|
this.set_error(friendly_error(&error.to_string()), cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
fn set_error(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) {
|
fn set_error(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) {
|
||||||
self.error = Some(message.into());
|
self.error = Some(message.into());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -450,13 +503,24 @@ impl Render for NewGroup {
|
|||||||
.selected_relay
|
.selected_relay
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(relay_label)
|
.map(relay_label)
|
||||||
.unwrap_or_else(|| "Custom relay...".into());
|
.unwrap_or_else(|| "Custom relay…".into());
|
||||||
|
|
||||||
let web_url = self
|
let web_url = self
|
||||||
.effective_relay(cx)
|
.effective_relay(cx)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|relay| relay_web_url(&relay));
|
.map(|relay| relay_web_url(&relay));
|
||||||
|
|
||||||
|
// The avatar fallback is seeded with the id, falling back to the name.
|
||||||
|
let id = self.id_input.read(cx).value().trim().to_lowercase();
|
||||||
|
let name = self.name_input.read(cx).value().trim().to_owned();
|
||||||
|
let seed: String = if !id.is_empty() {
|
||||||
|
id
|
||||||
|
} else if !name.is_empty() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
"group".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
@@ -467,6 +531,27 @@ impl Render for NewGroup {
|
|||||||
"Give your new group a name and description. You can always change these later.",
|
"Give your new group a name and description. You can always change these later.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_3()
|
||||||
|
.child(Avatar::new(self.picture.clone()).seed(seed).large())
|
||||||
|
.child(
|
||||||
|
Button::new("upload-picture")
|
||||||
|
.label(if self.picture.is_some() {
|
||||||
|
"Change picture"
|
||||||
|
} else {
|
||||||
|
"Add a picture"
|
||||||
|
})
|
||||||
|
.xsmall()
|
||||||
|
.ghost()
|
||||||
|
.disabled(self.uploading)
|
||||||
|
.loading(self.uploading)
|
||||||
|
.on_click(cx.listener(|this, _event, window, cx| {
|
||||||
|
this.upload(window, cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(field("Group Name", Input::new(&self.name_input), cx))
|
.child(field("Group Name", Input::new(&self.name_input), cx))
|
||||||
.when(self.show_custom_id, |this| {
|
.when(self.show_custom_id, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
@@ -520,7 +605,7 @@ impl Render for NewGroup {
|
|||||||
.primary()
|
.primary()
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
.loading(self.creating)
|
.loading(self.creating)
|
||||||
.disabled(self.creating)
|
.disabled(self.creating || self.uploading)
|
||||||
.on_click(cx.listener(|this, _event, _window, cx| this.create(cx))),
|
.on_click(cx.listener(|this, _event, _window, cx| this.create(cx))),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user