@@ -14,6 +14,7 @@ theme = { path = "../theme" }
|
||||
common = { path = "../common" }
|
||||
global = { path = "../global" }
|
||||
chats = { path = "../chats" }
|
||||
settings = { path = "../settings" }
|
||||
auto_update = { path = "../auto_update" }
|
||||
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -6,8 +6,8 @@ use global::constants::{DEFAULT_MODAL_WIDTH, DEFAULT_SIDEBAR_WIDTH};
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, impl_internal_actions, px, App, AppContext, Axis, Context, Entity, InteractiveElement,
|
||||
IntoElement, ParentElement, Render, Styled, Subscription, Task, Window,
|
||||
div, impl_internal_actions, px, App, AppContext, Axis, Context, Entity, IntoElement,
|
||||
ParentElement, Render, Styled, Subscription, Task, Window,
|
||||
};
|
||||
use nostr_connect::prelude::*;
|
||||
use serde::Deserialize;
|
||||
@@ -20,9 +20,7 @@ use ui::dock_area::{DockArea, DockItem};
|
||||
use ui::{ContextModal, IconName, Root, Sizable, TitleBar};
|
||||
|
||||
use crate::views::chat::{self, Chat};
|
||||
use crate::views::{
|
||||
compose, login, new_account, onboarding, profile, relays, sidebar, startup, welcome,
|
||||
};
|
||||
use crate::views::{login, new_account, onboarding, preferences, sidebar, startup, welcome};
|
||||
|
||||
impl_internal_actions!(dock, [ToggleModal]);
|
||||
|
||||
@@ -181,6 +179,17 @@ impl ChatSpace {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let settings = preferences::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.title("Preferences")
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(settings.clone())
|
||||
});
|
||||
}
|
||||
|
||||
fn titlebar(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||
self.titlebar = status;
|
||||
cx.notify();
|
||||
@@ -206,53 +215,19 @@ impl ChatSpace {
|
||||
})
|
||||
}
|
||||
|
||||
fn on_modal_action(
|
||||
&mut self,
|
||||
action: &ToggleModal,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match action.modal {
|
||||
ModalKind::Profile => {
|
||||
let profile = profile::init(window, cx);
|
||||
fn toggle_appearance(&self, window: &mut Window, cx: &mut App) {
|
||||
if cx.theme().mode.is_dark() {
|
||||
Theme::change(ThemeMode::Light, Some(window), cx);
|
||||
} else {
|
||||
Theme::change(ThemeMode::Dark, Some(window), cx);
|
||||
}
|
||||
}
|
||||
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.title("Profile")
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(profile.clone())
|
||||
})
|
||||
}
|
||||
ModalKind::Compose => {
|
||||
let compose = compose::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.title("Direct Messages")
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(compose.clone())
|
||||
})
|
||||
}
|
||||
ModalKind::Relay => {
|
||||
let relays = relays::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _, _| {
|
||||
this.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.title("Edit your Messaging Relays")
|
||||
.child(relays.clone())
|
||||
});
|
||||
}
|
||||
ModalKind::SetupRelay => {
|
||||
let relays = relays::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _, _| {
|
||||
this.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.title("Your Messaging Relays are not configured")
|
||||
.child(relays.clone())
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
fn logout(&self, _window: &mut Window, cx: &mut App) {
|
||||
cx.background_spawn(async move {
|
||||
shared_state().unset_signer().await;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub(crate) fn set_center_panel<P: PanelView>(panel: P, window: &mut Window, cx: &mut App) {
|
||||
@@ -310,40 +285,28 @@ impl Render for ChatSpace {
|
||||
this.icon(IconName::Moon)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
if cx.theme().mode.is_dark() {
|
||||
Theme::change(
|
||||
ThemeMode::Light,
|
||||
Some(window),
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
Theme::change(
|
||||
ThemeMode::Dark,
|
||||
Some(window),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.toggle_appearance(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("settings")
|
||||
.tooltip("Open settings")
|
||||
Button::new("preferences")
|
||||
.tooltip("Open Preferences")
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Settings),
|
||||
.icon(IconName::Settings)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.open_settings(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("logout")
|
||||
.tooltip("Log out")
|
||||
.tooltip("Log Out")
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Logout)
|
||||
.on_click(cx.listener(move |_, _, _window, cx| {
|
||||
cx.background_spawn(async move {
|
||||
shared_state().unset_signer().await;
|
||||
})
|
||||
.detach();
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.logout(window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
@@ -356,7 +319,5 @@ impl Render for ChatSpace {
|
||||
.child(div().absolute().top_8().children(notification_layer))
|
||||
// Modals
|
||||
.children(modal_layer)
|
||||
// Actions
|
||||
.on_action(cx.listener(Self::on_modal_action))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@ fn main() {
|
||||
cx.activate(true);
|
||||
// Initialize components
|
||||
ui::init(cx);
|
||||
// Initialize settings
|
||||
settings::init(cx);
|
||||
// Initialize auto update
|
||||
auto_update::init(cx);
|
||||
// Initialize chat state
|
||||
|
||||
@@ -20,6 +20,7 @@ use gpui::{
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::fs;
|
||||
use theme::ActiveTheme;
|
||||
@@ -371,6 +372,7 @@ impl Chat {
|
||||
|
||||
self.uploading(true, cx);
|
||||
|
||||
let nip96 = AppSettings::get_global(cx).settings().media_server.clone();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
directories: false,
|
||||
@@ -389,7 +391,9 @@ impl Chat {
|
||||
|
||||
// Spawn task via async utility instead of GPUI context
|
||||
spawn(async move {
|
||||
let url = match nip96_upload(&shared_state().client, file_data).await {
|
||||
let url = match nip96_upload(&shared_state().client, nip96, file_data)
|
||||
.await
|
||||
{
|
||||
Ok(url) => Some(url),
|
||||
Err(e) => {
|
||||
log::error!("Upload error: {e}");
|
||||
@@ -542,6 +546,9 @@ impl Chat {
|
||||
return div().id(ix);
|
||||
};
|
||||
|
||||
let proxy = AppSettings::get_global(cx).settings().proxy_user_avatars;
|
||||
let hide_avatar = AppSettings::get_global(cx).settings().hide_user_avatars;
|
||||
|
||||
let message = message.borrow();
|
||||
|
||||
// Message without ID, Author probably the placeholder
|
||||
@@ -590,7 +597,9 @@ impl Chat {
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.child(Avatar::new(author.render_avatar()).size(rems(2.)))
|
||||
.when(!hide_avatar, |this| {
|
||||
this.child(Avatar::new(author.render_avatar(proxy)).size(rems(2.)))
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
|
||||
@@ -14,6 +14,7 @@ use gpui::{
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::Timer;
|
||||
use theme::ActiveTheme;
|
||||
@@ -305,6 +306,8 @@ impl Render for Compose {
|
||||
const DESCRIPTION: &str =
|
||||
"Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";
|
||||
|
||||
let proxy = AppSettings::get_global(cx).settings().proxy_user_avatars;
|
||||
|
||||
let label: SharedString = if self.selected.read(cx).len() > 1 {
|
||||
"Create Group DM".into()
|
||||
} else {
|
||||
@@ -413,7 +416,7 @@ impl Render for Compose {
|
||||
.gap_3()
|
||||
.text_sm()
|
||||
.child(
|
||||
img(item.render_avatar())
|
||||
img(item.render_avatar(proxy))
|
||||
.size_7()
|
||||
.flex_shrink_0(),
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod compose;
|
||||
pub mod login;
|
||||
pub mod new_account;
|
||||
pub mod onboarding;
|
||||
pub mod preferences;
|
||||
pub mod profile;
|
||||
pub mod relays;
|
||||
pub mod sidebar;
|
||||
|
||||
@@ -9,6 +9,7 @@ use gpui::{
|
||||
Styled, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use settings::AppSettings;
|
||||
use smol::fs;
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
@@ -94,6 +95,7 @@ impl NewAccount {
|
||||
}
|
||||
|
||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let nip96 = AppSettings::get_global(cx).settings().media_server.clone();
|
||||
let avatar_input = self.avatar_input.downgrade();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
@@ -122,7 +124,9 @@ impl NewAccount {
|
||||
let (tx, rx) = oneshot::channel::<Url>();
|
||||
|
||||
spawn(async move {
|
||||
if let Ok(url) = nip96_upload(&shared_state().client, file_data).await {
|
||||
if let Ok(url) =
|
||||
nip96_upload(&shared_state().client, nip96, file_data).await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
});
|
||||
|
||||
308
crates/coop/src/views/preferences.rs
Normal file
308
crates/coop/src/views/preferences.rs
Normal file
@@ -0,0 +1,308 @@
|
||||
use common::profile::RenderProfile;
|
||||
use global::{
|
||||
constants::{DEFAULT_MODAL_WIDTH, NIP96_SERVER},
|
||||
shared_state,
|
||||
};
|
||||
use gpui::{
|
||||
div, http_client::Url, prelude::FluentBuilder, px, relative, rems, App, AppContext, Context,
|
||||
Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{
|
||||
avatar::Avatar,
|
||||
button::{Button, ButtonVariants},
|
||||
input::{InputState, TextInput},
|
||||
switch::Switch,
|
||||
ContextModal, IconName, Sizable, Size, StyledExt,
|
||||
};
|
||||
|
||||
use crate::views::{profile, relays};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
||||
Preferences::new(window, cx)
|
||||
}
|
||||
|
||||
pub struct Preferences {
|
||||
media_input: Entity<InputState>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Preferences {
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let media_server = AppSettings::get_global(cx)
|
||||
.settings()
|
||||
.media_server
|
||||
.to_string();
|
||||
|
||||
let media_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.default_value(media_server)
|
||||
.placeholder(NIP96_SERVER)
|
||||
});
|
||||
|
||||
Self {
|
||||
media_input,
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn open_profile(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let profile = profile::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.title("Profile")
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(profile.clone())
|
||||
});
|
||||
}
|
||||
|
||||
fn open_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let relays = relays::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _, _| {
|
||||
this.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.title("Edit your Messaging Relays")
|
||||
.child(relays.clone())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Preferences {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
const MEDIA_DESCRIPTION: &str = "Coop only supports NIP-96 media servers for now. If you're not sure about it, please keep the default value.";
|
||||
const BACKUP_DESCRIPTION: &str =
|
||||
"When a user sends a message, Coop won't back it up to the user's messaging relays";
|
||||
const TRUSTED_DESCRIPTION: &str = "Show trusted requests by default";
|
||||
const HIDE_AVATAR_DESCRIPTION: &str =
|
||||
"Unload all avatar pictures to improve performance and reduce memory usage";
|
||||
const PROXY_DESCRIPTION: &str =
|
||||
"Use wsrv.nl to resize and downscale avatar pictures (saves ~50MB of data)";
|
||||
|
||||
let input_state = self.media_input.downgrade();
|
||||
let settings = AppSettings::get_global(cx).settings();
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child("Account"),
|
||||
)
|
||||
.when_some(shared_state().identity(), |this, profile| {
|
||||
this.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.id("current-user")
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
Avatar::new(
|
||||
profile.render_avatar(settings.proxy_user_avatars),
|
||||
)
|
||||
.size(rems(2.4)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.3))
|
||||
.font_semibold()
|
||||
.child(profile.render_name()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.3))
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child("See your profile"),
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.open_profile(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("relays")
|
||||
.label("DM Relays")
|
||||
.ghost()
|
||||
.small()
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.open_relays(window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child("Media Server"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_start()
|
||||
.gap_1()
|
||||
.child(TextInput::new(&self.media_input).xsmall())
|
||||
.child(
|
||||
Button::new("update")
|
||||
.icon(IconName::CheckCircleFill)
|
||||
.ghost()
|
||||
.with_size(Size::Size(px(26.)))
|
||||
.on_click(move |_, window, cx| {
|
||||
if let Some(input) = input_state.upgrade() {
|
||||
let value = input.read(cx).value();
|
||||
let Ok(url) = Url::parse(value) else {
|
||||
window.push_notification("URL is not valid", cx);
|
||||
return;
|
||||
};
|
||||
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.media_server = url;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(MEDIA_DESCRIPTION),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child("Messages"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
Switch::new("backup_messages")
|
||||
.label("Backup messages")
|
||||
.description(BACKUP_DESCRIPTION)
|
||||
.checked(settings.backup_messages)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.backup_messages =
|
||||
!this.settings.backup_messages;
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("only_show_trusted")
|
||||
.label("Only trusted")
|
||||
.description(TRUSTED_DESCRIPTION)
|
||||
.checked(settings.only_show_trusted)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.only_show_trusted =
|
||||
!this.settings.only_show_trusted;
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child("Display"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
Switch::new("hide_user_avatars")
|
||||
.label("Hide user avatars")
|
||||
.description(HIDE_AVATAR_DESCRIPTION)
|
||||
.checked(settings.hide_user_avatars)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.hide_user_avatars =
|
||||
!this.settings.hide_user_avatars;
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("proxy_user_avatars")
|
||||
.label("Proxy user avatars")
|
||||
.description(PROXY_DESCRIPTION)
|
||||
.checked(settings.proxy_user_avatars)
|
||||
.on_click(|_, _window, cx| {
|
||||
AppSettings::global(cx).update(cx, |this, cx| {
|
||||
this.settings.proxy_user_avatars =
|
||||
!this.settings.proxy_user_avatars;
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use gpui::{
|
||||
PathPromptOptions, Render, Styled, Task, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use settings::AppSettings;
|
||||
use smol::fs;
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
@@ -104,6 +105,7 @@ impl Profile {
|
||||
}
|
||||
|
||||
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let nip96 = AppSettings::get_global(cx).settings().media_server.clone();
|
||||
let avatar_input = self.avatar_input.downgrade();
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
@@ -123,7 +125,9 @@ impl Profile {
|
||||
let (tx, rx) = oneshot::channel::<Url>();
|
||||
|
||||
spawn(async move {
|
||||
if let Ok(url) = nip96_upload(&shared_state().client, file_data).await {
|
||||
if let Ok(url) =
|
||||
nip96_upload(&shared_state().client, nip96, file_data).await
|
||||
{
|
||||
_ = tx.send(url);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ use gpui::{
|
||||
div, img, rems, App, ClickEvent, Div, InteractiveElement, IntoElement, ParentElement as _,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::StyledExt;
|
||||
@@ -59,6 +60,7 @@ impl DisplayRoom {
|
||||
impl RenderOnce for DisplayRoom {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let handler = self.handler.clone();
|
||||
let hide_avatar = AppSettings::get_global(cx).settings().hide_user_avatars;
|
||||
|
||||
self.base
|
||||
.id(self.ix)
|
||||
@@ -67,25 +69,27 @@ impl RenderOnce for DisplayRoom {
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.rounded(cx.theme().radius)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.size_6()
|
||||
.rounded_full()
|
||||
.overflow_hidden()
|
||||
.map(|this| {
|
||||
if let Some(path) = self.img {
|
||||
this.child(Avatar::new(path).size(rems(1.5)))
|
||||
} else {
|
||||
this.child(
|
||||
img("brand/avatar.png")
|
||||
.rounded_full()
|
||||
.size_6()
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.when(!hide_avatar, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.size_6()
|
||||
.rounded_full()
|
||||
.overflow_hidden()
|
||||
.map(|this| {
|
||||
if let Some(path) = self.img {
|
||||
this.child(Avatar::new(path).size(rems(1.5)))
|
||||
} else {
|
||||
this.child(
|
||||
img("brand/avatar.png")
|
||||
.rounded_full()
|
||||
.size_6()
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
|
||||
@@ -8,27 +8,29 @@ use chats::{ChatRegistry, RoomEmitter};
|
||||
use common::debounced_delay::DebouncedDelay;
|
||||
use common::profile::RenderProfile;
|
||||
use element::DisplayRoom;
|
||||
use global::constants::SEARCH_RELAYS;
|
||||
use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
||||
use global::shared_state;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, rems, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString,
|
||||
Styled, Subscription, Task, Window,
|
||||
div, px, rems, uniform_list, AnyElement, App, AppContext, ClipboardItem, Context, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
RetainAllImageCache, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
Window,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonRounded, ButtonVariants};
|
||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::popup_menu::{PopupMenu, PopupMenuExt};
|
||||
use ui::popup_menu::PopupMenu;
|
||||
use ui::skeleton::Skeleton;
|
||||
use ui::{ContextModal, IconName, Selectable, Sizable, StyledExt};
|
||||
|
||||
use crate::chatspace::{ModalKind, ToggleModal};
|
||||
use crate::views::compose;
|
||||
|
||||
mod element;
|
||||
|
||||
@@ -68,6 +70,7 @@ impl Sidebar {
|
||||
let indicator = cx.new(|_| None);
|
||||
let local_result = cx.new(|_| None);
|
||||
let global_result = cx.new(|_| None);
|
||||
let trusted_only = AppSettings::get_global(cx).settings().only_show_trusted;
|
||||
|
||||
let find_input =
|
||||
cx.new(|cx| InputState::new(window, cx).placeholder("Find or start a conversation"));
|
||||
@@ -118,7 +121,7 @@ impl Sidebar {
|
||||
image_cache: RetainAllImageCache::new(cx),
|
||||
find_debouncer: DebouncedDelay::new(),
|
||||
finding: false,
|
||||
trusted_only: false,
|
||||
trusted_only,
|
||||
indicator,
|
||||
active_filter,
|
||||
find_input,
|
||||
@@ -334,7 +337,20 @@ impl Sidebar {
|
||||
});
|
||||
}
|
||||
|
||||
fn open_compose(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let compose = compose::init(window, cx);
|
||||
|
||||
window.open_modal(cx, move |modal, _window, _cx| {
|
||||
modal
|
||||
.title("Direct Messages")
|
||||
.width(px(DEFAULT_MODAL_WIDTH))
|
||||
.child(compose.clone())
|
||||
});
|
||||
}
|
||||
|
||||
fn render_account(&self, profile: &Profile, cx: &Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_global(cx).settings().proxy_user_avatars;
|
||||
|
||||
div()
|
||||
.px_3()
|
||||
.h_8()
|
||||
@@ -344,56 +360,38 @@ impl Sidebar {
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.id("current-user")
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.font_semibold()
|
||||
.child(Avatar::new(profile.render_avatar()).size(rems(1.75)))
|
||||
.child(profile.render_name()),
|
||||
.child(Avatar::new(profile.render_avatar(proxy)).size(rems(1.75)))
|
||||
.child(profile.render_name())
|
||||
.on_click(cx.listener({
|
||||
let Ok(public_key) = profile.public_key().to_bech32();
|
||||
let item = ClipboardItem::new_string(public_key);
|
||||
|
||||
move |_, _, window, cx| {
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
cx.write_to_primary(item.clone());
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
cx.write_to_clipboard(item.clone());
|
||||
|
||||
window.push_notification("User's NPUB is copied", cx);
|
||||
}
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("user")
|
||||
.icon(IconName::Ellipsis)
|
||||
.small()
|
||||
.ghost()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.popup_menu(|this, _window, _cx| {
|
||||
this.menu(
|
||||
"Profile",
|
||||
Box::new(ToggleModal {
|
||||
modal: ModalKind::Profile,
|
||||
}),
|
||||
)
|
||||
.menu(
|
||||
"Relays",
|
||||
Box::new(ToggleModal {
|
||||
modal: ModalKind::Relay,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("compose")
|
||||
.icon(IconName::PlusFill)
|
||||
.tooltip("Create DM or Group DM")
|
||||
.small()
|
||||
.primary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(ToggleModal {
|
||||
modal: ModalKind::Compose,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
),
|
||||
Button::new("compose")
|
||||
.icon(IconName::PlusFill)
|
||||
.tooltip("Create DM or Group DM")
|
||||
.small()
|
||||
.primary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.open_compose(window, cx);
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user