clean up the sidebar
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.25 13L12 16.25L8.75 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 7.75V15.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -19,9 +19,6 @@ pub enum PanelEvent {
|
||||
|
||||
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
|
||||
/// The name of the panel used to serialize, deserialize and identify the panel.
|
||||
///
|
||||
/// This is used to identify the panel when deserializing the panel.
|
||||
/// Once you have defined a panel id, this must not be changed.
|
||||
fn panel_id(&self) -> SharedString;
|
||||
|
||||
/// The title of the panel
|
||||
|
||||
@@ -22,6 +22,7 @@ impl<T: IconNamed> From<T> for Icon {
|
||||
pub enum IconName {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowDownCircle,
|
||||
Boom,
|
||||
Book,
|
||||
ChevronDown,
|
||||
@@ -99,6 +100,7 @@ impl IconNamed for IconName {
|
||||
match self {
|
||||
Self::ArrowLeft => "icons/arrow-left.svg",
|
||||
Self::ArrowRight => "icons/arrow-right.svg",
|
||||
Self::ArrowDownCircle => "icons/arrow-down-circle.svg",
|
||||
Self::Boom => "icons/boom.svg",
|
||||
Self::Book => "icons/book.svg",
|
||||
Self::ChevronDown => "icons/chevron-down.svg",
|
||||
|
||||
+4
-181
@@ -1,4 +1,3 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::settings::AppSettings;
|
||||
@@ -7,22 +6,19 @@ use auto_update::AutoUpdater;
|
||||
use chat::{ChatEvent, ChatRegistry};
|
||||
use common::download_dir;
|
||||
use device::{DeviceEvent, DeviceRegistry};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, Styled, Subscription, Task, Window, div, px,
|
||||
Action, App, AppContext, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SharedString, Styled, Subscription, Task, Window, div, px,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{PersonRegistry, shorten_pubkey};
|
||||
use serde::Deserialize;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::{NostrRegistry, StateEvent};
|
||||
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
|
||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||
use ui::notification::{Notification, NotificationKind};
|
||||
use ui::{Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
|
||||
use ui::{IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
|
||||
|
||||
use crate::dialogs::restore::RestoreEncryption;
|
||||
use crate::dialogs::{new_chat, new_community, settings};
|
||||
@@ -69,7 +65,6 @@ enum Command {
|
||||
|
||||
pub struct Workspace {
|
||||
dock: Entity<DockArea>,
|
||||
title_bar_chrome: Rc<dock::TitleBarChrome>,
|
||||
/// Async tasks
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
/// Event subscriptions
|
||||
@@ -82,7 +77,7 @@ impl Workspace {
|
||||
let device = DeviceRegistry::global(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
|
||||
let (dock, title_bar_chrome) = dock::dock_area("coop", window, cx);
|
||||
let (dock, _) = dock::dock_area("coop", window, cx);
|
||||
let sidebar = cx.new(|cx| Sidebar::new(window, dock.downgrade(), cx));
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
@@ -207,7 +202,6 @@ impl Workspace {
|
||||
|
||||
Self {
|
||||
dock,
|
||||
title_bar_chrome,
|
||||
tasks: vec![],
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
@@ -510,174 +504,6 @@ impl Workspace {
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
fn titlebar_right(_window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
let auto_updater = AutoUpdater::try_global(cx);
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
|
||||
let Some(public_key) = nostr.read(cx).current_user() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
let announcement = profile.announcement();
|
||||
|
||||
let updater_status = auto_updater.as_ref().and_then(|updater| {
|
||||
let updater = updater.read(cx);
|
||||
(!updater.idle()).then(|| updater.status())
|
||||
});
|
||||
|
||||
let staged_update = auto_updater
|
||||
.as_ref()
|
||||
.is_some_and(|updater| updater.read(cx).staged());
|
||||
|
||||
h_flex()
|
||||
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
||||
.gap_2()
|
||||
.when_some(updater_status, |this, status| {
|
||||
this.child(div().text_xs().italic().child(status))
|
||||
})
|
||||
.when(staged_update, |this| {
|
||||
this.child(
|
||||
Button::new("restart-to-update")
|
||||
.label("Restart to Update")
|
||||
.tooltip("Quit and relaunch into the installed update")
|
||||
.small()
|
||||
.ghost()
|
||||
.on_click(|_event, _window, cx| {
|
||||
if let Some(auto_updater) = AutoUpdater::try_global(cx) {
|
||||
auto_updater.update(cx, |this, cx| this.restart(cx));
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(nip4e_enabled, |this| {
|
||||
this.child(
|
||||
Button::new("key")
|
||||
.icon(IconName::UserKey)
|
||||
.tooltip("Decoupled encryption key")
|
||||
.small()
|
||||
.ghost()
|
||||
.dropdown_menu(move |this, _window, _cx| {
|
||||
this.min_w(px(260.))
|
||||
.label("Encryption Key")
|
||||
.when_some(announcement.as_ref(), |this, announcement| {
|
||||
let name = announcement.client_name();
|
||||
let pkey = shorten_pubkey(announcement.public_key(), 8);
|
||||
|
||||
this.item(PopupMenuItem::element(move |_window, cx| {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::Device)
|
||||
.small()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(name.clone())
|
||||
}))
|
||||
.item(
|
||||
PopupMenuItem::element(move |_window, cx| {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
Icon::new(IconName::UserKey)
|
||||
.small()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(SharedString::from(pkey.clone()))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Backup",
|
||||
IconName::Shield,
|
||||
Box::new(Command::BackupEncryption),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Restore from secret key",
|
||||
IconName::Usb,
|
||||
Box::new(Command::ImportEncryption),
|
||||
)
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Reload",
|
||||
IconName::Refresh,
|
||||
Box::new(Command::RefreshEncryption),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Reset",
|
||||
IconName::Warning,
|
||||
Box::new(Command::ResetEncryption),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Button::new("inbox")
|
||||
.icon(IconName::Inbox)
|
||||
.small()
|
||||
.ghost()
|
||||
.dropdown_menu(move |this, _window, cx| {
|
||||
let urls: Vec<(SharedString, SharedString)> = profile
|
||||
.messaging_relays()
|
||||
.iter()
|
||||
.map(|url| {
|
||||
(
|
||||
SharedString::from(url.to_string()),
|
||||
chat.read(cx).count_messages(url).to_string().into(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Header
|
||||
let menu = this.min_w(px(260.)).label("Messaging Relays");
|
||||
|
||||
// Content
|
||||
let menu = urls.into_iter().fold(menu, |this, (url, count)| {
|
||||
this.item(PopupMenuItem::element(move |_window, cx| {
|
||||
h_flex()
|
||||
.px_1()
|
||||
.w_full()
|
||||
.text_sm()
|
||||
.justify_between()
|
||||
.child(url.clone())
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(count.clone()),
|
||||
)
|
||||
}))
|
||||
});
|
||||
|
||||
// Footer
|
||||
menu.separator()
|
||||
.menu_with_icon(
|
||||
"Manage gossip relays",
|
||||
IconName::Relay,
|
||||
Box::new(Command::ShowRelayList),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Manage messaging relays",
|
||||
IconName::Relay,
|
||||
Box::new(Command::ShowMessaging),
|
||||
)
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Reload",
|
||||
IconName::Refresh,
|
||||
Box::new(Command::RefreshMessagingRelays),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
@@ -685,9 +511,6 @@ impl Render for Workspace {
|
||||
let modal_layer = Root::render_modal_layer(window, cx);
|
||||
let notification_layer = Root::render_notification_layer(window, cx);
|
||||
|
||||
// Render the title bar chrome
|
||||
self.title_bar_chrome.set_trailing(Self::titlebar_right);
|
||||
|
||||
div()
|
||||
.id("workspace")
|
||||
.on_action(cx.listener(Self::on_command))
|
||||
|
||||
+370
-322
@@ -7,10 +7,10 @@ use common::TimestampExt;
|
||||
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
AnyElement, App, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, ScrollHandle, SharedString,
|
||||
StatefulInteractiveElement, Styled, StyledImage, Subscription, UniformListScrollHandle,
|
||||
WeakEntity, Window, div, img, px, retain_all, uniform_list,
|
||||
Stateful, StatefulInteractiveElement, Styled, StyledImage, Subscription,
|
||||
UniformListScrollHandle, WeakEntity, Window, div, img, px, retain_all, uniform_list,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
@@ -32,8 +32,8 @@ use ui::{
|
||||
};
|
||||
|
||||
use crate::Command;
|
||||
use crate::dialogs::import;
|
||||
|
||||
mod onboarding;
|
||||
mod tab;
|
||||
mod tree;
|
||||
|
||||
@@ -51,11 +51,9 @@ pub struct Sidebar {
|
||||
active_tab: SidebarTab,
|
||||
/// The community the sidebar is browsing, if any
|
||||
community: Option<WeakEntity<Community>>,
|
||||
/// Expanded state of the community's sections
|
||||
channels_open: bool,
|
||||
admins_open: bool,
|
||||
members_open: bool,
|
||||
/// Whether there are new chat requests
|
||||
new_requests: bool,
|
||||
_subscriptions: SmallVec<[Subscription; 4]>,
|
||||
}
|
||||
@@ -64,7 +62,6 @@ impl Sidebar {
|
||||
pub fn new(window: &mut Window, dock: WeakEntity<DockArea>, cx: &mut Context<Self>) -> Self {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let communities = CommunityRegistry::global(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
|
||||
@@ -82,16 +79,11 @@ impl Sidebar {
|
||||
window,
|
||||
|_this, _communities, event, window, cx| {
|
||||
if let CommunityEvent::Error(error) = event {
|
||||
window
|
||||
.push_notification(Notification::error(error.clone()).autohide(false), cx);
|
||||
window.push_notification(Notification::error(error.clone()), cx);
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
subscriptions.push(cx.observe(&nostr, |_this, _nostr, cx| cx.notify()));
|
||||
|
||||
subscriptions.push(cx.observe(&communities, |_this, _communities, cx| cx.notify()));
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handles: [
|
||||
@@ -115,7 +107,6 @@ impl Sidebar {
|
||||
if self.active_tab == tab {
|
||||
return;
|
||||
}
|
||||
|
||||
self.active_tab = tab;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -164,11 +155,11 @@ impl Sidebar {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_user(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
fn render_user(&self, cx: &mut Context<Self>) -> Stateful<Div> {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let current_user = nostr.read(cx).current_user();
|
||||
|
||||
let mut row = h_flex()
|
||||
h_flex()
|
||||
.id("sidebar-user")
|
||||
.w_full()
|
||||
.h(TABBAR_HEIGHT)
|
||||
@@ -178,16 +169,15 @@ impl Sidebar {
|
||||
.px_2()
|
||||
.when(cfg!(target_os = "macos"), |this| {
|
||||
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
||||
});
|
||||
|
||||
if let Some(public_key) = current_user.as_ref() {
|
||||
})
|
||||
.when_some(current_user.as_ref(), |this, public_key| {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(public_key, cx);
|
||||
let avatar = profile.avatar();
|
||||
let avatar_seed = profile.avatar_seed();
|
||||
let name = profile.name();
|
||||
|
||||
row = row.child(
|
||||
this.child(
|
||||
Button::new("current-user")
|
||||
.child(
|
||||
Avatar::new(avatar.clone())
|
||||
@@ -217,7 +207,11 @@ impl Sidebar {
|
||||
.child(name.clone())
|
||||
}))
|
||||
.separator()
|
||||
.menu_with_icon("Inbox", IconName::Inbox, Box::new(Command::ShowInbox))
|
||||
.menu_with_icon(
|
||||
"Inbox",
|
||||
IconName::Inbox,
|
||||
Box::new(Command::ShowInbox),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Search",
|
||||
IconName::Search,
|
||||
@@ -238,7 +232,11 @@ impl Sidebar {
|
||||
IconName::UserKey,
|
||||
Box::new(Command::ShowBackup),
|
||||
)
|
||||
.menu_with_icon("Themes", IconName::Sun, Box::new(Command::ToggleTheme))
|
||||
.menu_with_icon(
|
||||
"Themes",
|
||||
IconName::Sun,
|
||||
Box::new(Command::ToggleTheme),
|
||||
)
|
||||
.when(AutoUpdater::is_available(cx), |this| {
|
||||
this.separator().menu_with_icon(
|
||||
"Check for Updates",
|
||||
@@ -252,11 +250,14 @@ impl Sidebar {
|
||||
Box::new(Command::ShowSettings),
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if self.community.is_some() {
|
||||
row = row.child(div().flex_1()).child(
|
||||
)
|
||||
})
|
||||
.child(div().flex_1())
|
||||
.when_some(AutoUpdater::try_global(cx), |this, updater| {
|
||||
this.child(self.render_updater(updater, cx))
|
||||
})
|
||||
.when(self.community.is_some(), |this| {
|
||||
this.child(
|
||||
Button::new("sidebar-back")
|
||||
.icon(IconName::ArrowLeft)
|
||||
.tooltip("Back")
|
||||
@@ -265,300 +266,42 @@ impl Sidebar {
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.reset_community(cx);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
title_bar_drag_handlers(row, window, cx).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn recent_communities(cx: &App) -> Vec<Entity<Community>> {
|
||||
const LIMIT: usize = 3;
|
||||
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let communities = registry.read(cx).communities();
|
||||
let recent = AppSettings::get_recent_communities(cx);
|
||||
|
||||
let mut rows: Vec<Entity<Community>> = recent
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
communities
|
||||
.iter()
|
||||
.find(|community| community.read(cx).id().to_hex() == *id)
|
||||
)
|
||||
})
|
||||
.take(LIMIT)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if rows.is_empty() {
|
||||
rows = communities.iter().take(LIMIT).cloned().collect();
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
fn render_updater(&self, updater: Entity<AutoUpdater>, cx: &mut App) -> AnyElement {
|
||||
let status = updater.read(cx).status();
|
||||
let staged = updater.read(cx).staged();
|
||||
|
||||
fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||
match tab {
|
||||
SidebarTab::Recents => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let rooms = chat.read(cx).rooms(&RoomKind::Ongoing, cx);
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let community_count = registry.read(cx).communities().len();
|
||||
let communities = recent_communities(cx);
|
||||
|
||||
if communities.is_empty() && rooms.is_empty() {
|
||||
return vec![SidebarRow::Hint {
|
||||
text: "Nothing recent yet".into(),
|
||||
}];
|
||||
}
|
||||
|
||||
let mut rows = Vec::new();
|
||||
|
||||
if !communities.is_empty() {
|
||||
rows.push(SidebarRow::Section {
|
||||
label: "Communities".into(),
|
||||
count: community_count,
|
||||
});
|
||||
rows.extend(
|
||||
communities
|
||||
.into_iter()
|
||||
.map(|community| SidebarRow::Community { community }),
|
||||
);
|
||||
rows.push(SidebarRow::Action {
|
||||
label: "Show all communities".into(),
|
||||
tab: SidebarTab::Communities,
|
||||
});
|
||||
}
|
||||
|
||||
if !rooms.is_empty() {
|
||||
rows.push(SidebarRow::Section {
|
||||
label: "Chats".into(),
|
||||
count: rooms.len(),
|
||||
});
|
||||
rows.extend(
|
||||
rooms
|
||||
.into_iter()
|
||||
.take(5)
|
||||
.map(|room| SidebarRow::Room { room }),
|
||||
);
|
||||
rows.push(SidebarRow::Action {
|
||||
label: "Show all chats".into(),
|
||||
tab: SidebarTab::Chats,
|
||||
});
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
SidebarTab::Chats => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let chat = chat.read(cx);
|
||||
let messages = chat.rooms(&RoomKind::Ongoing, cx);
|
||||
|
||||
let mut rows = vec![SidebarRow::Section {
|
||||
label: "Chats".into(),
|
||||
count: messages.len(),
|
||||
}];
|
||||
|
||||
if messages.is_empty() {
|
||||
rows.push(SidebarRow::Hint {
|
||||
text: "No conversations yet".into(),
|
||||
});
|
||||
} else {
|
||||
rows.extend(messages.into_iter().map(|room| SidebarRow::Room { room }));
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
SidebarTab::Communities => {
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let communities = registry.read(cx).communities();
|
||||
|
||||
let mut rows = vec![SidebarRow::Section {
|
||||
label: "Communities".into(),
|
||||
count: communities.len(),
|
||||
}];
|
||||
|
||||
if communities.is_empty() {
|
||||
rows.push(SidebarRow::Hint {
|
||||
text: "No communities yet".into(),
|
||||
});
|
||||
} else {
|
||||
rows.extend(
|
||||
communities
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|community| SidebarRow::Community { community }),
|
||||
);
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_rows(range: Range<usize>, rows: &[SidebarRow], cx: &Context<Sidebar>) -> Vec<AnyElement> {
|
||||
rows.get(range.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(offset, row)| {
|
||||
let index = range.start + offset;
|
||||
|
||||
match row {
|
||||
SidebarRow::Section { label, count } => TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Section,
|
||||
label.clone(),
|
||||
)
|
||||
.count(*count)
|
||||
.into_any_element(),
|
||||
SidebarRow::Room { room } => {
|
||||
let name = room.read(cx).display_name(cx);
|
||||
let picture = room.read(cx).display_image(cx);
|
||||
let seed = room.read(cx).display_image_seed(cx);
|
||||
let created_at = room.read(cx).created_at.to_ago();
|
||||
let room_clone = room.clone();
|
||||
|
||||
let handler = cx.listener(move |this, _event, window, cx| {
|
||||
this.open_room(room_clone.clone(), window, cx);
|
||||
});
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Room,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.created_at(created_at)
|
||||
.on_click(handler)
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Community { community } => {
|
||||
let name = community.read(cx).name();
|
||||
let seed = community.read(cx).id().to_hex();
|
||||
let picture = community.read(cx).icon();
|
||||
let community = community.clone();
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Community,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.open_community(community.clone(), window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Action { label, tab } => {
|
||||
let tab = *tab;
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Action,
|
||||
label.clone(),
|
||||
)
|
||||
.icon(IconName::ArrowRight)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.select_tab(tab, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Hint { text } => TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Hint,
|
||||
text.clone(),
|
||||
)
|
||||
.into_any_element(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Panel for Sidebar {
|
||||
fn panel_id(&self) -> SharedString {
|
||||
"Sidebar".into()
|
||||
}
|
||||
|
||||
fn title(&self, _cx: &App) -> AnyElement {
|
||||
SharedString::from("Sidebar").into_any_element()
|
||||
}
|
||||
|
||||
fn closable(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn zoomable(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for Sidebar {}
|
||||
|
||||
impl Focusable for Sidebar {
|
||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Sidebar {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (logged_in, ready) = {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
(
|
||||
nostr.read(cx).current_user().is_some(),
|
||||
nostr.read(cx).ready(),
|
||||
)
|
||||
};
|
||||
|
||||
if !logged_in {
|
||||
if !ready {
|
||||
return v_flex()
|
||||
.size_full()
|
||||
.bg(cx.theme().surface_background)
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().border_variant)
|
||||
.into_any_element();
|
||||
}
|
||||
return onboarding::render(window, cx).into_any_element();
|
||||
}
|
||||
|
||||
let community = self
|
||||
.community
|
||||
.clone()
|
||||
.and_then(|community| community.upgrade());
|
||||
|
||||
if community.is_none() {
|
||||
self.community = None;
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.image_cache(retain_all("sidebar"))
|
||||
.size_full()
|
||||
.relative()
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.bg(cx.theme().surface_background)
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().border_variant)
|
||||
.child(self.render_user(window, cx))
|
||||
.map(|this| match community {
|
||||
Some(community) => this.child(self.render_community(community, cx)),
|
||||
None => this.child(self.render_tabs(cx)),
|
||||
.text_xs()
|
||||
.child(status)
|
||||
.when(staged, |this| {
|
||||
this.child(
|
||||
Button::new("restart-to-update")
|
||||
.icon(IconName::ArrowDownCircle)
|
||||
.tooltip("Quit and relaunch into the installed update")
|
||||
.small()
|
||||
.ghost()
|
||||
.on_click(move |_, _window, cx| {
|
||||
updater.update(cx, |this, cx| {
|
||||
this.restart(cx);
|
||||
});
|
||||
}),
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sidebar {
|
||||
fn render_tabs(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let loading = chat.read(cx).loading();
|
||||
|
||||
let sidebar = cx.entity().downgrade();
|
||||
let active_tab = self.active_tab;
|
||||
let rows = Rc::new(rows_for(active_tab, cx));
|
||||
let rows = Rc::new(self.rows_for(active_tab, cx));
|
||||
let scroll_handle = &self.scroll_handles[active_tab.index()];
|
||||
|
||||
v_flex()
|
||||
@@ -653,8 +396,8 @@ impl Sidebar {
|
||||
uniform_list(
|
||||
active_tab.list_id(),
|
||||
rows.len(),
|
||||
cx.processor(move |_this, range, _window, cx| {
|
||||
render_rows(range, rows.as_slice(), cx)
|
||||
cx.processor(move |this, range, _window, cx| {
|
||||
this.render_rows(range, rows.as_slice(), cx)
|
||||
}),
|
||||
)
|
||||
.track_scroll(scroll_handle)
|
||||
@@ -742,7 +485,7 @@ impl Sidebar {
|
||||
.pb_2()
|
||||
.track_scroll(&self.community_scroll)
|
||||
.overflow_y_scroll()
|
||||
.child(section_row(
|
||||
.child(self.section_row(
|
||||
"channels",
|
||||
"Channels",
|
||||
channels.len(),
|
||||
@@ -752,10 +495,10 @@ impl Sidebar {
|
||||
))
|
||||
.when(self.channels_open, |this| {
|
||||
this.children(channels.into_iter().map(|(id, name, private)| {
|
||||
channel_row(id, name, private, active == Some(id), &community, cx)
|
||||
self.channel_row(id, name, private, active == Some(id), &community, cx)
|
||||
}))
|
||||
})
|
||||
.child(section_row(
|
||||
.child(self.section_row(
|
||||
"admins",
|
||||
"Admins",
|
||||
admins.len(),
|
||||
@@ -767,10 +510,10 @@ impl Sidebar {
|
||||
this.children(
|
||||
admins
|
||||
.iter()
|
||||
.map(|public_key| member_row("community-admin", *public_key, cx)),
|
||||
.map(|public_key| self.member_row("community-admin", *public_key, cx)),
|
||||
)
|
||||
})
|
||||
.child(section_row(
|
||||
.child(self.section_row(
|
||||
"members",
|
||||
"Members",
|
||||
members.len(),
|
||||
@@ -782,7 +525,7 @@ impl Sidebar {
|
||||
this.children(
|
||||
members
|
||||
.iter()
|
||||
.map(|public_key| member_row("member", *public_key, cx)),
|
||||
.map(|public_key| self.member_row("member", *public_key, cx)),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -806,16 +549,226 @@ impl Sidebar {
|
||||
.child(Scrollbar::vertical(&self.community_scroll))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn section_row(
|
||||
fn recent_communities(&self, cx: &App) -> Vec<Entity<Community>> {
|
||||
const LIMIT: usize = 3;
|
||||
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let communities = registry.read(cx).communities();
|
||||
let recent = AppSettings::get_recent_communities(cx);
|
||||
|
||||
let mut rows: Vec<Entity<Community>> = recent
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
communities
|
||||
.iter()
|
||||
.find(|community| community.read(cx).id().to_hex() == *id)
|
||||
})
|
||||
.take(LIMIT)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if rows.is_empty() {
|
||||
rows = communities.iter().take(LIMIT).cloned().collect();
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn rows_for(&self, tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||
match tab {
|
||||
SidebarTab::Recents => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let rooms = chat.read(cx).rooms(&RoomKind::Ongoing, cx);
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let community_count = registry.read(cx).communities().len();
|
||||
let communities = self.recent_communities(cx);
|
||||
|
||||
if communities.is_empty() && rooms.is_empty() {
|
||||
return vec![SidebarRow::Hint {
|
||||
text: "Nothing recent yet".into(),
|
||||
}];
|
||||
}
|
||||
|
||||
let mut rows = Vec::new();
|
||||
|
||||
if !communities.is_empty() {
|
||||
rows.push(SidebarRow::Section {
|
||||
label: "Communities".into(),
|
||||
count: community_count,
|
||||
});
|
||||
rows.extend(
|
||||
communities
|
||||
.into_iter()
|
||||
.map(|community| SidebarRow::Community { community }),
|
||||
);
|
||||
rows.push(SidebarRow::Action {
|
||||
label: "Show all communities".into(),
|
||||
tab: SidebarTab::Communities,
|
||||
});
|
||||
}
|
||||
|
||||
if !rooms.is_empty() {
|
||||
rows.push(SidebarRow::Section {
|
||||
label: "Chats".into(),
|
||||
count: rooms.len(),
|
||||
});
|
||||
rows.extend(
|
||||
rooms
|
||||
.into_iter()
|
||||
.take(5)
|
||||
.map(|room| SidebarRow::Room { room }),
|
||||
);
|
||||
rows.push(SidebarRow::Action {
|
||||
label: "Show all chats".into(),
|
||||
tab: SidebarTab::Chats,
|
||||
});
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
SidebarTab::Chats => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let chat = chat.read(cx);
|
||||
let messages = chat.rooms(&RoomKind::Ongoing, cx);
|
||||
|
||||
let mut rows = vec![SidebarRow::Section {
|
||||
label: "Chats".into(),
|
||||
count: messages.len(),
|
||||
}];
|
||||
|
||||
if messages.is_empty() {
|
||||
rows.push(SidebarRow::Hint {
|
||||
text: "No conversations yet".into(),
|
||||
});
|
||||
} else {
|
||||
rows.extend(messages.into_iter().map(|room| SidebarRow::Room { room }));
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
SidebarTab::Communities => {
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let communities = registry.read(cx).communities();
|
||||
|
||||
let mut rows = vec![SidebarRow::Section {
|
||||
label: "Communities".into(),
|
||||
count: communities.len(),
|
||||
}];
|
||||
|
||||
if communities.is_empty() {
|
||||
rows.push(SidebarRow::Hint {
|
||||
text: "No communities yet".into(),
|
||||
});
|
||||
} else {
|
||||
rows.extend(
|
||||
communities
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|community| SidebarRow::Community { community }),
|
||||
);
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_rows(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
rows: &[SidebarRow],
|
||||
cx: &Context<Sidebar>,
|
||||
) -> Vec<AnyElement> {
|
||||
rows.get(range.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(offset, row)| {
|
||||
let index = range.start + offset;
|
||||
|
||||
match row {
|
||||
SidebarRow::Section { label, count } => TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Section,
|
||||
label.clone(),
|
||||
)
|
||||
.count(*count)
|
||||
.into_any_element(),
|
||||
SidebarRow::Room { room } => {
|
||||
let name = room.read(cx).display_name(cx);
|
||||
let picture = room.read(cx).display_image(cx);
|
||||
let seed = room.read(cx).display_image_seed(cx);
|
||||
let created_at = room.read(cx).created_at.to_ago();
|
||||
let room_clone = room.clone();
|
||||
|
||||
let handler = cx.listener(move |this, _event, window, cx| {
|
||||
this.open_room(room_clone.clone(), window, cx);
|
||||
});
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Room,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.created_at(created_at)
|
||||
.on_click(handler)
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Community { community } => {
|
||||
let name = community.read(cx).name();
|
||||
let seed = community.read(cx).id().to_hex();
|
||||
let picture = community.read(cx).icon();
|
||||
let community = community.clone();
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Community,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.open_community(community.clone(), window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Action { label, tab } => {
|
||||
let tab = *tab;
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Action,
|
||||
label.clone(),
|
||||
)
|
||||
.icon(IconName::ArrowRight)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.select_tab(tab, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Hint { text } => TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Hint,
|
||||
text.clone(),
|
||||
)
|
||||
.into_any_element(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn section_row(
|
||||
&self,
|
||||
id: &'static str,
|
||||
label: &'static str,
|
||||
count: usize,
|
||||
open: bool,
|
||||
toggle: impl Fn(&mut Sidebar) + 'static,
|
||||
cx: &mut Context<Sidebar>,
|
||||
) -> AnyElement {
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.child(
|
||||
@@ -832,16 +785,17 @@ fn section_row(
|
||||
})),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_row(
|
||||
fn channel_row(
|
||||
&self,
|
||||
id: ChannelId,
|
||||
name: String,
|
||||
private: bool,
|
||||
selected: bool,
|
||||
community: &Entity<Community>,
|
||||
cx: &mut Context<Sidebar>,
|
||||
) -> AnyElement {
|
||||
) -> AnyElement {
|
||||
let community = community.clone();
|
||||
|
||||
div()
|
||||
@@ -868,9 +822,9 @@ fn channel_row(
|
||||
)
|
||||
.when(selected, |this| this.bg(cx.theme().ghost_element_active))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn member_row(prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
|
||||
fn member_row(&self, prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let person = persons.read(cx).get(&public_key, cx);
|
||||
|
||||
@@ -889,4 +843,98 @@ fn member_row(prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
|
||||
.picture(person.avatar()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for Sidebar {
|
||||
fn panel_id(&self) -> SharedString {
|
||||
"Sidebar".into()
|
||||
}
|
||||
|
||||
fn title(&self, _cx: &App) -> AnyElement {
|
||||
SharedString::from("Sidebar").into_any_element()
|
||||
}
|
||||
|
||||
fn closable(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn zoomable(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for Sidebar {}
|
||||
|
||||
impl Focusable for Sidebar {
|
||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Sidebar {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let logged_in = { nostr.read(cx).current_user().is_some() };
|
||||
|
||||
let community = self
|
||||
.community
|
||||
.clone()
|
||||
.and_then(|community| community.upgrade());
|
||||
|
||||
v_flex()
|
||||
.image_cache(retain_all("sidebar"))
|
||||
.size_full()
|
||||
.relative()
|
||||
.gap_2()
|
||||
.bg(cx.theme().surface_background)
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().border_variant)
|
||||
.when(!logged_in, |this| {
|
||||
this.relative()
|
||||
.child(title_bar_drag_handlers(
|
||||
div()
|
||||
.id("onboarding-drag")
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.h(TABBAR_HEIGHT)
|
||||
.w_full(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.justify_end()
|
||||
.gap_4()
|
||||
.p_4()
|
||||
.child(
|
||||
v_flex().child(div().font_semibold().child("Coop")).child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child("TODO"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("import-identity")
|
||||
.label("Import identity")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.h_8()
|
||||
.w_full()
|
||||
.on_click(|_, window, cx| {
|
||||
import::open(window, cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(title_bar_drag_handlers(self.render_user(cx), window, cx))
|
||||
.map(|this| match community {
|
||||
Some(community) => this.child(self.render_community(community, cx)),
|
||||
None => this.child(self.render_tabs(cx)),
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
use gpui::{App, InteractiveElement, IntoElement, ParentElement, Styled, Window, div, svg};
|
||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::{StyledExt, h_flex, title_bar_drag_handlers, v_flex};
|
||||
|
||||
use crate::dialogs::import;
|
||||
|
||||
const TITLE: &str = "Welcome to Coop!";
|
||||
const DESCRIPTION: &str = "Chat Freely, Stay Private on Nostr.";
|
||||
|
||||
pub(super) fn render(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.relative()
|
||||
.bg(cx.theme().surface_background)
|
||||
.child(title_bar_drag_handlers(
|
||||
div()
|
||||
.id("onboarding-drag")
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.h(TABBAR_HEIGHT)
|
||||
.w_full(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.justify_end()
|
||||
.gap_4()
|
||||
.p_4()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
svg()
|
||||
.path("brand/coop.svg")
|
||||
.size_8()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(
|
||||
v_flex().child(div().font_semibold().child(TITLE)).child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(DESCRIPTION),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
.child(
|
||||
Button::new("join-now")
|
||||
.label("Join now")
|
||||
.primary()
|
||||
.font_semibold()
|
||||
.h_8()
|
||||
.w_full(),
|
||||
)
|
||||
.child(
|
||||
Button::new("import-identity")
|
||||
.label("Import identity")
|
||||
.secondary()
|
||||
.font_semibold()
|
||||
.h_8()
|
||||
.w_full()
|
||||
.on_click(|_event, window, cx| import::open(window, cx)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user