wip
This commit is contained in:
@@ -46,7 +46,6 @@ setting_accessors! {
|
||||
pub nip4e: bool,
|
||||
pub trusted_relays: Vec<String>,
|
||||
pub file_server: Url,
|
||||
pub expanded_sections: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Signer kind
|
||||
@@ -131,10 +130,6 @@ pub struct Settings {
|
||||
|
||||
/// Server for blossom media attachments
|
||||
pub file_server: Url,
|
||||
|
||||
/// Expanded sidebar tree sections; `None` means the default sections
|
||||
#[serde(default)]
|
||||
pub expanded_sections: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -147,7 +142,6 @@ impl Default for Settings {
|
||||
nip4e: false,
|
||||
trusted_relays: vec![],
|
||||
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
||||
expanded_sections: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ pub enum IconName {
|
||||
Ship,
|
||||
Shield,
|
||||
Group,
|
||||
History,
|
||||
UserKey,
|
||||
Upload,
|
||||
Usb,
|
||||
@@ -145,6 +146,7 @@ impl IconNamed for IconName {
|
||||
Self::Upload => "icons/upload.svg",
|
||||
Self::Usb => "icons/usb.svg",
|
||||
Self::Group => "icons/group.svg",
|
||||
Self::History => "icons/history.svg",
|
||||
Self::PanelLeft => "icons/panel-left.svg",
|
||||
Self::PanelLeftOpen => "icons/panel-left-open.svg",
|
||||
Self::PanelRight => "icons/panel-right.svg",
|
||||
|
||||
+199
-241
@@ -1,4 +1,3 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -13,44 +12,41 @@ use gpui::{
|
||||
UniformListScrollHandle, Window, div, px, retain_all, uniform_list,
|
||||
};
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock::{ClosePanel, Panel, PanelEvent};
|
||||
use ui::dock::{Panel, PanelEvent};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::nav_item::NavItem;
|
||||
use ui::scroll::Scrollbar;
|
||||
use ui::{
|
||||
Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension, h_flex,
|
||||
title_bar_drag_handlers, v_flex,
|
||||
Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, h_flex, title_bar_drag_handlers,
|
||||
v_flex,
|
||||
};
|
||||
|
||||
use crate::Command;
|
||||
use crate::dialogs::screening;
|
||||
|
||||
mod tab;
|
||||
mod tree;
|
||||
|
||||
use tree::{SidebarRow, TreeSection};
|
||||
use tab::{SidebarTab, TabBar};
|
||||
use tree::SidebarRow;
|
||||
pub(crate) use tree::{TreeRow, TreeRowKind};
|
||||
|
||||
pub struct Sidebar {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
scroll_handles: [UniformListScrollHandle; 3],
|
||||
active_tab: SidebarTab,
|
||||
/// Whether there are new chat requests
|
||||
new_requests: bool,
|
||||
/// Expanded tree sections
|
||||
expanded: BTreeSet<TreeSection>,
|
||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||
}
|
||||
|
||||
impl Sidebar {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let settings = AppSettings::global(cx).read(cx).entity().clone();
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let communities = CommunityRegistry::global(cx);
|
||||
|
||||
@@ -65,10 +61,6 @@ impl Sidebar {
|
||||
}),
|
||||
);
|
||||
|
||||
subscriptions.push(cx.observe(&settings, move |this, _settings, cx| {
|
||||
this.restore_state(cx);
|
||||
}));
|
||||
|
||||
subscriptions.push(
|
||||
cx.subscribe(&communities, |_this, _communities, event, _cx| {
|
||||
if let CommunityEvent::Error(error) = event {
|
||||
@@ -79,192 +71,26 @@ impl Sidebar {
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
scroll_handles: [
|
||||
UniformListScrollHandle::new(),
|
||||
UniformListScrollHandle::new(),
|
||||
UniformListScrollHandle::new(),
|
||||
],
|
||||
active_tab: SidebarTab::Recents,
|
||||
new_requests: false,
|
||||
expanded: load_expanded(cx),
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_section(&mut self, section: TreeSection, cx: &mut Context<Self>) {
|
||||
if !self.expanded.remove(§ion) {
|
||||
self.expanded.insert(section);
|
||||
}
|
||||
|
||||
self.save_expanded(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn is_expanded(&self, section: TreeSection) -> bool {
|
||||
self.expanded.contains(§ion)
|
||||
}
|
||||
|
||||
fn restore_state(&mut self, cx: &mut Context<Self>) {
|
||||
let expanded = load_expanded(cx);
|
||||
if self.expanded == expanded {
|
||||
fn select_tab(&mut self, tab: SidebarTab, cx: &mut Context<Self>) {
|
||||
if self.active_tab == tab {
|
||||
return;
|
||||
}
|
||||
self.expanded = expanded;
|
||||
|
||||
self.active_tab = tab;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn save_expanded(&self, cx: &mut App) {
|
||||
let keys = self
|
||||
.expanded
|
||||
.iter()
|
||||
.map(|section| section.key().to_string())
|
||||
.collect();
|
||||
AppSettings::update_expanded_sections(Some(keys), cx);
|
||||
}
|
||||
|
||||
fn tree_rows(&self, cx: &App) -> Vec<SidebarRow> {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let chat = chat.read(cx);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
|
||||
let registry = CommunityRegistry::global(cx);
|
||||
let communities = registry.read(cx).communities();
|
||||
|
||||
rows.push(SidebarRow::Section {
|
||||
section: TreeSection::Community,
|
||||
count: communities.len(),
|
||||
});
|
||||
|
||||
if self.is_expanded(TreeSection::Community) {
|
||||
if communities.is_empty() {
|
||||
rows.push(SidebarRow::Hint {
|
||||
text: "No communities yet".into(),
|
||||
});
|
||||
} else {
|
||||
rows.extend(
|
||||
communities
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|community| SidebarRow::Community { community }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let messages = chat.rooms(&RoomKind::Ongoing, cx);
|
||||
rows.push(SidebarRow::Section {
|
||||
section: TreeSection::Messages,
|
||||
count: messages.len(),
|
||||
});
|
||||
|
||||
if self.is_expanded(TreeSection::Messages) {
|
||||
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
|
||||
}
|
||||
|
||||
fn render_rows(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
rows: &[SidebarRow],
|
||||
cx: &Context<Self>,
|
||||
) -> Vec<AnyElement> {
|
||||
rows.get(range.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(offset, row)| {
|
||||
let index = range.start + offset;
|
||||
|
||||
match row {
|
||||
SidebarRow::Section { section, count } => {
|
||||
let section = *section;
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Section,
|
||||
section.label(),
|
||||
)
|
||||
.caret(if self.is_expanded(section) {
|
||||
IconName::CaretDown
|
||||
} else {
|
||||
IconName::CaretRight
|
||||
})
|
||||
.count(*count)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.toggle_section(section, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Room { room } => {
|
||||
let public_key = room.read(cx).display_member(cx).public_key();
|
||||
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 kind = room.read(cx).kind;
|
||||
let created_at = room.read(cx).created_at.to_ago();
|
||||
let room_clone = room.clone();
|
||||
|
||||
let handler = cx.listener(move |_this, _event, window, cx| {
|
||||
ChatRegistry::global(cx).update(cx, |chat, cx| {
|
||||
chat.emit_room(&room_clone, window, cx);
|
||||
});
|
||||
|
||||
if kind != RoomKind::Ongoing && AppSettings::get_screening(cx) {
|
||||
let screening = screening::init(public_key, window, cx);
|
||||
|
||||
window.open_modal(cx, move |this, _window, _cx| {
|
||||
this.confirm()
|
||||
.child(screening.clone())
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text("Ignore")
|
||||
.ok_text("Response"),
|
||||
)
|
||||
.on_cancel(move |_event, window, cx| {
|
||||
window.dispatch_action(Box::new(ClosePanel), cx);
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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 community = community.read(cx);
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Community,
|
||||
community.name(),
|
||||
)
|
||||
.avatar(community.id().to_hex())
|
||||
.picture(community.icon())
|
||||
.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 render_user(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let current_user = nostr.read(cx).current_user();
|
||||
@@ -318,6 +144,16 @@ impl Sidebar {
|
||||
.child(name.clone())
|
||||
}))
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Inbox",
|
||||
IconName::Inbox,
|
||||
Box::new(Command::ShowInbox),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Search",
|
||||
IconName::Search,
|
||||
Box::new(Command::ShowSearch),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Profile",
|
||||
IconName::Profile,
|
||||
@@ -359,13 +195,118 @@ impl Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_expanded(cx: &App) -> BTreeSet<TreeSection> {
|
||||
let Some(keys) = AppSettings::get_expanded_sections(cx) else {
|
||||
return BTreeSet::from([TreeSection::Community, TreeSection::Messages]);
|
||||
};
|
||||
fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||
match tab {
|
||||
SidebarTab::Recents => vec![SidebarRow::Hint {
|
||||
text: "Nothing recent yet".into(),
|
||||
}],
|
||||
SidebarTab::Chats => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let chat = chat.read(cx);
|
||||
let messages = chat.rooms(&RoomKind::Ongoing, cx);
|
||||
|
||||
keys.iter()
|
||||
.filter_map(|key| TreeSection::from_key(key.as_str()))
|
||||
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| {
|
||||
ChatRegistry::global(cx).update(cx, |chat, cx| {
|
||||
chat.emit_room(&room_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 community = community.read(cx);
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Community,
|
||||
community.name(),
|
||||
)
|
||||
.avatar(community.id().to_hex())
|
||||
.picture(community.icon())
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Hint { text } => TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Hint,
|
||||
text.clone(),
|
||||
)
|
||||
.into_any_element(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -404,86 +345,103 @@ impl Render for Sidebar {
|
||||
let loading = chat.read(cx).loading() && logged_in;
|
||||
|
||||
let sidebar = cx.entity().downgrade();
|
||||
let rows = Rc::new(self.tree_rows(cx));
|
||||
let active_tab = self.active_tab;
|
||||
let rows = Rc::new(rows_for(active_tab, cx));
|
||||
let scroll_handle = &self.scroll_handles[active_tab.index()];
|
||||
|
||||
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)
|
||||
.child(self.render_user(window, cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
NavItem::new("nav-inbox", "Inbox", Icon::new(IconName::Inbox).small())
|
||||
.when(active_tab == SidebarTab::Chats, |this| {
|
||||
this.child(
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
NavItem::new(
|
||||
"nav-contacts",
|
||||
"Contacts",
|
||||
Icon::new(IconName::Book).small(),
|
||||
)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::ShowInbox), cx)
|
||||
window.dispatch_action(Box::new(Command::ShowContactList), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new(
|
||||
"nav-requests",
|
||||
"Requests",
|
||||
Icon::new(IconName::Invite).small(),
|
||||
)
|
||||
.when(self.new_requests, |this| {
|
||||
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
||||
})
|
||||
.on_click({
|
||||
let sidebar = sidebar.clone();
|
||||
move |_event, window, cx| {
|
||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||
this.new_requests = false;
|
||||
cx.notify();
|
||||
}) {
|
||||
log::error!("Failed to clear new requests: {error}");
|
||||
.child(
|
||||
NavItem::new(
|
||||
"nav-requests",
|
||||
"Requests",
|
||||
Icon::new(IconName::Invite).small(),
|
||||
)
|
||||
.when(self.new_requests, |this| {
|
||||
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
||||
})
|
||||
.on_click({
|
||||
let sidebar = sidebar.clone();
|
||||
move |_event, window, cx| {
|
||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||
this.new_requests = false;
|
||||
cx.notify();
|
||||
}) {
|
||||
log::error!("Failed to clear new requests: {error}");
|
||||
}
|
||||
window.dispatch_action(Box::new(Command::ShowRequests), cx);
|
||||
}
|
||||
window.dispatch_action(Box::new(Command::ShowRequests), cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(active_tab == SidebarTab::Communities, |this| {
|
||||
this.child(
|
||||
v_flex().px_2().gap_1().child(
|
||||
NavItem::new("nav-browse", "Browse", Icon::new(IconName::Compass).small())
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("nav-search", "Search", Icon::new(IconName::Search).small())
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::ShowSearch), cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.gap_1()
|
||||
.pb_12()
|
||||
.child(
|
||||
uniform_list(
|
||||
"sidebar-tree",
|
||||
active_tab.list_id(),
|
||||
rows.len(),
|
||||
cx.processor(move |this, range, _window, cx| {
|
||||
this.render_rows(range, rows.as_slice(), cx)
|
||||
cx.processor(move |_this, range, _window, cx| {
|
||||
render_rows(range, rows.as_slice(), cx)
|
||||
}),
|
||||
)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.track_scroll(scroll_handle)
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.px_2(),
|
||||
)
|
||||
.child(Scrollbar::vertical(&self.scroll_handle)),
|
||||
.child(Scrollbar::vertical(scroll_handle)),
|
||||
)
|
||||
.child(TabBar::new(active_tab).on_select({
|
||||
let sidebar = sidebar.clone();
|
||||
move |tab, _window, cx| {
|
||||
if let Err(error) = sidebar.update(cx, |this, cx| this.select_tab(tab, cx)) {
|
||||
log::error!("Failed to switch sidebar tab: {error}");
|
||||
}
|
||||
}
|
||||
}))
|
||||
.when(loading, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.bottom_2()
|
||||
.bottom_12()
|
||||
.left_0()
|
||||
.h_9()
|
||||
.w_full()
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{App, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::{IconName, Selectable, h_flex};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SidebarTab {
|
||||
Recents,
|
||||
Chats,
|
||||
Communities,
|
||||
}
|
||||
|
||||
impl SidebarTab {
|
||||
pub const ALL: [SidebarTab; 3] = [Self::Recents, Self::Chats, Self::Communities];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Recents => "Recents",
|
||||
Self::Chats => "Chats",
|
||||
Self::Communities => "Communities",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(self) -> IconName {
|
||||
match self {
|
||||
Self::Recents => IconName::History,
|
||||
Self::Chats => IconName::Message,
|
||||
Self::Communities => IconName::Group,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Recents => "sidebar-recents",
|
||||
Self::Chats => "sidebar-chats",
|
||||
Self::Communities => "sidebar-communities",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index(self) -> usize {
|
||||
match self {
|
||||
Self::Recents => 0,
|
||||
Self::Chats => 1,
|
||||
Self::Communities => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct TabBar {
|
||||
active: SidebarTab,
|
||||
on_select: Option<Rc<dyn Fn(SidebarTab, &mut Window, &mut App)>>,
|
||||
}
|
||||
|
||||
impl TabBar {
|
||||
pub fn new(active: SidebarTab) -> Self {
|
||||
Self {
|
||||
active,
|
||||
on_select: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_select(
|
||||
mut self,
|
||||
handler: impl Fn(SidebarTab, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_select = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for TabBar {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let Self { active, on_select } = self;
|
||||
|
||||
div().absolute().bottom_2().left_0().w_full().px_2().child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1()
|
||||
.gap_1()
|
||||
.rounded(cx.theme().radius_lg)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.when(cx.theme().shadow, |this| this.shadow_md())
|
||||
.children(SidebarTab::ALL.into_iter().map(|tab| {
|
||||
let on_select = on_select.clone();
|
||||
|
||||
Button::new(format!("tab-{}", tab.list_id()))
|
||||
.icon(tab.icon())
|
||||
.ghost()
|
||||
.flex_1()
|
||||
.selected(tab == active)
|
||||
.tooltip(tab.label())
|
||||
.on_click(move |_event, window, cx| {
|
||||
if let Some(on_select) = on_select.as_ref() {
|
||||
on_select(tab, window, cx);
|
||||
}
|
||||
})
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,38 +12,8 @@ use theme::ActiveTheme;
|
||||
use ui::avatar::{Avatar, PixelAvatar};
|
||||
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, h_flex};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum TreeSection {
|
||||
Community,
|
||||
Messages,
|
||||
}
|
||||
|
||||
impl TreeSection {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Community => "Community",
|
||||
Self::Messages => "Messages",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(self) -> &'static str {
|
||||
match self {
|
||||
Self::Community => "community",
|
||||
Self::Messages => "messages",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_key(key: &str) -> Option<Self> {
|
||||
match key {
|
||||
"community" => Some(Self::Community),
|
||||
"messages" => Some(Self::Messages),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SidebarRow {
|
||||
Section { section: TreeSection, count: usize },
|
||||
Section { label: SharedString, count: usize },
|
||||
Room { room: Entity<Room> },
|
||||
Community { community: Entity<Community> },
|
||||
Hint { text: SharedString },
|
||||
@@ -64,7 +34,6 @@ pub struct TreeRow {
|
||||
label: SharedString,
|
||||
avatar: Option<SharedString>,
|
||||
picture: Option<ImageSource>,
|
||||
caret: Option<IconName>,
|
||||
count: Option<usize>,
|
||||
created_at: Option<SharedString>,
|
||||
selected: bool,
|
||||
@@ -84,7 +53,6 @@ impl TreeRow {
|
||||
label: label.into(),
|
||||
avatar: None,
|
||||
picture: None,
|
||||
caret: None,
|
||||
count: None,
|
||||
created_at: None,
|
||||
selected: false,
|
||||
@@ -92,11 +60,6 @@ impl TreeRow {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn caret(mut self, caret: IconName) -> Self {
|
||||
self.caret = Some(caret);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the seed for the row's generated avatar.
|
||||
pub fn avatar(mut self, seed: impl Into<SharedString>) -> Self {
|
||||
self.avatar = Some(seed.into());
|
||||
@@ -221,9 +184,6 @@ impl RenderOnce for TreeRow {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(self.caret, |this, caret| {
|
||||
this.child(Icon::new(caret).small().text_color(cx.theme().icon_muted))
|
||||
})
|
||||
.when_some(self.on_click, |this, handler| {
|
||||
this.cursor_pointer()
|
||||
.when(!is_section, |this| {
|
||||
|
||||
Reference in New Issue
Block a user