feat: add option for toggle chat folders

This commit is contained in:
2025-04-30 13:10:18 +07:00
parent 44f0650617
commit 2c2aeb915e
9 changed files with 247 additions and 164 deletions

View File

@@ -7,6 +7,7 @@ use gpui::{
};
use ui::{
theme::{scale::ColorScaleStep, ActiveTheme},
tooltip::Tooltip,
Collapsible, Icon, IconName, Sizable, StyledExt,
};
@@ -16,6 +17,7 @@ type Handler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
pub struct Parent {
base: Div,
icon: Option<Icon>,
tooltip: Option<SharedString>,
label: SharedString,
items: Vec<Folder>,
collapsed: bool,
@@ -28,6 +30,7 @@ impl Parent {
base: div().flex().flex_col().gap_2(),
label: label.into(),
icon: None,
tooltip: None,
items: Vec::new(),
collapsed: false,
handler: Rc::new(|_, _, _| {}),
@@ -39,6 +42,11 @@ impl Parent {
self
}
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn collapsed(mut self, collapsed: bool) -> Self {
self.collapsed = collapsed;
self
@@ -105,6 +113,11 @@ impl RenderOnce for Parent {
.when_some(self.icon, |this, icon| this.child(icon.small()))
.child(self.label.clone()),
)
.when_some(self.tooltip.clone(), |this, tooltip| {
this.tooltip(move |window, cx| {
Tooltip::new(tooltip.clone(), window, cx).into()
})
})
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
.on_click(move |ev, window, cx| handler(ev, window, cx)),
)
@@ -118,6 +131,7 @@ impl RenderOnce for Parent {
pub struct Folder {
base: Div,
icon: Option<Icon>,
tooltip: Option<SharedString>,
label: SharedString,
items: Vec<FolderItem>,
collapsed: bool,
@@ -130,6 +144,7 @@ impl Folder {
base: div().flex().flex_col().gap_2(),
label: label.into(),
icon: None,
tooltip: None,
items: Vec::new(),
collapsed: false,
handler: Rc::new(|_, _, _| {}),
@@ -141,6 +156,11 @@ impl Folder {
self
}
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn collapsed(mut self, collapsed: bool) -> Self {
self.collapsed = collapsed;
self
@@ -201,6 +221,11 @@ impl RenderOnce for Folder {
.when_some(self.icon, |this, icon| this.child(icon.small()))
.child(self.label.clone()),
)
.when_some(self.tooltip.clone(), |this, tooltip| {
this.tooltip(move |window, cx| {
Tooltip::new(tooltip.clone(), window, cx).into()
})
})
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
.on_click(move |ev, window, cx| handler(ev, window, cx)),
)

View File

@@ -1,3 +1,5 @@
use std::{cmp::Reverse, collections::HashSet};
use account::Account;
use button::SidebarButton;
use chats::{
@@ -10,10 +12,11 @@ use global::get_client;
use gpui::{
actions, div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity,
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
SharedString, Styled, Task, Window,
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Task, Window,
};
use itertools::Itertools;
use ui::{
button::{Button, ButtonVariants},
button::{Button, ButtonCustomVariant, ButtonVariants},
dock_area::{
dock::DockPlacement,
panel::{Panel, PanelEvent},
@@ -36,13 +39,25 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
Sidebar::new(window, cx)
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Item {
Ongoing,
Incoming,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum SubItem {
Trusted,
Unknown,
}
pub struct Sidebar {
name: SharedString,
split_into_folders: bool,
active_items: HashSet<Item>,
active_subitems: HashSet<SubItem>,
focus_handle: FocusHandle,
ongoing: bool,
incoming: bool,
trusted: bool,
unknown: bool,
scroll_handle: ScrollHandle,
}
impl Sidebar {
@@ -52,34 +67,41 @@ impl Sidebar {
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let scroll_handle = ScrollHandle::default();
let mut active_items = HashSet::with_capacity(2);
active_items.insert(Item::Ongoing);
let mut active_subitems = HashSet::with_capacity(2);
active_subitems.insert(SubItem::Trusted);
active_subitems.insert(SubItem::Unknown);
Self {
name: "Chat Sidebar".into(),
ongoing: false,
incoming: false,
trusted: true,
unknown: true,
split_into_folders: false,
active_items,
active_subitems,
focus_handle,
scroll_handle,
}
}
fn ongoing(&mut self, cx: &mut Context<Self>) {
self.ongoing = !self.ongoing;
fn toggle_item(&mut self, item: Item, cx: &mut Context<Self>) {
if !self.active_items.remove(&item) {
self.active_items.insert(item);
}
cx.notify();
}
fn incoming(&mut self, cx: &mut Context<Self>) {
self.incoming = !self.incoming;
fn toggle_subitem(&mut self, subitem: SubItem, cx: &mut Context<Self>) {
if !self.active_subitems.remove(&subitem) {
self.active_subitems.insert(subitem);
}
cx.notify();
}
fn trusted(&mut self, cx: &mut Context<Self>) {
self.trusted = !self.trusted;
cx.notify();
}
fn unknown(&mut self, cx: &mut Context<Self>) {
self.unknown = !self.unknown;
fn split_into_folders(&mut self, cx: &mut Context<Self>) {
self.split_into_folders = !self.split_into_folders;
cx.notify();
}
@@ -176,20 +198,18 @@ impl Focusable for Sidebar {
}
impl Render for Sidebar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let account = Account::global(cx).read(cx).profile.as_ref();
let registry = ChatRegistry::global(cx).read(cx);
let rooms = registry.rooms(cx);
let loading = registry.loading();
let ongoing = rooms.get(&RoomKind::Ongoing);
let trusted = rooms.get(&RoomKind::Trusted);
let unknown = rooms.get(&RoomKind::Unknown);
div()
.scrollable(cx.entity_id(), ScrollbarAxis::Vertical)
.id("sidebar")
.track_scroll(&self.scroll_handle)
.on_action(cx.listener(Self::on_logout))
.overflow_y_scroll()
.size_full()
.flex()
.flex_col()
@@ -279,23 +299,65 @@ impl Render for Sidebar {
.gap_2()
.child(
div()
.px_2()
.pl_2()
.pr_1()
.flex()
.justify_between()
.items_center()
.text_xs()
.font_semibold()
.text_color(cx.theme().base.step(cx, ColorScaleStep::NINE))
.child("Messages"),
.child("Messages")
.child(
Button::new("menu")
.tooltip("Toggle chat folders")
.map(|this| {
if self.split_into_folders {
this.icon(IconName::ToggleFill)
} else {
this.icon(IconName::Toggle)
}
})
.small()
.custom(
ButtonCustomVariant::new(window, cx)
.foreground(
cx.theme().base.step(cx, ColorScaleStep::NINE),
)
.color(cx.theme().transparent)
.hover(cx.theme().transparent)
.active(cx.theme().transparent)
.border(cx.theme().transparent),
)
.on_click(cx.listener(move |this, _, _, cx| {
this.split_into_folders(cx);
})),
),
)
.map(|this| {
if loading {
this.children(self.render_skeleton(6))
} else if !self.split_into_folders {
let rooms: Vec<_> = rooms
.values()
.flat_map(|v| v.iter().cloned())
.sorted_by_key(|e| Reverse(e.read(cx).created_at))
.collect();
this.children(Self::render_items(&rooms, cx))
} else {
let ongoing = rooms.get(&RoomKind::Ongoing);
let trusted = rooms.get(&RoomKind::Trusted);
let unknown = rooms.get(&RoomKind::Unknown);
this.when_some(ongoing, |this, rooms| {
this.child(
Folder::new("Ongoing")
.icon(IconName::Folder)
.collapsed(self.ongoing)
.tooltip("All ongoing conversations")
.collapsed(!self.active_items.contains(&Item::Ongoing))
.on_click(cx.listener(move |this, _, _, cx| {
this.ongoing(cx);
this.toggle_item(Item::Ongoing, cx);
}))
.children(Self::render_items(rooms, cx)),
)
@@ -303,17 +365,23 @@ impl Render for Sidebar {
.child(
Parent::new("Incoming")
.icon(IconName::Folder)
.collapsed(self.incoming)
.tooltip("Incoming messages")
.collapsed(!self.active_items.contains(&Item::Incoming))
.on_click(cx.listener(move |this, _, _, cx| {
this.incoming(cx);
this.toggle_item(Item::Incoming, cx);
}))
.when_some(trusted, |this, rooms| {
this.child(
Folder::new("Trusted")
.icon(IconName::Folder)
.collapsed(self.trusted)
.tooltip("Incoming messages from trusted contacts")
.collapsed(
!self
.active_subitems
.contains(&SubItem::Trusted),
)
.on_click(cx.listener(move |this, _, _, cx| {
this.trusted(cx);
this.toggle_subitem(SubItem::Trusted, cx);
}))
.children(Self::render_items(rooms, cx)),
)
@@ -322,9 +390,14 @@ impl Render for Sidebar {
this.child(
Folder::new("Unknown")
.icon(IconName::Folder)
.collapsed(self.unknown)
.tooltip("Incoming messages from unknowns")
.collapsed(
!self
.active_subitems
.contains(&SubItem::Unknown),
)
.on_click(cx.listener(move |this, _, _, cx| {
this.unknown(cx);
this.toggle_subitem(SubItem::Unknown, cx);
}))
.children(Self::render_items(rooms, cx)),
)

View File

@@ -36,6 +36,8 @@ pub enum IconName {
EmojiFill,
Folder,
FolderFill,
Filter,
FilterFill,
Inbox,
Info,
Loader,
@@ -61,6 +63,8 @@ pub enum IconName {
SortAscending,
SortDescending,
Sun,
Toggle,
ToggleFill,
ThumbsDown,
ThumbsUp,
TriangleAlert,
@@ -101,6 +105,8 @@ impl IconName {
Self::EyeOff => "icons/eye-off.svg",
Self::Folder => "icons/folder.svg",
Self::FolderFill => "icons/folder-fill.svg",
Self::Filter => "icons/filter.svg",
Self::FilterFill => "icons/filter-fill.svg",
Self::Inbox => "icons/inbox.svg",
Self::Info => "icons/info.svg",
Self::Loader => "icons/loader.svg",
@@ -126,6 +132,8 @@ impl IconName {
Self::SortAscending => "icons/sort-ascending.svg",
Self::SortDescending => "icons/sort-descending.svg",
Self::Sun => "icons/sun.svg",
Self::Toggle => "icons/toggle.svg",
Self::ToggleFill => "icons/toggle-fill.svg",
Self::ThumbsDown => "icons/thumbs-down.svg",
Self::ThumbsUp => "icons/thumbs-up.svg",
Self::TriangleAlert => "icons/triangle-alert.svg",

View File

@@ -64,7 +64,7 @@ pub trait StyledExt: Styled + Sized {
/// Set as Popover style
fn popover_style(self, cx: &mut App) -> Self {
self.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
self.bg(cx.theme().background)
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.shadow_lg()