wip
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="M12 7.75V12L15.5 15.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.75 4.75V8.75H6.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M3.25 15.0833C4.52169 18.676 7.95303 21.25 11.9864 21.25C17.1026 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1026 2.75 11.9864 2.75C8.14808 2.75 4.85497 5.08106 3.44947 8.40278" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 600 B |
@@ -46,7 +46,6 @@ setting_accessors! {
|
|||||||
pub nip4e: bool,
|
pub nip4e: bool,
|
||||||
pub trusted_relays: Vec<String>,
|
pub trusted_relays: Vec<String>,
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
pub expanded_sections: Option<Vec<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signer kind
|
/// Signer kind
|
||||||
@@ -131,10 +130,6 @@ pub struct Settings {
|
|||||||
|
|
||||||
/// Server for blossom media attachments
|
/// Server for blossom media attachments
|
||||||
pub file_server: Url,
|
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 {
|
impl Default for Settings {
|
||||||
@@ -147,7 +142,6 @@ impl Default for Settings {
|
|||||||
nip4e: false,
|
nip4e: false,
|
||||||
trusted_relays: vec![],
|
trusted_relays: vec![],
|
||||||
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
||||||
expanded_sections: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ pub enum IconName {
|
|||||||
Ship,
|
Ship,
|
||||||
Shield,
|
Shield,
|
||||||
Group,
|
Group,
|
||||||
|
History,
|
||||||
UserKey,
|
UserKey,
|
||||||
Upload,
|
Upload,
|
||||||
Usb,
|
Usb,
|
||||||
@@ -145,6 +146,7 @@ impl IconNamed for IconName {
|
|||||||
Self::Upload => "icons/upload.svg",
|
Self::Upload => "icons/upload.svg",
|
||||||
Self::Usb => "icons/usb.svg",
|
Self::Usb => "icons/usb.svg",
|
||||||
Self::Group => "icons/group.svg",
|
Self::Group => "icons/group.svg",
|
||||||
|
Self::History => "icons/history.svg",
|
||||||
Self::PanelLeft => "icons/panel-left.svg",
|
Self::PanelLeft => "icons/panel-left.svg",
|
||||||
Self::PanelLeftOpen => "icons/panel-left-open.svg",
|
Self::PanelLeftOpen => "icons/panel-left-open.svg",
|
||||||
Self::PanelRight => "icons/panel-right.svg",
|
Self::PanelRight => "icons/panel-right.svg",
|
||||||
|
|||||||
+199
-241
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::BTreeSet;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -13,44 +12,41 @@ use gpui::{
|
|||||||
UniformListScrollHandle, Window, div, px, retain_all, uniform_list,
|
UniformListScrollHandle, Window, div, px, retain_all, uniform_list,
|
||||||
};
|
};
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use settings::AppSettings;
|
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{ClosePanel, Panel, PanelEvent};
|
use ui::dock::{Panel, PanelEvent};
|
||||||
use ui::indicator::Indicator;
|
use ui::indicator::Indicator;
|
||||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||||
use ui::modal::ModalButtonProps;
|
|
||||||
use ui::nav_item::NavItem;
|
use ui::nav_item::NavItem;
|
||||||
use ui::scroll::Scrollbar;
|
use ui::scroll::Scrollbar;
|
||||||
use ui::{
|
use ui::{
|
||||||
Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension, h_flex,
|
Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, h_flex, title_bar_drag_handlers,
|
||||||
title_bar_drag_handlers, v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Command;
|
use crate::Command;
|
||||||
use crate::dialogs::screening;
|
|
||||||
|
|
||||||
|
mod tab;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
use tree::{SidebarRow, TreeSection};
|
use tab::{SidebarTab, TabBar};
|
||||||
|
use tree::SidebarRow;
|
||||||
pub(crate) use tree::{TreeRow, TreeRowKind};
|
pub(crate) use tree::{TreeRow, TreeRowKind};
|
||||||
|
|
||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handles: [UniformListScrollHandle; 3],
|
||||||
|
active_tab: SidebarTab,
|
||||||
/// Whether there are new chat requests
|
/// Whether there are new chat requests
|
||||||
new_requests: bool,
|
new_requests: bool,
|
||||||
/// Expanded tree sections
|
|
||||||
expanded: BTreeSet<TreeSection>,
|
|
||||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sidebar {
|
impl Sidebar {
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
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 chat = ChatRegistry::global(cx);
|
||||||
let communities = CommunityRegistry::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(
|
subscriptions.push(
|
||||||
cx.subscribe(&communities, |_this, _communities, event, _cx| {
|
cx.subscribe(&communities, |_this, _communities, event, _cx| {
|
||||||
if let CommunityEvent::Error(error) = event {
|
if let CommunityEvent::Error(error) = event {
|
||||||
@@ -79,192 +71,26 @@ impl Sidebar {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: UniformListScrollHandle::new(),
|
scroll_handles: [
|
||||||
|
UniformListScrollHandle::new(),
|
||||||
|
UniformListScrollHandle::new(),
|
||||||
|
UniformListScrollHandle::new(),
|
||||||
|
],
|
||||||
|
active_tab: SidebarTab::Recents,
|
||||||
new_requests: false,
|
new_requests: false,
|
||||||
expanded: load_expanded(cx),
|
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_section(&mut self, section: TreeSection, cx: &mut Context<Self>) {
|
fn select_tab(&mut self, tab: SidebarTab, cx: &mut Context<Self>) {
|
||||||
if !self.expanded.remove(§ion) {
|
if self.active_tab == tab {
|
||||||
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 {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.expanded = expanded;
|
|
||||||
|
self.active_tab = tab;
|
||||||
cx.notify();
|
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 {
|
fn render_user(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let current_user = nostr.read(cx).current_user();
|
let current_user = nostr.read(cx).current_user();
|
||||||
@@ -318,6 +144,16 @@ impl Sidebar {
|
|||||||
.child(name.clone())
|
.child(name.clone())
|
||||||
}))
|
}))
|
||||||
.separator()
|
.separator()
|
||||||
|
.menu_with_icon(
|
||||||
|
"Inbox",
|
||||||
|
IconName::Inbox,
|
||||||
|
Box::new(Command::ShowInbox),
|
||||||
|
)
|
||||||
|
.menu_with_icon(
|
||||||
|
"Search",
|
||||||
|
IconName::Search,
|
||||||
|
Box::new(Command::ShowSearch),
|
||||||
|
)
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Profile",
|
"Profile",
|
||||||
IconName::Profile,
|
IconName::Profile,
|
||||||
@@ -359,13 +195,118 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_expanded(cx: &App) -> BTreeSet<TreeSection> {
|
fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||||
let Some(keys) = AppSettings::get_expanded_sections(cx) else {
|
match tab {
|
||||||
return BTreeSet::from([TreeSection::Community, TreeSection::Messages]);
|
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()
|
let mut rows = vec![SidebarRow::Section {
|
||||||
.filter_map(|key| TreeSection::from_key(key.as_str()))
|
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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,86 +345,103 @@ impl Render for Sidebar {
|
|||||||
let loading = chat.read(cx).loading() && logged_in;
|
let loading = chat.read(cx).loading() && logged_in;
|
||||||
|
|
||||||
let sidebar = cx.entity().downgrade();
|
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()
|
v_flex()
|
||||||
.image_cache(retain_all("sidebar"))
|
.image_cache(retain_all("sidebar"))
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.relative()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.bg(cx.theme().surface_background)
|
.bg(cx.theme().surface_background)
|
||||||
.border_r_1()
|
.border_r_1()
|
||||||
.border_color(cx.theme().border_variant)
|
.border_color(cx.theme().border_variant)
|
||||||
.child(self.render_user(window, cx))
|
.child(self.render_user(window, cx))
|
||||||
.child(
|
.when(active_tab == SidebarTab::Chats, |this| {
|
||||||
v_flex()
|
this.child(
|
||||||
.px_2()
|
v_flex()
|
||||||
.gap_1()
|
.px_2()
|
||||||
.child(
|
.gap_1()
|
||||||
NavItem::new("nav-inbox", "Inbox", Icon::new(IconName::Inbox).small())
|
.child(
|
||||||
|
NavItem::new(
|
||||||
|
"nav-contacts",
|
||||||
|
"Contacts",
|
||||||
|
Icon::new(IconName::Book).small(),
|
||||||
|
)
|
||||||
.on_click(|_event, window, cx| {
|
.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| {
|
.child(
|
||||||
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
NavItem::new(
|
||||||
})
|
"nav-requests",
|
||||||
.on_click({
|
"Requests",
|
||||||
let sidebar = sidebar.clone();
|
Icon::new(IconName::Invite).small(),
|
||||||
move |_event, window, cx| {
|
)
|
||||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
.when(self.new_requests, |this| {
|
||||||
this.new_requests = false;
|
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
||||||
cx.notify();
|
})
|
||||||
}) {
|
.on_click({
|
||||||
log::error!("Failed to clear new requests: {error}");
|
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())
|
NavItem::new("nav-browse", "Browse", Icon::new(IconName::Compass).small())
|
||||||
.on_click(|_event, window, cx| {
|
.on_click(|_event, window, cx| {
|
||||||
window.dispatch_action(Box::new(Command::ShowBrowse), 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(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
.min_h_0()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.pb_12()
|
||||||
.child(
|
.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
"sidebar-tree",
|
active_tab.list_id(),
|
||||||
rows.len(),
|
rows.len(),
|
||||||
cx.processor(move |this, range, _window, cx| {
|
cx.processor(move |_this, range, _window, cx| {
|
||||||
this.render_rows(range, rows.as_slice(), cx)
|
render_rows(range, rows.as_slice(), cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.track_scroll(&self.scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.h_full()
|
.h_full()
|
||||||
.px_2(),
|
.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| {
|
.when(loading, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bottom_2()
|
.bottom_12()
|
||||||
.left_0()
|
.left_0()
|
||||||
.h_9()
|
.h_9()
|
||||||
.w_full()
|
.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::avatar::{Avatar, PixelAvatar};
|
||||||
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, h_flex};
|
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 {
|
pub enum SidebarRow {
|
||||||
Section { section: TreeSection, count: usize },
|
Section { label: SharedString, count: usize },
|
||||||
Room { room: Entity<Room> },
|
Room { room: Entity<Room> },
|
||||||
Community { community: Entity<Community> },
|
Community { community: Entity<Community> },
|
||||||
Hint { text: SharedString },
|
Hint { text: SharedString },
|
||||||
@@ -64,7 +34,6 @@ pub struct TreeRow {
|
|||||||
label: SharedString,
|
label: SharedString,
|
||||||
avatar: Option<SharedString>,
|
avatar: Option<SharedString>,
|
||||||
picture: Option<ImageSource>,
|
picture: Option<ImageSource>,
|
||||||
caret: Option<IconName>,
|
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
created_at: Option<SharedString>,
|
created_at: Option<SharedString>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
@@ -84,7 +53,6 @@ impl TreeRow {
|
|||||||
label: label.into(),
|
label: label.into(),
|
||||||
avatar: None,
|
avatar: None,
|
||||||
picture: None,
|
picture: None,
|
||||||
caret: None,
|
|
||||||
count: None,
|
count: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
selected: false,
|
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.
|
/// Sets the seed for the row's generated avatar.
|
||||||
pub fn avatar(mut self, seed: impl Into<SharedString>) -> Self {
|
pub fn avatar(mut self, seed: impl Into<SharedString>) -> Self {
|
||||||
self.avatar = Some(seed.into());
|
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| {
|
.when_some(self.on_click, |this, handler| {
|
||||||
this.cursor_pointer()
|
this.cursor_pointer()
|
||||||
.when(!is_section, |this| {
|
.when(!is_section, |this| {
|
||||||
|
|||||||
Reference in New Issue
Block a user