feat: replace sidebar tabs with a single collapsible list
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::avatar::Avatar;
|
||||
use crate::button::Button;
|
||||
use crate::menu::PopupMenu;
|
||||
use crate::nav::Nav;
|
||||
use crate::nav_item::NavItem;
|
||||
use crate::popover::{Popover, PopoverState};
|
||||
|
||||
/// Builds the items of a popup menu on each render.
|
||||
@@ -44,6 +45,8 @@ impl DropdownMenu for Avatar {}
|
||||
|
||||
impl DropdownMenu for Nav {}
|
||||
|
||||
impl DropdownMenu for NavItem {}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {
|
||||
id: ElementId,
|
||||
|
||||
@@ -2,23 +2,26 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
AnyElement, App, ClickEvent, ElementId, InteractiveElement, IntoElement, ParentElement,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
|
||||
div,
|
||||
AnyElement, App, ClickEvent, ElementId, InteractiveElement, Interactivity, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement,
|
||||
Styled, Window, div,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
use crate::{StyledExt, h_flex};
|
||||
use crate::{Selectable, StyledExt, h_flex};
|
||||
|
||||
/// A single navigation entry in a sidebar.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct NavItem {
|
||||
id: ElementId,
|
||||
interactivity: Interactivity,
|
||||
style: StyleRefinement,
|
||||
icon: AnyElement,
|
||||
label: SharedString,
|
||||
suffix: Option<AnyElement>,
|
||||
selected: bool,
|
||||
clickable: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
}
|
||||
|
||||
@@ -28,16 +31,30 @@ impl NavItem {
|
||||
label: impl Into<SharedString>,
|
||||
icon: impl IntoElement,
|
||||
) -> Self {
|
||||
let id = id.into();
|
||||
let mut interactivity = Interactivity::default();
|
||||
interactivity.element_id = Some(id.clone());
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
id,
|
||||
interactivity,
|
||||
style: StyleRefinement::default(),
|
||||
icon: icon.into_any_element(),
|
||||
label: label.into(),
|
||||
suffix: None,
|
||||
selected: false,
|
||||
clickable: false,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the item as clickable even when it has no `on_click` handler, so it
|
||||
/// still shows the hover affordance. Used by items whose click opens a menu.
|
||||
pub fn clickable(mut self, clickable: bool) -> Self {
|
||||
self.clickable = clickable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
|
||||
self.suffix = Some(suffix.into_any_element());
|
||||
self
|
||||
@@ -52,6 +69,23 @@ impl NavItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Selectable for NavItem {
|
||||
fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
fn is_selected(&self) -> bool {
|
||||
self.selected
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for NavItem {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for NavItem {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
@@ -60,7 +94,7 @@ impl Styled for NavItem {
|
||||
|
||||
impl RenderOnce for NavItem {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let clickable = self.on_click.is_some();
|
||||
let clickable = self.on_click.is_some() || self.clickable;
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
@@ -86,6 +120,9 @@ impl RenderOnce for NavItem {
|
||||
.when(clickable, |this| {
|
||||
this.cursor_pointer()
|
||||
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||
.when(self.selected, |this| {
|
||||
this.bg(cx.theme().ghost_element_active)
|
||||
})
|
||||
})
|
||||
.when_some(self.on_click, |this, handler| {
|
||||
this.on_click(move |event, window, cx| handler(event, window, cx))
|
||||
|
||||
+193
-189
@@ -8,10 +8,10 @@ use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||
use common::TimestampExt;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext, ClipboardItem, Context, Div, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString,
|
||||
Stateful, Styled, StyledImage, Subscription, Task, UniformListScrollHandle, WeakEntity, Window,
|
||||
div, img, px, relative, retain_all, uniform_list,
|
||||
Anchor, AnyElement, App, AppContext, ClipboardItem, Context, Div, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render,
|
||||
SharedString, Stateful, Styled, StyledImage, Subscription, Task, UniformListScrollHandle,
|
||||
WeakEntity, Window, div, img, px, relative, retain_all, uniform_list,
|
||||
};
|
||||
use nip29::{Group, GroupKey, GroupsEvent, GroupsRegistry, Membership};
|
||||
use nostr_sdk::prelude::*;
|
||||
@@ -30,8 +30,6 @@ use ui::nav::Nav;
|
||||
use ui::nav_item::NavItem;
|
||||
use ui::notification::Notification;
|
||||
use ui::scroll::Scrollbar;
|
||||
use ui::tab::Tab;
|
||||
use ui::tab::tab_bar::TabBar;
|
||||
use ui::{
|
||||
Disableable, Icon, IconName, Selectable, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING,
|
||||
WindowExtension, h_flex, title_bar_drag_handlers, v_flex,
|
||||
@@ -40,19 +38,34 @@ use ui::{
|
||||
use crate::Command;
|
||||
use crate::dialogs::{import, profile, screening};
|
||||
|
||||
mod tab;
|
||||
mod utils;
|
||||
|
||||
use tab::SidebarTab;
|
||||
pub(crate) use utils::pick_banner;
|
||||
|
||||
/// A collapsible section of the sidebar's main list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ListSection {
|
||||
Chats,
|
||||
Groups,
|
||||
}
|
||||
|
||||
impl ListSection {
|
||||
fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Chats => "Chats",
|
||||
Self::Groups => "Groups",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SidebarRow {
|
||||
Section(ListSection),
|
||||
Room { room: Entity<Room> },
|
||||
Group { group: Entity<Group> },
|
||||
Contact { public_key: PublicKey },
|
||||
}
|
||||
|
||||
/// A collapsible section of the group view that replaces the tab list.
|
||||
/// A collapsible section of the group view.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum GroupSection {
|
||||
Admins,
|
||||
@@ -79,15 +92,14 @@ enum GroupRow {
|
||||
|
||||
pub struct Sidebar {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handles: [UniformListScrollHandle; 2],
|
||||
list_scroll: UniformListScrollHandle,
|
||||
dock: WeakEntity<DockArea>,
|
||||
/// Random background shown behind the signed-out screen.
|
||||
banner: SharedString,
|
||||
active_tab: SidebarTab,
|
||||
|
||||
/// The group whose information the sidebar is browsing, if any.
|
||||
group: Option<WeakEntity<Group>>,
|
||||
/// Whether the sidebar is showing the group's information instead of its tabs.
|
||||
/// Whether the sidebar is showing the group's information instead of its list.
|
||||
group_open: bool,
|
||||
/// Scroll state of the group's member list.
|
||||
group_scroll: UniformListScrollHandle,
|
||||
@@ -96,7 +108,12 @@ pub struct Sidebar {
|
||||
/// Whether the group view's members section is expanded.
|
||||
members_open: bool,
|
||||
|
||||
/// Whether the inbox list is showing contacts to start a chat with.
|
||||
/// Whether the main list's chats section is expanded.
|
||||
chats_open: bool,
|
||||
/// Whether the main list's groups section is expanded.
|
||||
groups_open: bool,
|
||||
|
||||
/// Whether the main list is showing contacts to start a chat with.
|
||||
contacts_open: bool,
|
||||
/// The signed-in user's contacts, loaded when the contact picker opens.
|
||||
contacts: Option<Vec<PublicKey>>,
|
||||
@@ -105,11 +122,10 @@ pub struct Sidebar {
|
||||
/// Scroll state of the contact picker's list.
|
||||
contacts_scroll: UniformListScrollHandle,
|
||||
|
||||
/// Whether the inbox list is showing chat requests instead of rooms.
|
||||
/// Whether the main list is showing chat requests instead of chats and groups.
|
||||
requests_open: bool,
|
||||
/// Scroll state of the requests list.
|
||||
requests_scroll: UniformListScrollHandle,
|
||||
new_requests: bool,
|
||||
requests_scroll: UniformListScrollHandle,
|
||||
|
||||
tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
|
||||
_subscriptions: SmallVec<[Subscription; 4]>,
|
||||
@@ -159,18 +175,16 @@ impl Sidebar {
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handles: [
|
||||
UniformListScrollHandle::new(),
|
||||
UniformListScrollHandle::new(),
|
||||
],
|
||||
list_scroll: UniformListScrollHandle::new(),
|
||||
dock,
|
||||
banner: pick_banner(),
|
||||
active_tab: SidebarTab::Inbox,
|
||||
group: None,
|
||||
group_open: false,
|
||||
group_scroll: UniformListScrollHandle::new(),
|
||||
admins_open: true,
|
||||
members_open: true,
|
||||
chats_open: true,
|
||||
groups_open: true,
|
||||
contacts_open: false,
|
||||
contacts: None,
|
||||
selected_contacts: HashSet::new(),
|
||||
@@ -183,7 +197,7 @@ impl Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the group view, replacing the tab list.
|
||||
/// Show the group view, replacing the main list.
|
||||
fn show_group(&mut self, group: &Entity<Group>, cx: &mut Context<Self>) {
|
||||
self.group = Some(group.downgrade());
|
||||
self.group_open = true;
|
||||
@@ -214,7 +228,7 @@ impl Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
/// Leave the group view, returning the sidebar to its tab list.
|
||||
/// Leave the group view, returning the sidebar to its main list.
|
||||
fn hide_group(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.group_open {
|
||||
return;
|
||||
@@ -224,16 +238,6 @@ impl Sidebar {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_tab(&mut self, tab: SidebarTab, cx: &mut Context<Self>) {
|
||||
if self.active_tab == tab {
|
||||
return;
|
||||
}
|
||||
self.active_tab = tab;
|
||||
self.close_contacts();
|
||||
self.close_requests();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Leave the contact picker and clear its selection.
|
||||
fn close_contacts(&mut self) {
|
||||
self.contacts_open = false;
|
||||
@@ -508,26 +512,20 @@ impl Sidebar {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_tabs(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let sidebar = cx.entity().downgrade();
|
||||
let active_tab = self.active_tab;
|
||||
let requests_open = self.requests_open && active_tab == SidebarTab::Inbox;
|
||||
let contacts_open = self.contacts_open && active_tab == SidebarTab::Inbox && !requests_open;
|
||||
let rows = Rc::new(self.rows_for(active_tab, cx));
|
||||
fn render_list(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let requests_open = self.requests_open;
|
||||
let contacts_open = self.contacts_open && !requests_open;
|
||||
let rows = Rc::new(self.rows_for(cx));
|
||||
|
||||
// Whether to show the create button for the active tab
|
||||
// Whether to show the create button for the contact picker
|
||||
let create_button = contacts_open && !self.selected_contacts.is_empty();
|
||||
|
||||
let (list_id, scroll_handle, list_title) = if contacts_open {
|
||||
("sidebar-contacts", &self.contacts_scroll, "Contacts")
|
||||
("sidebar-contacts", &self.contacts_scroll, Some("Contacts"))
|
||||
} else if requests_open {
|
||||
("sidebar-requests", &self.requests_scroll, "Requests")
|
||||
("sidebar-requests", &self.requests_scroll, Some("Requests"))
|
||||
} else {
|
||||
(
|
||||
active_tab.list_id(),
|
||||
&self.scroll_handles[active_tab.index()],
|
||||
active_tab.list_title(),
|
||||
)
|
||||
("sidebar-list", &self.list_scroll, None)
|
||||
};
|
||||
|
||||
let request_count = ChatRegistry::global(cx)
|
||||
@@ -541,127 +539,93 @@ impl Sidebar {
|
||||
.relative()
|
||||
.gap_2()
|
||||
.child(
|
||||
div().px_2().child(
|
||||
TabBar::new("sidebar-tabs")
|
||||
.segmented(true)
|
||||
.selected_index(active_tab.index())
|
||||
.child(Tab::new().label(SidebarTab::Inbox.label()))
|
||||
.child(Tab::new().label(SidebarTab::Groups.label()))
|
||||
.on_click({
|
||||
let sidebar = sidebar.clone();
|
||||
move |index, _window, cx| {
|
||||
let Some(tab) = SidebarTab::ALL.get(*index).copied() else {
|
||||
return;
|
||||
};
|
||||
if let Err(error) =
|
||||
sidebar.update(cx, |this, cx| this.select_tab(tab, cx))
|
||||
{
|
||||
log::error!("Failed to switch sidebar tab: {error}");
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.map(|this| match active_tab {
|
||||
SidebarTab::Inbox => this.child(
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
NavItem::new("chat", "New Chat", Icon::new(IconName::Message).small())
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::NewChat), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("reqs", "Requests", Icon::new(IconName::Invite).small())
|
||||
.when(request_count > 0, |this| {
|
||||
this.suffix(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.child(
|
||||
h_flex()
|
||||
.py_0p5()
|
||||
.px_1()
|
||||
.min_w_6()
|
||||
.justify_center()
|
||||
.text_size(px(10.))
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.child(SharedString::from(
|
||||
request_count.to_string(),
|
||||
)),
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
NavItem::new("new", "New", Icon::new(IconName::PlusCircle).small())
|
||||
.clickable(true)
|
||||
.dropdown_menu_with_anchor(Anchor::TopRight, |menu, _window, _cx| {
|
||||
menu.menu_with_icon(
|
||||
"New Chat",
|
||||
IconName::Message,
|
||||
Box::new(Command::NewChat),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"New Group",
|
||||
IconName::Group,
|
||||
Box::new(Command::NewGroup),
|
||||
)
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Join Group",
|
||||
IconName::Door,
|
||||
Box::new(Command::JoinGroup),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("browse", "Browse", Icon::new(IconName::Compass).small())
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("reqs", "Requests", Icon::new(IconName::Invite).small())
|
||||
.when(request_count > 0, |this| {
|
||||
this.suffix(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.child(
|
||||
h_flex()
|
||||
.py_0p5()
|
||||
.px_1()
|
||||
.min_w_6()
|
||||
.justify_center()
|
||||
.text_size(px(10.))
|
||||
.text_color(cx.theme().text_muted)
|
||||
.text_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.child(SharedString::from(
|
||||
request_count.to_string(),
|
||||
)),
|
||||
)
|
||||
.when(self.new_requests && !requests_open, |this| {
|
||||
this.child(
|
||||
div().size_1().rounded_full().bg(cx.theme().cursor),
|
||||
)
|
||||
.when(self.new_requests && !requests_open, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.size_1()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().cursor),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(requests_open, |this| {
|
||||
this.bg(cx.theme().ghost_element_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.toggle_requests(cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("contacts", "Contacts", Icon::new(IconName::Book).small())
|
||||
.when(contacts_open, |this| {
|
||||
this.bg(cx.theme().ghost_element_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.toggle_contacts(window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
SidebarTab::Groups => this.child(
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
NavItem::new(
|
||||
"new-group",
|
||||
"New Group",
|
||||
Icon::new(IconName::Group).small(),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::NewGroup), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new(
|
||||
"join-group",
|
||||
"Join Group",
|
||||
Icon::new(IconName::Door).small(),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::JoinGroup), cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("browse", "Browse", Icon::new(IconName::Compass).small())
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
||||
}),
|
||||
),
|
||||
),
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(list_title),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(requests_open, |this| {
|
||||
this.bg(cx.theme().ghost_element_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.toggle_requests(cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
NavItem::new("contacts", "Contacts", Icon::new(IconName::Book).small())
|
||||
.when(contacts_open, |this| {
|
||||
this.bg(cx.theme().ghost_element_active)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.toggle_contacts(window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
.when_some(list_title, |this, list_title| {
|
||||
this.child(
|
||||
div()
|
||||
.px_4()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(list_title),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.min_h_0()
|
||||
@@ -720,42 +684,54 @@ impl Sidebar {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn rows_for(&self, tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||
match tab {
|
||||
SidebarTab::Inbox if self.contacts_open => self
|
||||
fn rows_for(&self, cx: &App) -> Vec<SidebarRow> {
|
||||
if self.contacts_open {
|
||||
return self
|
||||
.contacts
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|public_key| SidebarRow::Contact { public_key })
|
||||
.collect(),
|
||||
SidebarTab::Inbox if self.requests_open => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
chat.read(cx)
|
||||
.rooms(&RoomKind::Request, cx)
|
||||
.into_iter()
|
||||
.map(|room| SidebarRow::Room { room })
|
||||
.collect()
|
||||
}
|
||||
SidebarTab::Inbox => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
.collect();
|
||||
}
|
||||
|
||||
if self.requests_open {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
return chat
|
||||
.read(cx)
|
||||
.rooms(&RoomKind::Request, cx)
|
||||
.into_iter()
|
||||
.map(|room| SidebarRow::Room { room })
|
||||
.collect();
|
||||
}
|
||||
|
||||
let mut rows = Vec::new();
|
||||
|
||||
let chat = ChatRegistry::global(cx);
|
||||
rows.push(SidebarRow::Section(ListSection::Chats));
|
||||
if self.chats_open {
|
||||
rows.extend(
|
||||
chat.read(cx)
|
||||
.rooms(&RoomKind::Ongoing, cx)
|
||||
.into_iter()
|
||||
.map(|room| SidebarRow::Room { room })
|
||||
.collect()
|
||||
}
|
||||
SidebarTab::Groups => {
|
||||
let registry = GroupsRegistry::global(cx);
|
||||
.map(|room| SidebarRow::Room { room }),
|
||||
);
|
||||
}
|
||||
|
||||
let registry = GroupsRegistry::global(cx);
|
||||
rows.push(SidebarRow::Section(ListSection::Groups));
|
||||
if self.groups_open {
|
||||
rows.extend(
|
||||
registry
|
||||
.read(cx)
|
||||
.groups()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|group| SidebarRow::Group { group })
|
||||
.collect()
|
||||
}
|
||||
.map(|group| SidebarRow::Group { group }),
|
||||
);
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn render_rows(
|
||||
@@ -774,6 +750,34 @@ impl Sidebar {
|
||||
let index = range.start + offset;
|
||||
|
||||
match row {
|
||||
SidebarRow::Section(section) => {
|
||||
let section = *section;
|
||||
let open = match section {
|
||||
ListSection::Chats => self.chats_open,
|
||||
ListSection::Groups => self.groups_open,
|
||||
};
|
||||
let icon = if open {
|
||||
IconName::CaretDown
|
||||
} else {
|
||||
IconName::CaretRight
|
||||
};
|
||||
let label = section.label();
|
||||
|
||||
Nav::new(label)
|
||||
.label(label)
|
||||
.suffix(Icon::new(icon).small().text_color(cx.theme().icon_muted))
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
match section {
|
||||
ListSection::Chats => this.chats_open = !this.chats_open,
|
||||
ListSection::Groups => this.groups_open = !this.groups_open,
|
||||
}
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
SidebarRow::Room { room } => {
|
||||
let name = room.read(cx).display_name(cx);
|
||||
let picture = room.read(cx).display_image(cx);
|
||||
@@ -1261,7 +1265,7 @@ impl Render for Sidebar {
|
||||
})
|
||||
.map(|this| match group {
|
||||
Some(group) if self.group_open => this.child(self.render_group(group, cx)),
|
||||
_ => this.child(self.render_tabs(cx)),
|
||||
_ => this.child(self.render_list(cx)),
|
||||
})
|
||||
.when(loading && logged_in, |this| {
|
||||
this.child(
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SidebarTab {
|
||||
Inbox,
|
||||
Groups,
|
||||
}
|
||||
|
||||
impl SidebarTab {
|
||||
pub const ALL: [SidebarTab; 2] = [Self::Inbox, Self::Groups];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Inbox => "Inbox",
|
||||
Self::Groups => "Groups",
|
||||
}
|
||||
}
|
||||
|
||||
/// Heading for the tab's list of items.
|
||||
pub fn list_title(self) -> &'static str {
|
||||
match self {
|
||||
Self::Inbox => "Direct Messages",
|
||||
Self::Groups => "Groups",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Inbox => "sidebar-inbox",
|
||||
Self::Groups => "sidebar-groups",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index(self) -> usize {
|
||||
match self {
|
||||
Self::Inbox => 0,
|
||||
Self::Groups => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user