@@ -4,20 +4,19 @@ use gpui::{
|
||||
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||
Subscription, Task, Window, div, px, relative,
|
||||
};
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, WindowExt, v_flex};
|
||||
use instant::Duration;
|
||||
use nostr_connect::prelude::*;
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, surface_radius};
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::input::{Input, InputEvent, InputState};
|
||||
use ui::{Disableable, WindowExtension, v_flex};
|
||||
|
||||
pub fn open(window: &mut Window, cx: &mut App) {
|
||||
let import = cx.new(|cx| ImportIdentity::new(window, cx));
|
||||
|
||||
window.open_dialog(cx, move |this, _window, _cx| {
|
||||
this.width(px(450.))
|
||||
.show_close(false)
|
||||
.close_button(false)
|
||||
.overlay_closable(false)
|
||||
.keyboard(false)
|
||||
.title("Continue with your Nostr Identity")
|
||||
@@ -31,7 +30,7 @@ pub fn open_unlock(window: &mut Window, cx: &mut App) {
|
||||
|
||||
window.open_dialog(cx, move |this, _window, _cx| {
|
||||
this.width(px(450.))
|
||||
.show_close(false)
|
||||
.close_button(false)
|
||||
.overlay_closable(false)
|
||||
.keyboard(false)
|
||||
.title("Unlock your identity")
|
||||
@@ -239,15 +238,15 @@ impl Render for ImportIdentity {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_4()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap(px(theme::SPACE))
|
||||
.gap_2()
|
||||
.when(!is_unlock, |this| {
|
||||
this.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Use secret key or bunker connection")
|
||||
.child(Input::new(&self.key_input)),
|
||||
)
|
||||
@@ -256,7 +255,7 @@ impl Render for ImportIdentity {
|
||||
this.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(if is_unlock {
|
||||
"Passphrase to unlock your identity:"
|
||||
} else {
|
||||
@@ -269,14 +268,14 @@ impl Render for ImportIdentity {
|
||||
this.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Passphrase to encrypt your key on this device:")
|
||||
.child(Input::new(&self.pass_input)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Repeat Passphrase:")
|
||||
.child(Input::new(&self.repass_input)),
|
||||
)
|
||||
@@ -285,9 +284,9 @@ impl Render for ImportIdentity {
|
||||
this.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded(px(surface_radius()))
|
||||
.bg(cx.theme().warning_muted)
|
||||
.text_style(TextStyle::Caption)
|
||||
.rounded(cx.theme().radius_lg)
|
||||
.bg(cx.theme().warning.opacity(0.15))
|
||||
.text_xs()
|
||||
.text_color(cx.theme().warning)
|
||||
.line_height(relative(1.2))
|
||||
.child(div().child(BUNKER_WARN)),
|
||||
@@ -297,9 +296,9 @@ impl Render for ImportIdentity {
|
||||
this.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded(px(surface_radius()))
|
||||
.bg(cx.theme().warning_muted)
|
||||
.text_style(TextStyle::Caption)
|
||||
.rounded(cx.theme().radius_lg)
|
||||
.bg(cx.theme().warning.opacity(0.15))
|
||||
.text_xs()
|
||||
.text_color(cx.theme().warning)
|
||||
.line_height(relative(1.2))
|
||||
.child(div().child(KEY_WARN)),
|
||||
@@ -330,7 +329,7 @@ impl Render for ImportIdentity {
|
||||
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
||||
this.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_center()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error.clone()),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::input::{Input, InputState};
|
||||
use gpui_kit::component::notification::Notification;
|
||||
use gpui_kit::component::{WindowExt, h_flex, v_flex};
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{App, AppContext, ClickEvent, ParentElement, Styled, Window, px};
|
||||
use nip29::{GroupReference, GroupsRegistry};
|
||||
use ui::button::{Button, ButtonVariants, IconPosition};
|
||||
use ui::input::{Input, InputState};
|
||||
use ui::notification::Notification;
|
||||
use ui::{IconName, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
|
||||
type SubmitFn = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool>;
|
||||
|
||||
@@ -57,10 +58,8 @@ pub fn open(window: &mut Window, cx: &mut App) {
|
||||
.child(
|
||||
Button::new("join")
|
||||
.icon(IconName::ArrowRight)
|
||||
.icon_position(IconPosition::End)
|
||||
.label("Join")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.on_click({
|
||||
let join = join.clone();
|
||||
move |event, window, cx| {
|
||||
|
||||
@@ -9,17 +9,19 @@ use gpui::{
|
||||
Render, SharedString, StatefulInteractiveElement as _, Styled, Subscription, Task, WeakEntity,
|
||||
Window, div, px, uniform_list,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::dock::{DockArea, DockPlacement, PanelHandle};
|
||||
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::Directory;
|
||||
use settings::AppSettings;
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants, IconPosition};
|
||||
use ui::dock::{DockArea, DockPlacement, PanelHandle};
|
||||
use ui::input::{Input, InputEvent, InputState};
|
||||
use ui::nav::Nav;
|
||||
use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
use ui::Avatar;
|
||||
|
||||
use crate::nav::Nav;
|
||||
|
||||
/// Maximum number of members a chat can have.
|
||||
const MAX_MEMBERS: usize = 3;
|
||||
@@ -301,7 +303,7 @@ impl NewChat {
|
||||
room
|
||||
});
|
||||
|
||||
ui::dock::add_panel_to(
|
||||
crate::dock::add_panel_to(
|
||||
&self.dock,
|
||||
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
|
||||
DockPlacement::Center,
|
||||
@@ -336,13 +338,12 @@ impl NewChat {
|
||||
|
||||
Nav::new(start + offset)
|
||||
.label(profile.name())
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_sm()
|
||||
.when(!AppSettings::get_hide_avatar(cx), |this| {
|
||||
this.prefix(
|
||||
Avatar::from_source(profile.avatar())
|
||||
Avatar::new(profile.avatar())
|
||||
.seed(profile.avatar_seed())
|
||||
.small()
|
||||
.flex_shrink_0(),
|
||||
.small(),
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
@@ -367,14 +368,14 @@ impl NewChat {
|
||||
.gap_1()
|
||||
.p_0p5()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().surface_raised)
|
||||
.text_style(TextStyle::Caption2)
|
||||
.hover(|this| this.bg(cx.theme().element_hover))
|
||||
.bg(cx.theme().secondary)
|
||||
.text_xs()
|
||||
.font_medium()
|
||||
.hover(|this| this.bg(cx.theme().primary))
|
||||
.child(
|
||||
Avatar::from_source(profile.avatar())
|
||||
Avatar::new(profile.avatar())
|
||||
.seed(profile.avatar_seed())
|
||||
.xsmall()
|
||||
.flex_shrink_0(),
|
||||
.xsmall(),
|
||||
)
|
||||
.child(div().max_w_32().truncate().child(profile.name()))
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
@@ -414,17 +415,12 @@ impl Render for NewChat {
|
||||
})
|
||||
.map(|this| {
|
||||
if let Some(error) = self.error.clone() {
|
||||
this.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error),
|
||||
)
|
||||
this.child(div().text_xs().text_color(cx.theme().danger).child(error))
|
||||
} else {
|
||||
this.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_faint)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(notice),
|
||||
)
|
||||
}
|
||||
@@ -452,10 +448,8 @@ impl Render for NewChat {
|
||||
.child(
|
||||
Button::new("start")
|
||||
.icon(IconName::ArrowRight)
|
||||
.icon_position(IconPosition::End)
|
||||
.label("Start")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
if this.start_chat(window, cx) {
|
||||
window.close_dialog(cx);
|
||||
|
||||
@@ -3,29 +3,30 @@ use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
App, AppContext, Context, Div, Entity, FocusHandle, InteractiveElement as _, IntoElement,
|
||||
ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement as _,
|
||||
Styled, Subscription, Task, WeakEntity, Window, div, px, rems,
|
||||
StyleRefinement, Styled, Subscription, Task, WeakEntity, Window, div, px, rems,
|
||||
};
|
||||
use gpui_base::{Popup, Select};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::dock::{DockArea, DockPlacement, PanelHandle};
|
||||
use gpui_kit::component::group_box::{GroupBox, GroupBoxVariants};
|
||||
use gpui_kit::component::input::{Input, InputEvent, InputState, Textarea, TextareaState};
|
||||
use gpui_kit::component::separator::Separator;
|
||||
use gpui_kit::component::switch::Switch;
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Icon, Sizable, WindowExt, h_flex, v_flex};
|
||||
use nip29::{GroupCreateOptions, GroupKey, GroupsEvent, GroupsRegistry};
|
||||
use nostr_sdk::prelude::*;
|
||||
use settings::AppSettings;
|
||||
use state::upload;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, control_radius};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock::{DockArea, DockPlacement, PanelHandle};
|
||||
use ui::input::{Input, InputEvent, InputState, Textarea, TextareaState};
|
||||
use ui::switch::Switch;
|
||||
use ui::{
|
||||
Disableable, Icon, IconName, Sizable, StyledExt, WindowExtension, divider, h_flex, v_flex,
|
||||
};
|
||||
use ui::{Avatar, settings_row};
|
||||
|
||||
const DEFAULT_RELAYS: [&str; 2] = ["wss://relay.ditto.net", "wss://chat.wisp.talk"];
|
||||
const PRIVATE_DESC: &str = "Only members can read group messages";
|
||||
const CLOSED_DESC: &str = "Join requests are ignored (invite-only)";
|
||||
const RESTRICTED_DESC: &str = "Only members can post messages";
|
||||
const HIDDEN_DESC: &str = "Hidden from non-members, not discoverable";
|
||||
const LIST_PRIVATE_DESC: &str = "Kept encrypted in your group list so others can't see you joined";
|
||||
const LIST_PRIVATE_DESC: &str = "Kept encrypted in your group list so others can't see";
|
||||
|
||||
pub fn open(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) {
|
||||
open_with(dock, None, window, cx);
|
||||
@@ -257,7 +258,7 @@ impl NewGroup {
|
||||
let registry = GroupsRegistry::global(cx);
|
||||
let group = registry.update(cx, |registry, cx| registry.open(key, cx));
|
||||
|
||||
ui::dock::add_panel_to(
|
||||
crate::dock::add_panel_to(
|
||||
&self.dock,
|
||||
PanelHandle::new(nip29_ui::init(group, window, cx)),
|
||||
DockPlacement::Center,
|
||||
@@ -347,8 +348,8 @@ impl NewGroup {
|
||||
if let Some(parent) = &self.parent {
|
||||
return v_flex().gap_1p5().child(field_label("Relay", cx)).child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(format!(
|
||||
"Created on {} (same relay as the parent group).",
|
||||
relay_label(parent.relay())
|
||||
@@ -365,17 +366,17 @@ impl NewGroup {
|
||||
.w_full()
|
||||
.h_8()
|
||||
.px_3()
|
||||
.rounded(px(button_radius()))
|
||||
.text_style(TextStyle::Callout)
|
||||
.rounded(cx.theme().radius)
|
||||
.text_xs()
|
||||
.cursor_default()
|
||||
.bg(if open {
|
||||
cx.theme().element_active
|
||||
cx.theme().list_active
|
||||
} else {
|
||||
cx.theme().surface_raised
|
||||
cx.theme().secondary
|
||||
})
|
||||
.when(!open, |this| {
|
||||
this.hover(|this| this.bg(cx.theme().element_hover))
|
||||
.active(|this| this.bg(cx.theme().element_active))
|
||||
this.hover(|this| this.bg(cx.theme().primary))
|
||||
.active(|this| this.bg(cx.theme().list_active))
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.relay_select_open = !open;
|
||||
@@ -385,7 +386,7 @@ impl NewGroup {
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.small()
|
||||
.text_color(cx.theme().text_muted),
|
||||
.text_color(cx.theme().muted_foreground),
|
||||
);
|
||||
|
||||
let select = Select::new("relay-select")
|
||||
@@ -412,36 +413,40 @@ impl NewGroup {
|
||||
.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
||||
this.set_relay_select_open(false, cx);
|
||||
}))
|
||||
.popover_style(cx)
|
||||
.bg(cx.theme().popover)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.shadow_md()
|
||||
.rounded(cx.theme().radius_lg)
|
||||
.p_1()
|
||||
.gap_y_1()
|
||||
.min_w(rems(8.))
|
||||
.text_color(cx.theme().text)
|
||||
.text_color(cx.theme().foreground)
|
||||
.children(self.relays.iter().enumerate().map(|(index, relay)| {
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("option-{relay}")))
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.rounded(px(button_radius()))
|
||||
.text_style(TextStyle::Callout)
|
||||
.rounded(cx.theme().radius)
|
||||
.text_xs()
|
||||
.cursor_default()
|
||||
.hover(|this| this.bg(cx.theme().element_hover))
|
||||
.hover(|this| this.bg(cx.theme().primary))
|
||||
.child(relay_label(relay))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.activate_relay_option(index, cx);
|
||||
}))
|
||||
}))
|
||||
.child(divider(cx))
|
||||
.child(Separator::horizontal())
|
||||
.child(
|
||||
h_flex()
|
||||
.id("relay-add")
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.rounded(px(button_radius()))
|
||||
.text_style(TextStyle::Callout)
|
||||
.rounded(cx.theme().radius)
|
||||
.text_xs()
|
||||
.cursor_default()
|
||||
.child("Custom relay")
|
||||
.hover(|this| this.bg(cx.theme().element_hover))
|
||||
.hover(|this| this.bg(cx.theme().primary))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.activate_relay_option(this.relays.len(), cx);
|
||||
})),
|
||||
@@ -457,65 +462,72 @@ impl NewGroup {
|
||||
}
|
||||
|
||||
fn render_access(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let private = Switch::new("private")
|
||||
.label("Private")
|
||||
.description(PRIVATE_DESC)
|
||||
.checked(self.private)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.private = *checked;
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
let closed = Switch::new("closed")
|
||||
.label("Closed")
|
||||
.description(CLOSED_DESC)
|
||||
.checked(self.closed)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.closed = *checked;
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
let restricted = Switch::new("restricted")
|
||||
.label("Restricted")
|
||||
.description(RESTRICTED_DESC)
|
||||
.checked(self.restricted)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.restricted = *checked;
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
let hidden = Switch::new("hidden")
|
||||
.label("Hidden")
|
||||
.description(HIDDEN_DESC)
|
||||
.checked(self.hidden)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.hidden = *checked;
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(field_label("Access settings", cx))
|
||||
.child(switch_row(private, cx))
|
||||
.child(switch_row(closed, cx))
|
||||
.child(switch_row(restricted, cx))
|
||||
.child(switch_row(hidden, cx))
|
||||
GroupBox::new()
|
||||
.fill()
|
||||
.title("Access settings")
|
||||
.title_style(group_box_title_style(cx))
|
||||
.child(settings_row(
|
||||
"Private",
|
||||
PRIVATE_DESC,
|
||||
Switch::new("private")
|
||||
.checked(self.private)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.private = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
cx,
|
||||
))
|
||||
.child(settings_row(
|
||||
"Closed",
|
||||
CLOSED_DESC,
|
||||
Switch::new("closed")
|
||||
.checked(self.closed)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.closed = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
cx,
|
||||
))
|
||||
.child(settings_row(
|
||||
"Restricted",
|
||||
RESTRICTED_DESC,
|
||||
Switch::new("restricted")
|
||||
.checked(self.restricted)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.restricted = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
cx,
|
||||
))
|
||||
.child(settings_row(
|
||||
"Hidden",
|
||||
HIDDEN_DESC,
|
||||
Switch::new("hidden")
|
||||
.checked(self.hidden)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.hidden = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
fn render_list_setting(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let list_private = Switch::new("list-private")
|
||||
.label("Private on my list")
|
||||
.description(LIST_PRIVATE_DESC)
|
||||
.checked(self.list_private)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.list_private = *checked;
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(field_label("List", cx))
|
||||
.child(switch_row(list_private, cx))
|
||||
GroupBox::new()
|
||||
.fill()
|
||||
.title("List")
|
||||
.title_style(group_box_title_style(cx))
|
||||
.child(settings_row(
|
||||
"Private on my list",
|
||||
LIST_PRIVATE_DESC,
|
||||
Switch::new("list-private")
|
||||
.checked(self.list_private)
|
||||
.on_click(cx.listener(|this, checked: &bool, _window, cx| {
|
||||
this.list_private = *checked;
|
||||
cx.notify();
|
||||
})),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
fn render_error(
|
||||
@@ -530,7 +542,7 @@ impl NewGroup {
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error.to_owned()),
|
||||
)
|
||||
@@ -541,7 +553,7 @@ impl NewGroup {
|
||||
.cursor_pointer()
|
||||
.flex_shrink_0()
|
||||
.underline()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().danger)
|
||||
.child("Open relay website")
|
||||
.on_click({
|
||||
@@ -560,14 +572,6 @@ impl NewGroup {
|
||||
|
||||
impl Render for NewGroup {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let name = self.name_input.read(cx).value().trim().to_owned();
|
||||
|
||||
let seed: String = if name.is_empty() {
|
||||
"group".to_owned()
|
||||
} else {
|
||||
name
|
||||
};
|
||||
|
||||
let selected = self
|
||||
.selected_relay
|
||||
.as_ref()
|
||||
@@ -593,7 +597,11 @@ impl Render for NewGroup {
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(Avatar::new(self.picture.clone()).seed(seed).large())
|
||||
.child(
|
||||
Avatar::new(self.picture.clone())
|
||||
.seed(self.name_input.read(cx).value().trim().to_owned())
|
||||
.large(),
|
||||
)
|
||||
.child(
|
||||
Button::new("upload-picture")
|
||||
.label(if self.picture.is_some() {
|
||||
@@ -610,9 +618,9 @@ impl Render for NewGroup {
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(field("Name", Input::new(&self.name_input), cx))
|
||||
.child(input_field("Name", Input::new(&self.name_input), cx))
|
||||
.child(self.render_relay(&selected, cx))
|
||||
.child(field("About", Textarea::new(&self.about_input), cx))
|
||||
.child(input_field("About", Textarea::new(&self.about_input), cx))
|
||||
.child(self.render_access(cx))
|
||||
.child(self.render_list_setting(cx))
|
||||
.overflow_y_scroll(),
|
||||
@@ -635,7 +643,6 @@ impl Render for NewGroup {
|
||||
"Create Group"
|
||||
})
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.loading(self.creating)
|
||||
.disabled(self.creating || self.uploading)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| this.create(cx))),
|
||||
@@ -644,25 +651,24 @@ impl Render for NewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
fn field(text: &'static str, input: impl IntoElement, cx: &App) -> Div {
|
||||
fn input_field(text: &'static str, input: impl IntoElement, cx: &App) -> Div {
|
||||
v_flex().gap_1p5().child(field_label(text, cx)).child(input)
|
||||
}
|
||||
|
||||
fn field_label(text: &'static str, cx: &App) -> Div {
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.font_semibold()
|
||||
.child(text)
|
||||
}
|
||||
|
||||
fn switch_row(switch: impl IntoElement, cx: &App) -> Div {
|
||||
div()
|
||||
.w_full()
|
||||
.p_2()
|
||||
.rounded(px(control_radius()))
|
||||
.bg(cx.theme().surface_raised)
|
||||
.child(switch)
|
||||
/// Match [`field_label`]'s design for GroupBox titles.
|
||||
fn group_box_title_style(cx: &App) -> StyleRefinement {
|
||||
StyleRefinement::default()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.font_semibold()
|
||||
}
|
||||
|
||||
fn relay_label(relay: &RelayUrl) -> SharedString {
|
||||
|
||||
@@ -5,20 +5,21 @@ use chat::{ChatRegistry, Room, RoomKind};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
App, AppContext, ClipboardItem, Context, Div, Entity, IntoElement, ParentElement,
|
||||
PathPromptOptions, Render, SharedString, Styled, Task, Window, div, px, retain_all,
|
||||
PathPromptOptions, Render, SharedString, Styled, Task, Window, div, retain_all,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::dock::DockPlacement;
|
||||
use gpui_kit::component::input::{Input, InputState, Textarea, TextareaState};
|
||||
use gpui_kit::component::notification::Notification;
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||
use settings::AppSettings;
|
||||
use state::{NostrRegistry, upload};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants, IconPosition};
|
||||
use ui::dock::DockPlacement;
|
||||
use ui::input::{Input, InputState, Textarea, TextareaState};
|
||||
use ui::notification::Notification;
|
||||
use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
use ui::Avatar;
|
||||
|
||||
use crate::Workspace;
|
||||
|
||||
@@ -372,17 +373,15 @@ impl Render for ProfileDialog {
|
||||
.w_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.bg(cx.theme().surface)
|
||||
.bg(cx.theme().sidebar)
|
||||
.child(Avatar::new(avatar).seed(profile.avatar_seed()).large())
|
||||
.when(self.editing, |this| {
|
||||
this.child(
|
||||
Button::new("upload")
|
||||
.icon(IconName::ArrowRight)
|
||||
.icon_position(IconPosition::End)
|
||||
.label("Add an avatar")
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.rounded()
|
||||
.disabled(self.uploading)
|
||||
.loading(self.uploading)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
@@ -398,15 +397,15 @@ impl Render for ProfileDialog {
|
||||
.w_full()
|
||||
.justify_center()
|
||||
.gap_3()
|
||||
.rounded(px(button_radius()))
|
||||
.bg(cx.theme().surface)
|
||||
.text_style(TextStyle::Callout)
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().sidebar)
|
||||
.text_xs()
|
||||
.child(shorten_pkey)
|
||||
.child(
|
||||
Button::new("copy")
|
||||
.icon({
|
||||
if self.copied {
|
||||
IconName::CheckCircle
|
||||
IconName::CircleCheck
|
||||
} else {
|
||||
IconName::Copy
|
||||
}
|
||||
@@ -485,11 +484,9 @@ impl Render for ProfileDialog {
|
||||
.when(self.is_current_user && !self.editing, |this| {
|
||||
this.child(
|
||||
Button::new("update")
|
||||
.icon(IconName::Edit)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon(IconName::SquarePen)
|
||||
.label("Update")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||
this.toggle_edit(window, cx);
|
||||
})),
|
||||
@@ -498,11 +495,9 @@ impl Render for ProfileDialog {
|
||||
.when(!self.is_current_user, |this| {
|
||||
this.child(
|
||||
Button::new("message")
|
||||
.icon(IconName::Message)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon(IconName::MessageSquare)
|
||||
.label("Message")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||
this.message(window, cx);
|
||||
})),
|
||||
@@ -520,11 +515,9 @@ impl Render for ProfileDialog {
|
||||
)
|
||||
.child(
|
||||
Button::new("save")
|
||||
.icon(IconName::CheckCircle)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon(IconName::CircleCheck)
|
||||
.label("Save")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.loading(self.updating)
|
||||
.disabled(self.updating)
|
||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||
@@ -538,8 +531,8 @@ impl Render for ProfileDialog {
|
||||
|
||||
fn label(label: &'static str, cx: &App) -> Div {
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.font_semibold()
|
||||
.child(label)
|
||||
}
|
||||
@@ -551,14 +544,12 @@ fn field(
|
||||
cx: &App,
|
||||
) -> Div {
|
||||
let (content, color) = match value {
|
||||
Some(value) => (value, cx.theme().text),
|
||||
None => (SharedString::from(placeholder), cx.theme().text_muted),
|
||||
Some(value) => (value, cx.theme().foreground),
|
||||
None => (SharedString::from(placeholder), cx.theme().muted_foreground),
|
||||
};
|
||||
|
||||
v_flex().gap_1p5().child(label(text, cx)).child(
|
||||
div()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_color(color)
|
||||
.child(content),
|
||||
)
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.child(label(text, cx))
|
||||
.child(div().text_xs().text_color(color).child(content))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
||||
use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem};
|
||||
use gpui_kit::component::scroll::ScrollableElement;
|
||||
use gpui_kit::component::tab::{Tab, TabBar};
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::{Error, anyhow};
|
||||
@@ -5,20 +13,12 @@ use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
App, AppContext, ClickEvent, Context, Div, Entity, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, Stateful, Styled, Subscription, Task, TextAlign,
|
||||
WeakEntity, Window, div, px, rems,
|
||||
WeakEntity, Window, div, rems,
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, panel_radius};
|
||||
use ui::button::{Button, ButtonVariants, IconPosition};
|
||||
use ui::input::{Input, InputEvent, InputState};
|
||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||
use ui::scroll::ScrollableElement;
|
||||
use ui::tab::Tab;
|
||||
use ui::tab::tab_bar::TabBar;
|
||||
use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
|
||||
const GOSSIP_MSG: &str = "Gossip Relays are the relays where you publish all your events and discover events from others.";
|
||||
const INBOX_MSG: &str = "Messaging Relays host your private messages. Others find these relays and send you messages there.";
|
||||
@@ -108,11 +108,11 @@ impl RelayManager {
|
||||
let manager = cx.entity().downgrade();
|
||||
|
||||
v_flex()
|
||||
.gap(px(theme::SPACE))
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(GOSSIP_MSG)),
|
||||
)
|
||||
.child(
|
||||
@@ -132,7 +132,7 @@ impl RelayManager {
|
||||
.tooltip("Relay metadata")
|
||||
.ghost()
|
||||
.h(rems(2.))
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.dropdown_menu(move |menu, _window, _cx| {
|
||||
menu.item(metadata_item(&manager, RelayMetadata::Read))
|
||||
.item(metadata_item(&manager, RelayMetadata::Write))
|
||||
@@ -153,7 +153,7 @@ impl RelayManager {
|
||||
this.child(
|
||||
div()
|
||||
.italic()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error.clone()),
|
||||
)
|
||||
@@ -197,11 +197,11 @@ impl RelayManager {
|
||||
|
||||
fn render_inbox(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap(px(theme::SPACE))
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(INBOX_MSG)),
|
||||
)
|
||||
.child(
|
||||
@@ -224,7 +224,7 @@ impl RelayManager {
|
||||
this.child(
|
||||
div()
|
||||
.italic()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error.clone()),
|
||||
)
|
||||
@@ -264,7 +264,7 @@ impl RelayManager {
|
||||
|
||||
fn render_tab_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
TabBar::new("relays-tabs")
|
||||
.segmented(true)
|
||||
.segmented()
|
||||
.selected_index(self.active_tab)
|
||||
.on_click(cx.listener(|this, ix: &usize, _window, cx| {
|
||||
this.active_tab = *ix;
|
||||
@@ -284,10 +284,8 @@ impl RelayManager {
|
||||
h_flex().gap_1().justify_end().child(
|
||||
Button::new("update")
|
||||
.icon(IconName::ArrowRight)
|
||||
.icon_position(IconPosition::End)
|
||||
.label("Update")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.loading(updating)
|
||||
.disabled(updating)
|
||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||
@@ -608,7 +606,7 @@ impl Render for RelayManager {
|
||||
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(self.render_tab_bar(cx))
|
||||
.child(content)
|
||||
.child(self.render_actions(cx))
|
||||
@@ -647,26 +645,27 @@ fn relay_row(
|
||||
.h_8()
|
||||
.px_2()
|
||||
.justify_between()
|
||||
.rounded(px(button_radius()))
|
||||
.bg(cx.theme().surface)
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().sidebar)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(label)
|
||||
.when_some(badge, |this, badge| {
|
||||
this.child(
|
||||
div()
|
||||
.p_0p5()
|
||||
.rounded_xs()
|
||||
.text_style(TextStyle::Caption2)
|
||||
.text_xs()
|
||||
.font_medium()
|
||||
.child(badge),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("remove")
|
||||
.icon(IconName::Close)
|
||||
.icon(IconName::X)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.invisible()
|
||||
@@ -682,8 +681,8 @@ fn empty_relays(cx: &App) -> impl IntoElement {
|
||||
.border_2()
|
||||
.border_dashed()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded(px(panel_radius()))
|
||||
.text_style(TextStyle::Callout)
|
||||
.rounded(cx.theme().radius_lg)
|
||||
.text_xs()
|
||||
.text_align(TextAlign::Center)
|
||||
.child(SharedString::from("Please add some relays."))
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ use gpui::{
|
||||
AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||
Subscription, Task, Window, div,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
||||
use gpui_kit::component::{ActiveTheme, WindowExt, h_flex, v_flex};
|
||||
use instant::Duration;
|
||||
use nip4e::DeviceRegistry;
|
||||
use nostr_sdk::prelude::*;
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _};
|
||||
use ui::button::{Button, ButtonVariants, IconPosition};
|
||||
use ui::input::{Input, InputEvent, InputState};
|
||||
use ui::{IconName, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RestoreEncryption {
|
||||
@@ -100,12 +100,12 @@ impl Render for RestoreEncryption {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_3()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Secret Key")
|
||||
.child(Input::new(&self.key_input)),
|
||||
)
|
||||
@@ -113,10 +113,8 @@ impl Render for RestoreEncryption {
|
||||
h_flex().gap_1().justify_end().child(
|
||||
Button::new("restore")
|
||||
.icon(IconName::ArrowRight)
|
||||
.icon_position(IconPosition::End)
|
||||
.label("Restore")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.restore(window, cx);
|
||||
})),
|
||||
@@ -125,7 +123,7 @@ impl Render for RestoreEncryption {
|
||||
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
||||
this.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_xs()
|
||||
.text_center()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(error.clone()),
|
||||
|
||||
@@ -7,16 +7,17 @@ use gpui::{
|
||||
App, AppContext, Context, Div, Entity, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
SharedString, Styled, Subscription, Task, Window, div, px, relative, uniform_list,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::spinner::Spinner;
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Icon, Sizable, WindowExt, h_flex, v_flex};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::{Disableable, Icon, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
use ui::Avatar;
|
||||
|
||||
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
|
||||
cx.new(|cx| Screening::new(public_key, window, cx))
|
||||
@@ -310,10 +311,14 @@ impl Screening {
|
||||
.w_full()
|
||||
.px_2()
|
||||
.gap_1p5()
|
||||
.rounded(px(button_radius()))
|
||||
.text_style(TextStyle::Callout)
|
||||
.hover(|this| this.bg(cx.theme().element_hover))
|
||||
.child(Avatar::new(profile.avatar()).small())
|
||||
.rounded(cx.theme().radius)
|
||||
.text_xs()
|
||||
.hover(|this| this.bg(cx.theme().primary))
|
||||
.child(
|
||||
Avatar::new(profile.avatar())
|
||||
.seed(profile.avatar_seed())
|
||||
.small(),
|
||||
)
|
||||
.child(profile.name()),
|
||||
);
|
||||
}
|
||||
@@ -365,7 +370,11 @@ impl Render for Screening {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.child(Avatar::new(profile.avatar()).large())
|
||||
.child(
|
||||
Avatar::new(profile.avatar())
|
||||
.seed(profile.avatar_seed())
|
||||
.large(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
@@ -383,8 +392,8 @@ impl Render for Screening {
|
||||
.h_7()
|
||||
.justify_center()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().surface_raised)
|
||||
.text_style(TextStyle::Callout)
|
||||
.bg(cx.theme().secondary)
|
||||
.text_xs()
|
||||
.truncate()
|
||||
.text_ellipsis()
|
||||
.text_center()
|
||||
@@ -398,9 +407,8 @@ impl Render for Screening {
|
||||
Button::new("njump")
|
||||
.icon(IconName::Link)
|
||||
.label("njump.me")
|
||||
.ghost_alt()
|
||||
.ghost()
|
||||
.small()
|
||||
.rounded()
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.open_njump(window, cx);
|
||||
})),
|
||||
@@ -408,9 +416,8 @@ impl Render for Screening {
|
||||
.child(
|
||||
Button::new("report")
|
||||
.tooltip("Report as a scam or impostor")
|
||||
.icon(IconName::Warning)
|
||||
.icon(IconName::TriangleAlert)
|
||||
.warning()
|
||||
.rounded()
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.report(window, cx);
|
||||
})),
|
||||
@@ -423,36 +430,33 @@ impl Render for Screening {
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap(px(theme::SPACE))
|
||||
.text_style(TextStyle::Callout)
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.child(status_badge(Some(self.followed), cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.text_style(TextStyle::Callout)
|
||||
.child("Contact")
|
||||
.child(
|
||||
div()
|
||||
.line_clamp(1)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child({
|
||||
if self.followed {
|
||||
SharedString::from(CONTACT)
|
||||
} else {
|
||||
SharedString::from(NOT_CONTACT)
|
||||
}
|
||||
}),
|
||||
),
|
||||
v_flex().text_xs().child("Contact").child(
|
||||
div()
|
||||
.line_clamp(1)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child({
|
||||
if self.followed {
|
||||
SharedString::from(CONTACT)
|
||||
} else {
|
||||
SharedString::from(NOT_CONTACT)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap(px(theme::SPACE))
|
||||
.text_style(TextStyle::Callout)
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.child(status_badge(last_active, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
@@ -462,7 +466,6 @@ impl Render for Screening {
|
||||
.icon(IconName::Info)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.rounded()
|
||||
.tooltip(RELAY_INFO),
|
||||
),
|
||||
)
|
||||
@@ -470,7 +473,7 @@ impl Render for Screening {
|
||||
div()
|
||||
.w_full()
|
||||
.line_clamp(1)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.map(|this| {
|
||||
if let Some(t) = self.last_active {
|
||||
this.child(SharedString::from(format!(
|
||||
@@ -487,11 +490,11 @@ impl Render for Screening {
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap(px(theme::SPACE))
|
||||
.gap_2()
|
||||
.child(status_badge(Some(self.verified), cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child({
|
||||
if let Some(addr) = self.address(cx) {
|
||||
SharedString::from(format!("{} validation", addr))
|
||||
@@ -504,7 +507,7 @@ impl Render for Screening {
|
||||
.child(
|
||||
div()
|
||||
.line_clamp(1)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child({
|
||||
if self.address(cx).is_some() {
|
||||
if self.verified {
|
||||
@@ -522,15 +525,15 @@ impl Render for Screening {
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap(px(theme::SPACE))
|
||||
.gap_2()
|
||||
.child(status_badge(Some(mutuals > 0), cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.text_style(TextStyle::Callout)
|
||||
.text_xs()
|
||||
.child(
|
||||
div()
|
||||
.line_clamp(1)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child({
|
||||
if mutuals > 0 {
|
||||
SharedString::from(mutuals_str)
|
||||
@@ -544,7 +547,6 @@ impl Render for Screening {
|
||||
.icon(IconName::Info)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.rounded()
|
||||
.disabled(mutuals == 0)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.mutual_contacts(
|
||||
@@ -567,15 +569,15 @@ fn status_badge(status: Option<bool>, cx: &App) -> Div {
|
||||
.flex_shrink_0()
|
||||
.map(|this| {
|
||||
if let Some(status) = status {
|
||||
this.child(Icon::new(IconName::CheckCircle).small().text_color({
|
||||
this.child(Icon::new(IconName::CircleCheck).small().text_color({
|
||||
if status {
|
||||
cx.theme().accent
|
||||
cx.theme().primary
|
||||
} else {
|
||||
cx.theme().text_faint
|
||||
cx.theme().muted_foreground
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
this.child(Indicator::new().small())
|
||||
this.child(Spinner::new().small())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@ use gpui::{
|
||||
App, AppContext, Context, Entity, IntoElement, ParentElement, PathPromptOptions, Render,
|
||||
SharedString, Styled, Window, div,
|
||||
};
|
||||
use settings::AppSettings;
|
||||
use theme::{ActiveTheme, AppExt as _, AppearanceMode, TextStyle, Typeset as _};
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::group_box::{GroupBox, GroupBoxVariants};
|
||||
use ui::input::{Input, InputState};
|
||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||
use ui::notification::Notification;
|
||||
use ui::switch::Switch;
|
||||
use ui::{Disableable, IconName, Sizable, WindowExtension, h_flex, v_flex};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||
use gpui_kit::component::group_box::{GroupBox, GroupBoxVariants};
|
||||
use gpui_kit::component::input::{Input, InputState};
|
||||
use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem};
|
||||
use gpui_kit::component::notification::Notification;
|
||||
use gpui_kit::component::switch::Switch;
|
||||
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
||||
use settings::{AppSettings, AppearanceMode};
|
||||
use ui::settings_row;
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
||||
cx.new(|cx| Preferences::new(window, cx))
|
||||
@@ -50,7 +52,9 @@ impl Preferences {
|
||||
/// Set the appearance mode (system, light or dark)
|
||||
fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) {
|
||||
AppSettings::update_appearance_mode(mode, cx);
|
||||
cx.set_appearance_mode(mode);
|
||||
AppSettings::global(cx).update(cx, |settings, cx| {
|
||||
settings.apply_theme(cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Ask the user for an image to show behind chat and group panels
|
||||
@@ -109,33 +113,36 @@ impl Render for Preferences {
|
||||
.id("general")
|
||||
.title("General")
|
||||
.fill()
|
||||
.child(
|
||||
Switch::new("screening")
|
||||
.label("Screening")
|
||||
.description(SCREENING)
|
||||
.checked(screening)
|
||||
.on_click(move |_, _window, cx| {
|
||||
.child(settings_row(
|
||||
"Screening",
|
||||
SCREENING,
|
||||
Switch::new("screening").checked(screening).on_click(
|
||||
move |_, _window, cx| {
|
||||
AppSettings::update_screening(!screening, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
},
|
||||
),
|
||||
cx,
|
||||
))
|
||||
.child(settings_row(
|
||||
"Web of Trust",
|
||||
WOT,
|
||||
Switch::new("wot-auto-trust")
|
||||
.label("Web of Trust")
|
||||
.description(WOT)
|
||||
.checked(wot_auto_trust)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_wot_auto_trust(!wot_auto_trust, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("avatar")
|
||||
.label("Hide user avatar")
|
||||
.description(AVATAR)
|
||||
.checked(hide_avatar)
|
||||
.on_click(move |_, _window, cx| {
|
||||
cx,
|
||||
))
|
||||
.child(settings_row(
|
||||
"Hide user avatar",
|
||||
AVATAR,
|
||||
Switch::new("avatar").checked(hide_avatar).on_click(
|
||||
move |_, _window, cx| {
|
||||
AppSettings::update_hide_avatar(!hide_avatar, cx);
|
||||
}),
|
||||
),
|
||||
},
|
||||
),
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
GroupBox::new()
|
||||
@@ -148,22 +155,18 @@ impl Render for Preferences {
|
||||
.justify_between()
|
||||
.child(
|
||||
v_flex()
|
||||
.child(div().text_xs().child(SharedString::from("Mode")))
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Callout)
|
||||
.child(SharedString::from("Mode")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(MODE)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("appearance-mode")
|
||||
.label(appearance_mode.label())
|
||||
.ghost_alt()
|
||||
.ghost()
|
||||
.small()
|
||||
.dropdown_menu(|this, _window, _cx| {
|
||||
AppearanceMode::ALL.iter().fold(this, |menu, mode| {
|
||||
@@ -182,16 +185,17 @@ impl Render for Preferences {
|
||||
.id("background")
|
||||
.title("Background")
|
||||
.fill()
|
||||
.child(
|
||||
.child(settings_row(
|
||||
"Panel background",
|
||||
BACKGROUND,
|
||||
Switch::new("panel-background")
|
||||
.label("Panel background")
|
||||
.description(BACKGROUND)
|
||||
.checked(panel_background)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_panel_background(!panel_background, cx);
|
||||
cx.refresh_windows();
|
||||
}),
|
||||
)
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
@@ -200,15 +204,11 @@ impl Render for Preferences {
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.child(div().text_xs().child(SharedString::from("Image")))
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Callout)
|
||||
.child(SharedString::from("Image")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption)
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.truncate()
|
||||
.child(SharedString::from(background_label)),
|
||||
),
|
||||
@@ -220,7 +220,7 @@ impl Render for Preferences {
|
||||
.child(
|
||||
Button::new("choose-background")
|
||||
.label("Choose image")
|
||||
.ghost_alt()
|
||||
.ghost()
|
||||
.small()
|
||||
.disabled(!panel_background)
|
||||
.on_click(|_, _window, cx| {
|
||||
@@ -230,7 +230,7 @@ impl Render for Preferences {
|
||||
.child(
|
||||
Button::new("reset-background")
|
||||
.label("Reset")
|
||||
.ghost_alt()
|
||||
.ghost()
|
||||
.small()
|
||||
.disabled(
|
||||
!panel_background
|
||||
@@ -249,15 +249,16 @@ impl Render for Preferences {
|
||||
.id("experiments")
|
||||
.title("Experiments")
|
||||
.fill()
|
||||
.child(
|
||||
.child(settings_row(
|
||||
"Decoupling Encryption Key",
|
||||
NIP4E,
|
||||
Switch::new("nip4e")
|
||||
.label("Decoupling Encryption Key")
|
||||
.description(NIP4E)
|
||||
.checked(nip4e)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_nip4e(!nip4e, cx);
|
||||
}),
|
||||
),
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
GroupBox::new()
|
||||
@@ -283,9 +284,10 @@ impl Render for Preferences {
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_style(TextStyle::Caption2)
|
||||
.text_xs()
|
||||
.font_medium()
|
||||
.italic()
|
||||
.text_color(cx.theme().text_faint)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from("Only support blossom service")),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user