update sidebar
This commit is contained in:
@@ -31,7 +31,7 @@ fn uses_managed_updates() -> bool {
|
|||||||
// Allow opting out of in-app updates via an explicit environment variable.
|
// Allow opting out of in-app updates via an explicit environment variable.
|
||||||
|| std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
|| std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||||
// The Snap package sets `COOP_BUNDLE_TYPE=snap` (see snapcraft.yaml.in).
|
// The Snap package sets `COOP_BUNDLE_TYPE=snap` (see snapcraft.yaml.in).
|
||||||
|| std::env::var(COOP_BUNDLE_TYPE).is_ok_and(|value| value == "snap")
|
|| std::env::var(COOP_BUNDLE_TYPE).is_ok_and(|value | value == "snap")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the auto-update system.
|
/// Initialize the auto-update system.
|
||||||
@@ -129,6 +129,11 @@ impl AutoUpdater {
|
|||||||
matches!(self.status, UpdateStatus::Idle)
|
matches!(self.status, UpdateStatus::Idle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the running version is the newest release, so the status line can be hidden.
|
||||||
|
pub fn up_to_date(&self) -> bool {
|
||||||
|
matches!(self.status, UpdateStatus::Idle | UpdateStatus::UpToDate)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether a verified update is installed and waiting for a restart.
|
/// Whether a verified update is installed and waiting for a restart.
|
||||||
pub fn staged(&self) -> bool {
|
pub fn staged(&self) -> bool {
|
||||||
matches!(self.status, UpdateStatus::Staged(_))
|
matches!(self.status, UpdateStatus::Staged(_))
|
||||||
|
|||||||
+76
-31
@@ -25,6 +25,7 @@ pub struct Tab {
|
|||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
pub(super) disabled: bool,
|
pub(super) disabled: bool,
|
||||||
pub(super) selected: bool,
|
pub(super) selected: bool,
|
||||||
|
pub(super) segmented: bool,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ impl Default for Tab {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
segmented: false,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@@ -132,6 +134,12 @@ impl Tab {
|
|||||||
self.tab_bar_prefix = Some(tab_bar_prefix);
|
self.tab_bar_prefix = Some(tab_bar_prefix);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the tab as a segment inside a segmented control.
|
||||||
|
pub(crate) fn segmented(mut self, segmented: bool) -> Self {
|
||||||
|
self.segmented = segmented;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for Tab {
|
impl ParentElement for Tab {
|
||||||
@@ -167,74 +175,111 @@ impl Styled for Tab {
|
|||||||
|
|
||||||
impl RenderOnce for Tab {
|
impl RenderOnce for Tab {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let fg = if self.disabled {
|
let Self {
|
||||||
|
ix,
|
||||||
|
base,
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
selected,
|
||||||
|
segmented,
|
||||||
|
on_click,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let fg = if disabled {
|
||||||
cx.theme().text_muted
|
cx.theme().text_muted
|
||||||
} else if self.selected {
|
} else if selected {
|
||||||
cx.theme().tab_active_foreground
|
cx.theme().tab_active_foreground
|
||||||
} else {
|
} else {
|
||||||
cx.theme().tab_foreground
|
cx.theme().tab_foreground
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base
|
let content = h_flex()
|
||||||
.id(self.ix)
|
|
||||||
.flex()
|
|
||||||
.flex_wrap()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.h(TABBAR_HEIGHT)
|
|
||||||
.relative()
|
|
||||||
.overflow_hidden()
|
|
||||||
.text_color(fg)
|
|
||||||
.text_sm()
|
|
||||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.h(px(30.))
|
.map(|this| {
|
||||||
|
if segmented {
|
||||||
|
this.h(px(24.))
|
||||||
|
} else {
|
||||||
|
this.h(px(30.))
|
||||||
|
}
|
||||||
|
})
|
||||||
.line_height(relative(1.))
|
.line_height(relative(1.))
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.flex_shrink_0()
|
.when(segmented, |this| this.px_1())
|
||||||
.px_3()
|
.when(!segmented, |this| this.flex_shrink_0().px_3())
|
||||||
.map(|this| match self.icon {
|
.map(|this| match icon {
|
||||||
Some(icon) => this.w(px(38.)).child(icon.size_4()),
|
Some(icon) => this.w(px(38.)).child(icon.size_4()),
|
||||||
None => this
|
None => this
|
||||||
.map(|this| match self.label {
|
.map(|this| match label {
|
||||||
Some(label) => this.child(label),
|
Some(label) => this.child(label),
|
||||||
None => this,
|
None => this,
|
||||||
})
|
})
|
||||||
.children(self.children),
|
.children(children),
|
||||||
}),
|
});
|
||||||
)
|
|
||||||
.when_some(self.suffix, |this, suffix| {
|
base.id(ix)
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.text_color(fg)
|
||||||
|
.when(segmented, |this| {
|
||||||
|
this.text_xs()
|
||||||
|
.flex_1()
|
||||||
|
.h(px(24.))
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.when(selected && !disabled, |this| {
|
||||||
|
this.bg(cx.theme().tab_active_background)
|
||||||
|
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||||
|
})
|
||||||
|
.when(!selected && !disabled, |this| {
|
||||||
|
this.hover(|this| this.bg(cx.theme().tab_hover_background))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.when(!segmented, |this| {
|
||||||
|
this.text_sm()
|
||||||
|
.flex_wrap()
|
||||||
|
.gap_1()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.h(TABBAR_HEIGHT)
|
||||||
|
.relative()
|
||||||
|
.overflow_hidden()
|
||||||
|
})
|
||||||
|
.when_some(prefix, |this, prefix| this.child(prefix))
|
||||||
|
.child(content)
|
||||||
|
.when_some(suffix, |this, suffix| {
|
||||||
this.child(div().pr_2().child(suffix))
|
this.child(div().pr_2().child(suffix))
|
||||||
})
|
})
|
||||||
.on_mouse_down(MouseButton::Left, |_ev, _window, cx| {
|
.on_mouse_down(MouseButton::Left, |_ev, _window, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
})
|
||||||
.when(!self.disabled, |this| {
|
.when(!disabled, |this| {
|
||||||
this.when_some(self.on_click.clone(), |this, on_click| {
|
this.when_some(on_click, |this, on_click| {
|
||||||
this.on_click(move |event, window, cx| on_click(event, window, cx))
|
this.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.child(
|
.when(!segmented, |this| {
|
||||||
|
this.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.left_0()
|
.left_0()
|
||||||
.right_0()
|
.right_0()
|
||||||
.h_0p5()
|
.h_0p5()
|
||||||
.when(self.selected && !self.disabled, |this| {
|
.when(selected && !disabled, |this| {
|
||||||
this.bg(cx.theme().element_active)
|
this.bg(cx.theme().element_active)
|
||||||
})
|
})
|
||||||
.when(!self.selected && !self.disabled, |this| {
|
.when(!selected && !disabled, |this| {
|
||||||
this.invisible().group_hover("", |this| {
|
this.invisible().group_hover("", |this| {
|
||||||
this.visible().bg(cx.theme().secondary_background)
|
this.visible().bg(cx.theme().secondary_background)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use gpui::{
|
|||||||
Window, div, px,
|
Window, div, px,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use super::Tab;
|
use super::Tab;
|
||||||
use crate::button::{Button, ButtonVariants as _};
|
use crate::button::{Button, ButtonVariants as _};
|
||||||
@@ -25,6 +26,7 @@ pub struct TabBar {
|
|||||||
last_empty_space: AnyElement,
|
last_empty_space: AnyElement,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
menu: bool,
|
menu: bool,
|
||||||
|
segmented: bool,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
@@ -43,9 +45,16 @@ impl TabBar {
|
|||||||
selected_index: None,
|
selected_index: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
menu: false,
|
menu: false,
|
||||||
|
segmented: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the tabs as a segmented control inside a pill-shaped track.
|
||||||
|
pub fn segmented(mut self, segmented: bool) -> Self {
|
||||||
|
self.segmented = segmented;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether to show the menu button when tabs overflow, default is false.
|
/// Set whether to show the menu button when tabs overflow, default is false.
|
||||||
pub fn menu(mut self, menu: bool) -> Self {
|
pub fn menu(mut self, menu: bool) -> Self {
|
||||||
self.menu = menu;
|
self.menu = menu;
|
||||||
@@ -113,10 +122,11 @@ impl Styled for TabBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for TabBar {
|
impl RenderOnce for TabBar {
|
||||||
fn render(self, _: &mut Window, _cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let mut item_labels = Vec::new();
|
let mut item_labels = Vec::new();
|
||||||
let selected_index = self.selected_index;
|
let selected_index = self.selected_index;
|
||||||
let on_click = self.on_click.clone();
|
let on_click = self.on_click.clone();
|
||||||
|
let segmented = self.segmented;
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.group("tab-bar")
|
.group("tab-bar")
|
||||||
@@ -124,22 +134,28 @@ impl RenderOnce for TabBar {
|
|||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
|
.when(segmented, |this| {
|
||||||
|
this.bg(cx.theme().tab_background)
|
||||||
|
.p_0p5()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
})
|
||||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("tabs")
|
.id("tabs")
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.overflow_x_scroll()
|
.when(!segmented, |this| this.overflow_x_scroll())
|
||||||
.when_some(self.scroll_handle, |this, scroll_handle| {
|
.when_some(self.scroll_handle, |this, scroll_handle| {
|
||||||
this.track_scroll(&scroll_handle)
|
this.track_scroll(&scroll_handle)
|
||||||
})
|
})
|
||||||
.gap(px(0.))
|
.gap_1()
|
||||||
.children(self.children.into_iter().enumerate().map(|(ix, child)| {
|
.children(self.children.into_iter().enumerate().map(|(ix, child)| {
|
||||||
item_labels.push((child.label.clone(), child.disabled));
|
item_labels.push((child.label.clone(), child.disabled));
|
||||||
let tab_bar_prefix = child.tab_bar_prefix.unwrap_or(true);
|
let tab_bar_prefix = child.tab_bar_prefix.unwrap_or(true);
|
||||||
child
|
child
|
||||||
.ix(ix)
|
.ix(ix)
|
||||||
.tab_bar_prefix(tab_bar_prefix)
|
.tab_bar_prefix(tab_bar_prefix)
|
||||||
|
.segmented(segmented)
|
||||||
.when_some(self.selected_index, |this, selected_ix| {
|
.when_some(self.selected_index, |this, selected_ix| {
|
||||||
this.selected(selected_ix == ix)
|
this.selected(selected_ix == ix)
|
||||||
})
|
})
|
||||||
|
|||||||
+254
-335
@@ -2,19 +2,18 @@ use std::ops::Range;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use auto_update::AutoUpdater;
|
use auto_update::AutoUpdater;
|
||||||
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
use chat::{ChatEvent, ChatRegistry, RoomKind};
|
||||||
use common::TimestampExt;
|
use common::TimestampExt;
|
||||||
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, ScrollHandle, SharedString,
|
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Stateful,
|
||||||
Stateful, StatefulInteractiveElement, Styled, StyledImage, Subscription,
|
Styled, StyledImage, Subscription, UniformListScrollHandle, WeakEntity, Window, div, img, px,
|
||||||
UniformListScrollHandle, WeakEntity, Window, div, img, px, retain_all, uniform_list,
|
retain_all, uniform_list,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
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};
|
||||||
@@ -26,9 +25,11 @@ use ui::menu::{DropdownMenu, PopupMenuItem};
|
|||||||
use ui::nav_item::NavItem;
|
use ui::nav_item::NavItem;
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::scroll::Scrollbar;
|
use ui::scroll::Scrollbar;
|
||||||
|
use ui::tab::Tab;
|
||||||
|
use ui::tab::tab_bar::TabBar;
|
||||||
use ui::{
|
use ui::{
|
||||||
Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension, h_flex,
|
Disableable, Icon, IconName, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension,
|
||||||
title_bar_drag_handlers, v_flex,
|
h_flex, title_bar_drag_handlers, v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Command;
|
use crate::Command;
|
||||||
@@ -37,15 +38,15 @@ use crate::dialogs::import;
|
|||||||
mod tab;
|
mod tab;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
use tab::{SidebarTab, TabBar};
|
use tab::SidebarTab;
|
||||||
use tree::SidebarRow;
|
use tree::{CommunityRow, CommunitySection, 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_handles: [UniformListScrollHandle; 3],
|
scroll_handles: [UniformListScrollHandle; 2],
|
||||||
/// Scroll state of the channel and member lists
|
/// Scroll state of the community's channel and member lists
|
||||||
community_scroll: ScrollHandle,
|
community_scroll: UniformListScrollHandle,
|
||||||
/// The dock the sidebar opens its panels in
|
/// The dock the sidebar opens its panels in
|
||||||
dock: WeakEntity<DockArea>,
|
dock: WeakEntity<DockArea>,
|
||||||
active_tab: SidebarTab,
|
active_tab: SidebarTab,
|
||||||
@@ -89,11 +90,10 @@ impl Sidebar {
|
|||||||
scroll_handles: [
|
scroll_handles: [
|
||||||
UniformListScrollHandle::new(),
|
UniformListScrollHandle::new(),
|
||||||
UniformListScrollHandle::new(),
|
UniformListScrollHandle::new(),
|
||||||
UniformListScrollHandle::new(),
|
|
||||||
],
|
],
|
||||||
community_scroll: ScrollHandle::default(),
|
community_scroll: UniformListScrollHandle::new(),
|
||||||
dock,
|
dock,
|
||||||
active_tab: SidebarTab::Recents,
|
active_tab: SidebarTab::Inbox,
|
||||||
community: None,
|
community: None,
|
||||||
channels_open: true,
|
channels_open: true,
|
||||||
admins_open: true,
|
admins_open: true,
|
||||||
@@ -111,42 +111,6 @@ impl Sidebar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_community(
|
|
||||||
&mut self,
|
|
||||||
community: Entity<Community>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let id = community.read(cx).id().to_hex();
|
|
||||||
let settings = AppSettings::global(cx);
|
|
||||||
|
|
||||||
settings.update(cx, |settings, cx| {
|
|
||||||
settings.record_recent_community(id, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.community = Some(community.downgrade());
|
|
||||||
|
|
||||||
ui::dock::add_panel_to(
|
|
||||||
&self.dock,
|
|
||||||
PanelHandle::new(community_ui::init(community, window, cx)),
|
|
||||||
DockPlacement::Center,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_room(&mut self, room: Entity<Room>, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
ui::dock::add_panel_to(
|
|
||||||
&self.dock,
|
|
||||||
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
|
|
||||||
DockPlacement::Center,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Leave the community view, returning the sidebar to its tab list.
|
/// Leave the community view, returning the sidebar to its tab list.
|
||||||
fn reset_community(&mut self, cx: &mut Context<Self>) {
|
fn reset_community(&mut self, cx: &mut Context<Self>) {
|
||||||
if self.community.take().is_none() {
|
if self.community.take().is_none() {
|
||||||
@@ -155,9 +119,12 @@ impl Sidebar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_user(&self, cx: &mut Context<Self>) -> Stateful<Div> {
|
fn render_user(&self, current_user: &PublicKey, cx: &mut Context<Self>) -> Stateful<Div> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let current_user = nostr.read(cx).current_user();
|
let profile = persons.read(cx).get(current_user, cx);
|
||||||
|
let avatar = profile.avatar();
|
||||||
|
let avatar_seed = profile.avatar_seed();
|
||||||
|
let name = profile.name();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("sidebar-user")
|
.id("sidebar-user")
|
||||||
@@ -170,14 +137,7 @@ impl Sidebar {
|
|||||||
.when(cfg!(target_os = "macos"), |this| {
|
.when(cfg!(target_os = "macos"), |this| {
|
||||||
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
||||||
})
|
})
|
||||||
.when_some(current_user.as_ref(), |this, public_key| {
|
.child(
|
||||||
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();
|
|
||||||
|
|
||||||
this.child(
|
|
||||||
Button::new("current-user")
|
Button::new("current-user")
|
||||||
.child(
|
.child(
|
||||||
Avatar::new(avatar.clone())
|
Avatar::new(avatar.clone())
|
||||||
@@ -188,7 +148,7 @@ impl Sidebar {
|
|||||||
.caret()
|
.caret()
|
||||||
.compact()
|
.compact()
|
||||||
.transparent()
|
.transparent()
|
||||||
.dropdown_menu(move |this, _window, cx| {
|
.dropdown_menu(move |this, _window, _cx| {
|
||||||
let avatar = avatar.clone();
|
let avatar = avatar.clone();
|
||||||
let avatar_seed = avatar_seed.clone();
|
let avatar_seed = avatar_seed.clone();
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
@@ -207,11 +167,7 @@ impl Sidebar {
|
|||||||
.child(name.clone())
|
.child(name.clone())
|
||||||
}))
|
}))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_icon(
|
.menu_with_icon("Inbox", IconName::Inbox, Box::new(Command::ShowInbox))
|
||||||
"Inbox",
|
|
||||||
IconName::Inbox,
|
|
||||||
Box::new(Command::ShowInbox),
|
|
||||||
)
|
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Search",
|
"Search",
|
||||||
IconName::Search,
|
IconName::Search,
|
||||||
@@ -232,18 +188,8 @@ impl Sidebar {
|
|||||||
IconName::UserKey,
|
IconName::UserKey,
|
||||||
Box::new(Command::ShowBackup),
|
Box::new(Command::ShowBackup),
|
||||||
)
|
)
|
||||||
.menu_with_icon(
|
.menu_with_icon("Themes", IconName::Sun, Box::new(Command::ToggleTheme))
|
||||||
"Themes",
|
.separator()
|
||||||
IconName::Sun,
|
|
||||||
Box::new(Command::ToggleTheme),
|
|
||||||
)
|
|
||||||
.when(AutoUpdater::is_available(cx), |this| {
|
|
||||||
this.separator().menu_with_icon(
|
|
||||||
"Check for Updates",
|
|
||||||
IconName::Device,
|
|
||||||
Box::new(Command::Update),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Settings",
|
"Settings",
|
||||||
IconName::Settings,
|
IconName::Settings,
|
||||||
@@ -251,7 +197,6 @@ impl Sidebar {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.when_some(AutoUpdater::try_global(cx), |this, updater| {
|
.when_some(AutoUpdater::try_global(cx), |this, updater| {
|
||||||
this.child(self.render_updater(updater, cx))
|
this.child(self.render_updater(updater, cx))
|
||||||
@@ -272,12 +217,21 @@ impl Sidebar {
|
|||||||
|
|
||||||
fn render_updater(&self, updater: Entity<AutoUpdater>, cx: &mut App) -> AnyElement {
|
fn render_updater(&self, updater: Entity<AutoUpdater>, cx: &mut App) -> AnyElement {
|
||||||
let status = updater.read(cx).status();
|
let status = updater.read(cx).status();
|
||||||
|
let up_to_date = updater.read(cx).up_to_date();
|
||||||
let staged = updater.read(cx).staged();
|
let staged = updater.read(cx).staged();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
.when(!up_to_date, |this| {
|
||||||
.child(status)
|
this.child(
|
||||||
|
Button::new("update-status")
|
||||||
|
.icon(IconName::ArrowDownCircle)
|
||||||
|
.tooltip(status)
|
||||||
|
.small()
|
||||||
|
.warning()
|
||||||
|
.disabled(true),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when(staged, |this| {
|
.when(staged, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("restart-to-update")
|
Button::new("restart-to-update")
|
||||||
@@ -296,9 +250,6 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_tabs(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
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 sidebar = cx.entity().downgrade();
|
||||||
let active_tab = self.active_tab;
|
let active_tab = self.active_tab;
|
||||||
let rows = Rc::new(self.rows_for(active_tab, cx));
|
let rows = Rc::new(self.rows_for(active_tab, cx));
|
||||||
@@ -308,28 +259,46 @@ impl Sidebar {
|
|||||||
.size_full()
|
.size_full()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.min_h_0()
|
.min_h_0()
|
||||||
.gap_1()
|
.gap_2()
|
||||||
.when(active_tab.chat(), |this| {
|
.child(
|
||||||
this.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::Communities.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()
|
v_flex()
|
||||||
.px_2()
|
.px_2()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
NavItem::new(
|
NavItem::new(
|
||||||
"nav-contacts",
|
"new-chat",
|
||||||
"Contacts",
|
"New Chat",
|
||||||
Icon::new(IconName::Book).small(),
|
Icon::new(IconName::Message).small(),
|
||||||
)
|
)
|
||||||
.on_click(|_event, window, cx| {
|
.on_click(|_event, window, cx| {
|
||||||
window.dispatch_action(Box::new(Command::ShowContactList), cx)
|
window.dispatch_action(Box::new(Command::NewChat), cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
NavItem::new(
|
NavItem::new("reqs", "Requests", Icon::new(IconName::Invite).small())
|
||||||
"nav-requests",
|
|
||||||
"Requests",
|
|
||||||
Icon::new(IconName::Invite).small(),
|
|
||||||
)
|
|
||||||
.when(self.new_requests, |this| {
|
.when(self.new_requests, |this| {
|
||||||
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
this.suffix(div().size_1().rounded_full().bg(cx.theme().cursor))
|
||||||
})
|
})
|
||||||
@@ -347,51 +316,38 @@ impl Sidebar {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
NavItem::new(
|
NavItem::new("contacts", "Contacts", Icon::new(IconName::Book).small())
|
||||||
"nav-new-chat",
|
|
||||||
"New chat",
|
|
||||||
Icon::new(IconName::Plus).small(),
|
|
||||||
)
|
|
||||||
.on_click(|_event, window, cx| {
|
.on_click(|_event, window, cx| {
|
||||||
window.dispatch_action(Box::new(Command::NewChat), cx)
|
window.dispatch_action(Box::new(Command::ShowContactList), cx)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
})
|
SidebarTab::Communities => this.child(
|
||||||
.when(active_tab.community(), |this| {
|
|
||||||
this.child(
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.px_2()
|
.px_2()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
NavItem::new(
|
NavItem::new(
|
||||||
"nav-browse",
|
"new-community",
|
||||||
"Browse",
|
"New Community",
|
||||||
Icon::new(IconName::Compass).small(),
|
Icon::new(IconName::Group).small(),
|
||||||
)
|
)
|
||||||
.on_click(|_event, window, cx| {
|
.on_click(|_, window, cx| {
|
||||||
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
NavItem::new(
|
|
||||||
"nav-new-community",
|
|
||||||
"New community",
|
|
||||||
Icon::new(IconName::Plus).small(),
|
|
||||||
)
|
|
||||||
.on_click(|_event, window, cx| {
|
|
||||||
window.dispatch_action(Box::new(Command::NewCommunity), cx)
|
window.dispatch_action(Box::new(Command::NewCommunity), cx)
|
||||||
}),
|
}),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
NavItem::new("browse", "Browse", Icon::new(IconName::Compass).small())
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
div()
|
||||||
.size_full()
|
|
||||||
.flex_1()
|
|
||||||
.min_h_0()
|
.min_h_0()
|
||||||
.gap_1()
|
.flex_1()
|
||||||
.pb_12()
|
|
||||||
.child(
|
.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
active_tab.list_id(),
|
active_tab.list_id(),
|
||||||
@@ -401,46 +357,11 @@ impl Sidebar {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
.flex_1()
|
|
||||||
.h_full()
|
.h_full()
|
||||||
.px_2(),
|
.px_2(),
|
||||||
)
|
)
|
||||||
.child(Scrollbar::vertical(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_16()
|
|
||||||
.left_0()
|
|
||||||
.h_9()
|
|
||||||
.w_full()
|
|
||||||
.px_8()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.w_full()
|
|
||||||
.h_9()
|
|
||||||
.justify_center()
|
|
||||||
.bg(cx.theme().background.opacity(0.85))
|
|
||||||
.when(cx.theme().shadow, |this| this.shadow_md())
|
|
||||||
.rounded_full()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(Indicator::new().small().color(cx.theme().icon_accent))
|
|
||||||
.child("Getting messages..."),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +370,7 @@ impl Sidebar {
|
|||||||
community: Entity<Community>,
|
community: Entity<Community>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let (channels, active, admins, members, banner) = {
|
let (banner, rows) = {
|
||||||
let community = community.read(cx);
|
let community = community.read(cx);
|
||||||
let owner = community.state().owner;
|
let owner = community.state().owner;
|
||||||
let mut admins = Vec::new();
|
let mut admins = Vec::new();
|
||||||
@@ -463,71 +384,45 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
let active = community.active_channel();
|
||||||
|
let banner = community.banner();
|
||||||
|
|
||||||
|
let mut rows = vec![CommunityRow::Section(CommunitySection::Channels)];
|
||||||
|
if self.channels_open {
|
||||||
|
rows.extend(
|
||||||
community
|
community
|
||||||
.channels()
|
.channels()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|channel| (channel.id, channel.name.clone(), channel.private))
|
.map(|channel| CommunityRow::Channel {
|
||||||
.collect::<Vec<_>>(),
|
id: channel.id,
|
||||||
community.active_channel(),
|
name: channel.name.clone().into(),
|
||||||
admins,
|
private: channel.private,
|
||||||
members,
|
selected: active == Some(channel.id),
|
||||||
community.banner(),
|
}),
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push(CommunityRow::Section(CommunitySection::Admins));
|
||||||
|
if self.admins_open {
|
||||||
|
rows.extend(admins.into_iter().map(|public_key| CommunityRow::Member {
|
||||||
|
prefix: "community-admin",
|
||||||
|
public_key,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push(CommunityRow::Section(CommunitySection::Members));
|
||||||
|
if self.members_open {
|
||||||
|
rows.extend(members.into_iter().map(|public_key| CommunityRow::Member {
|
||||||
|
prefix: "member",
|
||||||
|
public_key,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
(banner, rows)
|
||||||
};
|
};
|
||||||
|
|
||||||
let sections = v_flex()
|
let rows = Rc::new(rows);
|
||||||
.id("community-sections")
|
let scroll_handle = &self.community_scroll;
|
||||||
.flex_1()
|
|
||||||
.min_h_0()
|
|
||||||
.w_full()
|
|
||||||
.px_2()
|
|
||||||
.pb_2()
|
|
||||||
.track_scroll(&self.community_scroll)
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.child(self.section_row(
|
|
||||||
"channels",
|
|
||||||
"Channels",
|
|
||||||
channels.len(),
|
|
||||||
self.channels_open,
|
|
||||||
|sidebar| sidebar.channels_open = !sidebar.channels_open,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.when(self.channels_open, |this| {
|
|
||||||
this.children(channels.into_iter().map(|(id, name, private)| {
|
|
||||||
self.channel_row(id, name, private, active == Some(id), &community, cx)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.child(self.section_row(
|
|
||||||
"admins",
|
|
||||||
"Admins",
|
|
||||||
admins.len(),
|
|
||||||
self.admins_open,
|
|
||||||
|sidebar| sidebar.admins_open = !sidebar.admins_open,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.when(self.admins_open, |this| {
|
|
||||||
this.children(
|
|
||||||
admins
|
|
||||||
.iter()
|
|
||||||
.map(|public_key| self.member_row("community-admin", *public_key, cx)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(self.section_row(
|
|
||||||
"members",
|
|
||||||
"Members",
|
|
||||||
members.len(),
|
|
||||||
self.members_open,
|
|
||||||
|sidebar| sidebar.members_open = !sidebar.members_open,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.when(self.members_open, |this| {
|
|
||||||
this.children(
|
|
||||||
members
|
|
||||||
.iter()
|
|
||||||
.map(|public_key| self.member_row("member", *public_key, cx)),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -539,110 +434,94 @@ impl Sidebar {
|
|||||||
div().px_2().child(
|
div().px_2().child(
|
||||||
img(banner)
|
img(banner)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(px(80.))
|
.h_20()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius_lg)
|
||||||
.object_fit(ObjectFit::Cover),
|
.object_fit(ObjectFit::Cover),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(sections)
|
.child(
|
||||||
.child(Scrollbar::vertical(&self.community_scroll))
|
div()
|
||||||
|
.min_h_0()
|
||||||
|
.flex_1()
|
||||||
|
.child(
|
||||||
|
uniform_list(
|
||||||
|
"community-rows",
|
||||||
|
rows.len(),
|
||||||
|
cx.processor(move |this, range, _window, cx| {
|
||||||
|
this.render_community_rows(range, rows.as_slice(), &community, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.track_scroll(scroll_handle)
|
||||||
|
.h_full()
|
||||||
|
.px_2(),
|
||||||
|
)
|
||||||
|
.child(Scrollbar::vertical(scroll_handle)),
|
||||||
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recent_communities(&self, cx: &App) -> Vec<Entity<Community>> {
|
fn render_community_rows(
|
||||||
const LIMIT: usize = 3;
|
&self,
|
||||||
|
range: Range<usize>,
|
||||||
let registry = CommunityRegistry::global(cx);
|
rows: &[CommunityRow],
|
||||||
let communities = registry.read(cx).communities();
|
community: &Entity<Community>,
|
||||||
let recent = AppSettings::get_recent_communities(cx);
|
cx: &mut Context<Self>,
|
||||||
|
) -> Vec<AnyElement> {
|
||||||
let mut rows: Vec<Entity<Community>> = recent
|
rows.get(range)
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter_map(|id| {
|
.flatten()
|
||||||
communities
|
.map(|row| match row {
|
||||||
.iter()
|
CommunityRow::Section(CommunitySection::Channels) => self.section_row(
|
||||||
.find(|community| community.read(cx).id().to_hex() == *id)
|
"channels",
|
||||||
})
|
"Channels",
|
||||||
.take(LIMIT)
|
self.channels_open,
|
||||||
.cloned()
|
|sidebar| sidebar.channels_open = !sidebar.channels_open,
|
||||||
.collect();
|
cx,
|
||||||
|
),
|
||||||
if rows.is_empty() {
|
CommunityRow::Section(CommunitySection::Admins) => self.section_row(
|
||||||
rows = communities.iter().take(LIMIT).cloned().collect();
|
"admins",
|
||||||
|
"Admins",
|
||||||
|
self.admins_open,
|
||||||
|
|sidebar| sidebar.admins_open = !sidebar.admins_open,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
CommunityRow::Section(CommunitySection::Members) => self.section_row(
|
||||||
|
"members",
|
||||||
|
"Members",
|
||||||
|
self.members_open,
|
||||||
|
|sidebar| sidebar.members_open = !sidebar.members_open,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
CommunityRow::Channel {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
private,
|
||||||
|
selected,
|
||||||
|
} => self.channel_row(*id, name.clone(), *private, *selected, community, cx),
|
||||||
|
CommunityRow::Member { prefix, public_key } => {
|
||||||
|
self.member_row(prefix, *public_key, cx)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
rows
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows_for(&self, tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
fn rows_for(&self, tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
|
||||||
match tab {
|
match tab {
|
||||||
SidebarTab::Recents => {
|
SidebarTab::Inbox => {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let rooms = chat.read(cx).rooms(&RoomKind::Ongoing, 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 {
|
let mut rows = vec![SidebarRow::Section {
|
||||||
label: "Chats".into(),
|
label: "Chats".into(),
|
||||||
count: messages.len(),
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if messages.is_empty() {
|
if rooms.is_empty() {
|
||||||
rows.push(SidebarRow::Hint {
|
rows.push(SidebarRow::Hint {
|
||||||
text: "No conversations yet".into(),
|
text: "No conversations yet".into(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
rows.extend(messages.into_iter().map(|room| SidebarRow::Room { room }));
|
rows.extend(rooms.into_iter().map(|room| SidebarRow::Room { room }));
|
||||||
}
|
}
|
||||||
|
|
||||||
rows
|
rows
|
||||||
@@ -653,7 +532,6 @@ impl Sidebar {
|
|||||||
|
|
||||||
let mut rows = vec![SidebarRow::Section {
|
let mut rows = vec![SidebarRow::Section {
|
||||||
label: "Communities".into(),
|
label: "Communities".into(),
|
||||||
count: communities.len(),
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if communities.is_empty() {
|
if communities.is_empty() {
|
||||||
@@ -688,23 +566,19 @@ impl Sidebar {
|
|||||||
let index = range.start + offset;
|
let index = range.start + offset;
|
||||||
|
|
||||||
match row {
|
match row {
|
||||||
SidebarRow::Section { label, count } => TreeRow::new(
|
SidebarRow::Section { label } => TreeRow::new(
|
||||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||||
TreeRowKind::Section,
|
TreeRowKind::Section,
|
||||||
label.clone(),
|
label.clone(),
|
||||||
)
|
)
|
||||||
.count(*count)
|
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
SidebarRow::Room { room } => {
|
SidebarRow::Room { room } => {
|
||||||
let name = room.read(cx).display_name(cx);
|
let name = room.read(cx).display_name(cx);
|
||||||
let picture = room.read(cx).display_image(cx);
|
let picture = room.read(cx).display_image(cx);
|
||||||
let seed = room.read(cx).display_image_seed(cx);
|
let seed = room.read(cx).display_image_seed(cx);
|
||||||
let created_at = room.read(cx).created_at.to_ago();
|
let created_at = room.read(cx).created_at.to_ago();
|
||||||
let room_clone = room.clone();
|
let dock = self.dock.clone();
|
||||||
|
let room = room.clone();
|
||||||
let handler = cx.listener(move |this, _event, window, cx| {
|
|
||||||
this.open_room(room_clone.clone(), window, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
TreeRow::new(
|
TreeRow::new(
|
||||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||||
@@ -714,13 +588,25 @@ impl Sidebar {
|
|||||||
.avatar(seed)
|
.avatar(seed)
|
||||||
.picture(picture)
|
.picture(picture)
|
||||||
.created_at(created_at)
|
.created_at(created_at)
|
||||||
.on_click(handler)
|
// Run in an app context: docking a new panel reads every
|
||||||
|
// docked panel's id, which would panic inside a sidebar update.
|
||||||
|
.on_click(move |_event, window, cx| {
|
||||||
|
ui::dock::add_panel_to(
|
||||||
|
&dock,
|
||||||
|
PanelHandle::new(chat_ui::init(room.downgrade(), window, cx)),
|
||||||
|
DockPlacement::Center,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
SidebarRow::Community { community } => {
|
SidebarRow::Community { community } => {
|
||||||
let name = community.read(cx).name();
|
let name = community.read(cx).name();
|
||||||
let seed = community.read(cx).id().to_hex();
|
let seed = community.read(cx).id().to_hex();
|
||||||
let picture = community.read(cx).icon();
|
let picture = community.read(cx).icon();
|
||||||
|
let dock = self.dock.clone();
|
||||||
|
let sidebar = cx.entity().downgrade();
|
||||||
let community = community.clone();
|
let community = community.clone();
|
||||||
|
|
||||||
TreeRow::new(
|
TreeRow::new(
|
||||||
@@ -730,23 +616,22 @@ impl Sidebar {
|
|||||||
)
|
)
|
||||||
.avatar(seed)
|
.avatar(seed)
|
||||||
.picture(picture)
|
.picture(picture)
|
||||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
this.open_community(community.clone(), window, cx);
|
ui::dock::add_panel_to(
|
||||||
}))
|
&dock,
|
||||||
.into_any_element()
|
PanelHandle::new(community_ui::init(community.clone(), window, cx)),
|
||||||
}
|
DockPlacement::Center,
|
||||||
SidebarRow::Action { label, tab } => {
|
window,
|
||||||
let tab = *tab;
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
TreeRow::new(
|
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
this.community = Some(community.downgrade());
|
||||||
TreeRowKind::Action,
|
cx.notify();
|
||||||
label.clone(),
|
}) {
|
||||||
)
|
log::error!("Failed to show community in sidebar: {error}");
|
||||||
.icon(IconName::ArrowRight)
|
}
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
})
|
||||||
this.select_tab(tab, cx);
|
|
||||||
}))
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
SidebarRow::Hint { text } => TreeRow::new(
|
SidebarRow::Hint { text } => TreeRow::new(
|
||||||
@@ -764,7 +649,6 @@ impl Sidebar {
|
|||||||
&self,
|
&self,
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
count: usize,
|
|
||||||
open: bool,
|
open: bool,
|
||||||
toggle: impl Fn(&mut Sidebar) + 'static,
|
toggle: impl Fn(&mut Sidebar) + 'static,
|
||||||
cx: &mut Context<Sidebar>,
|
cx: &mut Context<Sidebar>,
|
||||||
@@ -778,7 +662,6 @@ impl Sidebar {
|
|||||||
} else {
|
} else {
|
||||||
IconName::CaretRight
|
IconName::CaretRight
|
||||||
})
|
})
|
||||||
.count(count)
|
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
toggle(this);
|
toggle(this);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -790,7 +673,7 @@ impl Sidebar {
|
|||||||
fn channel_row(
|
fn channel_row(
|
||||||
&self,
|
&self,
|
||||||
id: ChannelId,
|
id: ChannelId,
|
||||||
name: String,
|
name: SharedString,
|
||||||
private: bool,
|
private: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
community: &Entity<Community>,
|
community: &Entity<Community>,
|
||||||
@@ -875,7 +758,11 @@ impl Focusable for Sidebar {
|
|||||||
impl Render 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 nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let logged_in = { nostr.read(cx).current_user().is_some() };
|
let current_user = nostr.read(cx).current_user();
|
||||||
|
let logged_in = current_user.is_some();
|
||||||
|
|
||||||
|
let chat = ChatRegistry::global(cx);
|
||||||
|
let loading = chat.read(cx).loading();
|
||||||
|
|
||||||
let community = self
|
let community = self
|
||||||
.community
|
.community
|
||||||
@@ -890,6 +777,13 @@ impl Render for Sidebar {
|
|||||||
.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)
|
||||||
|
.when_some(current_user.as_ref(), |this, current_user| {
|
||||||
|
this.child(title_bar_drag_handlers(
|
||||||
|
self.render_user(current_user, cx),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
})
|
||||||
.when(!logged_in, |this| {
|
.when(!logged_in, |this| {
|
||||||
this.relative()
|
this.relative()
|
||||||
.child(title_bar_drag_handlers(
|
.child(title_bar_drag_handlers(
|
||||||
@@ -930,11 +824,36 @@ impl Render for Sidebar {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(title_bar_drag_handlers(self.render_user(cx), window, cx))
|
|
||||||
.map(|this| match community {
|
.map(|this| match community {
|
||||||
Some(community) => this.child(self.render_community(community, cx)),
|
Some(community) => this.child(self.render_community(community, cx)),
|
||||||
None => this.child(self.render_tabs(cx)),
|
None => this.child(self.render_tabs(cx)),
|
||||||
})
|
})
|
||||||
|
.when(loading && logged_in, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.bottom_4()
|
||||||
|
.left_0()
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_8()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
|
.h_9()
|
||||||
|
.justify_center()
|
||||||
|
.bg(cx.theme().background.opacity(0.85))
|
||||||
|
.when(cx.theme().shadow, |this| this.shadow_md())
|
||||||
|
.rounded_full()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(Indicator::new().small().color(cx.theme().icon_accent))
|
||||||
|
.child("Getting messages..."),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,30 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
|
||||||
use gpui::{App, InteractiveElement, 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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum SidebarTab {
|
pub enum SidebarTab {
|
||||||
Recents,
|
Inbox,
|
||||||
Chats,
|
|
||||||
Communities,
|
Communities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SidebarTab {
|
impl SidebarTab {
|
||||||
pub const ALL: [SidebarTab; 3] = [Self::Recents, Self::Chats, Self::Communities];
|
pub const ALL: [SidebarTab; 2] = [Self::Inbox, Self::Communities];
|
||||||
|
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Recents => "Recents",
|
Self::Inbox => "Inbox",
|
||||||
Self::Chats => "Chats",
|
|
||||||
Self::Communities => "Communities",
|
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 {
|
pub fn list_id(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Recents => "sidebar-recents",
|
Self::Inbox => "sidebar-inbox",
|
||||||
Self::Chats => "sidebar-chats",
|
|
||||||
Self::Communities => "sidebar-communities",
|
Self::Communities => "sidebar-communities",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index(self) -> usize {
|
pub fn index(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Recents => 0,
|
Self::Inbox => 0,
|
||||||
Self::Chats => 1,
|
Self::Communities => 1,
|
||||||
Self::Communities => 2,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chat(self) -> bool {
|
|
||||||
matches!(self, Self::Chats)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn community(self) -> bool {
|
|
||||||
matches!(self, Self::Communities)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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()
|
|
||||||
.id("sidebar-tabs")
|
|
||||||
.absolute()
|
|
||||||
.bottom_3()
|
|
||||||
.left_0()
|
|
||||||
.w_full()
|
|
||||||
.px_4()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.p_1()
|
|
||||||
.gap_1()
|
|
||||||
.rounded_full()
|
|
||||||
.bg(cx.theme().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()
|
|
||||||
.rounded()
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,45 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use chat::Room;
|
use chat::Room;
|
||||||
use community::Community;
|
use community::{ChannelId, Community};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
|
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
|
||||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
||||||
};
|
};
|
||||||
|
use nostr_sdk::prelude::PublicKey;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use theme::ActiveTheme;
|
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};
|
||||||
|
|
||||||
use super::tab::SidebarTab;
|
|
||||||
|
|
||||||
pub enum SidebarRow {
|
pub enum SidebarRow {
|
||||||
Section {
|
Section { label: SharedString },
|
||||||
label: SharedString,
|
Room { room: Entity<Room> },
|
||||||
count: usize,
|
Community { community: Entity<Community> },
|
||||||
|
Hint { text: SharedString },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collapsible group of rows in the sidebar's community view.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum CommunitySection {
|
||||||
|
Channels,
|
||||||
|
Admins,
|
||||||
|
Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row in the sidebar's community view.
|
||||||
|
pub enum CommunityRow {
|
||||||
|
Section(CommunitySection),
|
||||||
|
Channel {
|
||||||
|
id: ChannelId,
|
||||||
|
name: SharedString,
|
||||||
|
private: bool,
|
||||||
|
selected: bool,
|
||||||
},
|
},
|
||||||
Room {
|
Member {
|
||||||
room: Entity<Room>,
|
prefix: &'static str,
|
||||||
},
|
public_key: PublicKey,
|
||||||
Community {
|
|
||||||
community: Entity<Community>,
|
|
||||||
},
|
|
||||||
Action {
|
|
||||||
label: SharedString,
|
|
||||||
tab: SidebarTab,
|
|
||||||
},
|
|
||||||
Hint {
|
|
||||||
text: SharedString,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +48,6 @@ pub enum TreeRowKind {
|
|||||||
Section,
|
Section,
|
||||||
Room,
|
Room,
|
||||||
Community,
|
Community,
|
||||||
Action,
|
|
||||||
Hint,
|
Hint,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +59,6 @@ pub struct TreeRow {
|
|||||||
avatar: Option<SharedString>,
|
avatar: Option<SharedString>,
|
||||||
picture: Option<ImageSource>,
|
picture: Option<ImageSource>,
|
||||||
icon: Option<IconName>,
|
icon: Option<IconName>,
|
||||||
count: Option<usize>,
|
|
||||||
created_at: Option<SharedString>,
|
created_at: Option<SharedString>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@@ -71,7 +78,6 @@ impl TreeRow {
|
|||||||
avatar: None,
|
avatar: None,
|
||||||
picture: None,
|
picture: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
count: None,
|
|
||||||
created_at: None,
|
created_at: None,
|
||||||
selected: false,
|
selected: false,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@@ -96,11 +102,6 @@ impl TreeRow {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(mut self, count: usize) -> Self {
|
|
||||||
self.count = Some(count);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn created_at(mut self, created_at: impl Into<SharedString>) -> Self {
|
pub fn created_at(mut self, created_at: impl Into<SharedString>) -> Self {
|
||||||
self.created_at = Some(created_at.into());
|
self.created_at = Some(created_at.into());
|
||||||
self
|
self
|
||||||
@@ -133,7 +134,6 @@ impl RenderOnce for TreeRow {
|
|||||||
let is_section = self.kind == TreeRowKind::Section;
|
let is_section = self.kind == TreeRowKind::Section;
|
||||||
let is_room = self.kind == TreeRowKind::Room;
|
let is_room = self.kind == TreeRowKind::Room;
|
||||||
let is_community = self.kind == TreeRowKind::Community;
|
let is_community = self.kind == TreeRowKind::Community;
|
||||||
let is_action = self.kind == TreeRowKind::Action;
|
|
||||||
let is_hint = self.kind == TreeRowKind::Hint;
|
let is_hint = self.kind == TreeRowKind::Hint;
|
||||||
let is_selected = self.selected;
|
let is_selected = self.selected;
|
||||||
|
|
||||||
@@ -172,7 +172,6 @@ impl RenderOnce for TreeRow {
|
|||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.h_8()
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -182,10 +181,8 @@ impl RenderOnce for TreeRow {
|
|||||||
.text_color(cx.theme().text_placeholder)
|
.text_color(cx.theme().text_placeholder)
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
})
|
})
|
||||||
.when(is_room || is_community, |this| this.text_sm())
|
.when(is_room || is_community, |this| this.text_sm().h_10())
|
||||||
.when(is_action, |this| {
|
.h_8()
|
||||||
this.text_sm().text_color(cx.theme().text_muted)
|
|
||||||
})
|
|
||||||
.when(is_hint, |this| {
|
.when(is_hint, |this| {
|
||||||
this.text_xs()
|
this.text_xs()
|
||||||
.font_normal()
|
.font_normal()
|
||||||
@@ -203,6 +200,7 @@ impl RenderOnce for TreeRow {
|
|||||||
.when(is_room, |this| this.font_medium())
|
.when(is_room, |this| this.font_medium())
|
||||||
.child(self.label),
|
.child(self.label),
|
||||||
)
|
)
|
||||||
|
.child(div().flex_1())
|
||||||
.when(is_selected, |this| {
|
.when(is_selected, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Icon::new(IconName::CheckCircle)
|
Icon::new(IconName::CheckCircle)
|
||||||
@@ -211,11 +209,8 @@ impl RenderOnce for TreeRow {
|
|||||||
.text_color(cx.theme().icon_accent),
|
.text_color(cx.theme().icon_accent),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when_some(self.count, |this, count| {
|
|
||||||
this.child(div().flex_shrink_0().font_normal().child(count.to_string()))
|
|
||||||
})
|
|
||||||
.when_some(self.created_at, |this, created_at| {
|
.when_some(self.created_at, |this, created_at| {
|
||||||
this.child(div().flex_1()).child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.text_color(cx.theme().text_placeholder)
|
.text_color(cx.theme().text_placeholder)
|
||||||
|
|||||||
Reference in New Issue
Block a user