update
This commit is contained in:
+187
-161
@@ -2,12 +2,12 @@ use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
use auto_update::AutoUpdater;
|
||||
use chat::{ChatEvent, ChatRegistry, RoomKind};
|
||||
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||
use common::TimestampExt;
|
||||
use community::{ChannelId, Community, CommunityEvent, CommunityRegistry};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
AnyElement, App, Context, Div, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
AnyElement, App, Context, Div, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Stateful,
|
||||
Styled, StyledImage, Subscription, UniformListScrollHandle, WeakEntity, Window, div, img, px,
|
||||
retain_all, uniform_list,
|
||||
@@ -15,32 +15,59 @@ use gpui::{
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::NostrRegistry;
|
||||
use state::{NostrRegistry, StateEvent};
|
||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::button::{Button, ButtonCustomVariant, ButtonVariants};
|
||||
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::menu::{DropdownMenu, PopupMenuItem};
|
||||
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, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING, WindowExtension,
|
||||
h_flex, title_bar_drag_handlers, v_flex,
|
||||
Disableable, Icon, IconName, Selectable, Sizable, StyledExt, TRAFFIC_LIGHT_PADDING,
|
||||
WindowExtension, h_flex, title_bar_drag_handlers, v_flex,
|
||||
};
|
||||
|
||||
use crate::Command;
|
||||
use crate::dialogs::import;
|
||||
|
||||
mod tab;
|
||||
mod tree;
|
||||
mod utils;
|
||||
|
||||
use tab::SidebarTab;
|
||||
use tree::{CommunityRow, CommunitySection, SidebarRow};
|
||||
pub(crate) use tree::{TreeRow, TreeRowKind};
|
||||
pub(crate) use utils::{nav_avatar, nav_icon, pick_banner};
|
||||
|
||||
pub enum SidebarRow {
|
||||
Room { room: Entity<Room> },
|
||||
Community { community: Entity<Community> },
|
||||
}
|
||||
|
||||
/// 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,
|
||||
},
|
||||
Member {
|
||||
public_key: PublicKey,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Sidebar {
|
||||
focus_handle: FocusHandle,
|
||||
@@ -49,6 +76,8 @@ pub struct Sidebar {
|
||||
community_scroll: UniformListScrollHandle,
|
||||
/// The dock the sidebar opens its panels in
|
||||
dock: WeakEntity<DockArea>,
|
||||
/// Background shown behind the signed-out screen, picked at random
|
||||
banner: SharedString,
|
||||
active_tab: SidebarTab,
|
||||
/// The community the sidebar is browsing, if any
|
||||
community: Option<WeakEntity<Community>>,
|
||||
@@ -63,6 +92,7 @@ impl Sidebar {
|
||||
pub fn new(window: &mut Window, dock: WeakEntity<DockArea>, cx: &mut Context<Self>) -> Self {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let communities = CommunityRegistry::global(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
|
||||
@@ -85,6 +115,16 @@ impl Sidebar {
|
||||
},
|
||||
));
|
||||
|
||||
subscriptions.push(
|
||||
cx.subscribe_in(&nostr, window, |this, _nostr, event, _window, cx| {
|
||||
// Re-pick the background each time the signed-out screen is shown.
|
||||
if let StateEvent::NoSigner = event {
|
||||
this.banner = pick_banner();
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handles: [
|
||||
@@ -93,6 +133,7 @@ impl Sidebar {
|
||||
],
|
||||
community_scroll: UniformListScrollHandle::new(),
|
||||
dock,
|
||||
banner: pick_banner(),
|
||||
active_tab: SidebarTab::Inbox,
|
||||
community: None,
|
||||
channels_open: true,
|
||||
@@ -381,6 +422,7 @@ impl Sidebar {
|
||||
let (banner, rows) = {
|
||||
let community = community.read(cx);
|
||||
let owner = community.state().owner;
|
||||
|
||||
let mut admins = Vec::new();
|
||||
let mut members = Vec::new();
|
||||
|
||||
@@ -412,25 +454,26 @@ impl Sidebar {
|
||||
|
||||
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.extend(
|
||||
admins
|
||||
.into_iter()
|
||||
.map(|public_key| CommunityRow::Member { 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,
|
||||
}));
|
||||
rows.extend(
|
||||
members
|
||||
.into_iter()
|
||||
.map(|public_key| CommunityRow::Member { public_key }),
|
||||
);
|
||||
}
|
||||
|
||||
(banner, rows)
|
||||
};
|
||||
|
||||
let rows = Rc::new(rows);
|
||||
let scroll_handle = &self.community_scroll;
|
||||
|
||||
v_flex()
|
||||
.flex_1()
|
||||
@@ -439,7 +482,7 @@ impl Sidebar {
|
||||
.gap_2()
|
||||
.when_some(banner, |this, banner| {
|
||||
this.child(
|
||||
div().px_2().child(
|
||||
div().px_2().flex_shrink_0().child(
|
||||
img(banner)
|
||||
.w_full()
|
||||
.h_20()
|
||||
@@ -460,11 +503,11 @@ impl Sidebar {
|
||||
this.render_community_rows(range, rows.as_slice(), &community, cx)
|
||||
}),
|
||||
)
|
||||
.track_scroll(scroll_handle)
|
||||
.track_scroll(&self.community_scroll)
|
||||
.h_full()
|
||||
.px_2(),
|
||||
)
|
||||
.child(Scrollbar::vertical(scroll_handle)),
|
||||
.child(Scrollbar::vertical(&self.community_scroll)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -480,36 +523,14 @@ impl Sidebar {
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|row| match row {
|
||||
CommunityRow::Section(CommunitySection::Channels) => self.section_row(
|
||||
"channels",
|
||||
"Channels",
|
||||
self.channels_open,
|
||||
|sidebar| sidebar.channels_open = !sidebar.channels_open,
|
||||
cx,
|
||||
),
|
||||
CommunityRow::Section(CommunitySection::Admins) => self.section_row(
|
||||
"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::Section(section) => self.section_row(section, cx),
|
||||
CommunityRow::Member { public_key } => self.member_row(public_key, 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)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -559,26 +580,30 @@ impl Sidebar {
|
||||
let dock = self.dock.clone();
|
||||
let room = room.clone();
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Room,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.created_at(created_at)
|
||||
// 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()
|
||||
Nav::new(SharedString::from(format!("room-{index}")))
|
||||
.label(name)
|
||||
.text_sm()
|
||||
.font_medium()
|
||||
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
||||
this.prefix(avatar)
|
||||
})
|
||||
.suffix(
|
||||
div()
|
||||
.font_normal()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(created_at),
|
||||
)
|
||||
.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()
|
||||
}
|
||||
SidebarRow::Community { community } => {
|
||||
let name = community.read(cx).name();
|
||||
@@ -588,58 +613,66 @@ impl Sidebar {
|
||||
let sidebar = cx.entity().downgrade();
|
||||
let community = community.clone();
|
||||
|
||||
TreeRow::new(
|
||||
ElementId::NamedInteger("tree-row".into(), index as u64),
|
||||
TreeRowKind::Community,
|
||||
name,
|
||||
)
|
||||
.avatar(seed)
|
||||
.picture(picture)
|
||||
.on_click(move |_event, window, cx| {
|
||||
ui::dock::add_panel_to(
|
||||
&dock,
|
||||
PanelHandle::new(community_ui::init(community.clone(), window, cx)),
|
||||
DockPlacement::Center,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Nav::new(SharedString::from(format!("com-{index}")))
|
||||
.label(name)
|
||||
.text_sm()
|
||||
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
||||
this.prefix(avatar)
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
ui::dock::add_panel_to(
|
||||
&dock,
|
||||
PanelHandle::new(community_ui::init(
|
||||
community.clone(),
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
DockPlacement::Center,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||
this.community = Some(community.downgrade());
|
||||
cx.notify();
|
||||
}) {
|
||||
log::error!("Failed to show community in sidebar: {error}");
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||
this.community = Some(community.downgrade());
|
||||
cx.notify();
|
||||
}) {
|
||||
log::error!("Failed to show community in sidebar: {error}");
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn section_row(
|
||||
&self,
|
||||
id: &'static str,
|
||||
label: &'static str,
|
||||
open: bool,
|
||||
toggle: impl Fn(&mut Sidebar) + 'static,
|
||||
cx: &mut Context<Sidebar>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.child(
|
||||
TreeRow::new(ElementId::Name(id.into()), TreeRowKind::Section, label)
|
||||
.icon(if open {
|
||||
IconName::CaretDown
|
||||
} else {
|
||||
IconName::CaretRight
|
||||
})
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
toggle(this);
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
fn section_row(&self, section: &CommunitySection, cx: &mut Context<Sidebar>) -> AnyElement {
|
||||
let section = *section;
|
||||
let (label, open) = match section {
|
||||
CommunitySection::Channels => ("Channels", self.channels_open),
|
||||
CommunitySection::Admins => ("Admins", self.admins_open),
|
||||
CommunitySection::Members => ("Members", self.members_open),
|
||||
};
|
||||
let icon = if open {
|
||||
IconName::CaretDown
|
||||
} else {
|
||||
IconName::CaretRight
|
||||
};
|
||||
|
||||
Nav::new(label)
|
||||
.label(label)
|
||||
.suffix(nav_icon(icon, cx))
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||
match section {
|
||||
CommunitySection::Channels => this.channels_open = !this.channels_open,
|
||||
CommunitySection::Admins => this.admins_open = !this.admins_open,
|
||||
CommunitySection::Members => this.members_open = !this.members_open,
|
||||
}
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -653,50 +686,38 @@ impl Sidebar {
|
||||
cx: &mut Context<Sidebar>,
|
||||
) -> AnyElement {
|
||||
let community = community.clone();
|
||||
let icon = if private {
|
||||
IconName::Lock
|
||||
} else {
|
||||
IconName::Hashtag
|
||||
};
|
||||
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.rounded(cx.theme().radius)
|
||||
.child(
|
||||
TreeRow::new(
|
||||
ElementId::Name(SharedString::from(format!(
|
||||
"community-channel-{}",
|
||||
id.to_hex()
|
||||
))),
|
||||
TreeRowKind::Room,
|
||||
name,
|
||||
)
|
||||
.icon(if private {
|
||||
IconName::Lock
|
||||
} else {
|
||||
IconName::Message
|
||||
})
|
||||
.on_click(cx.listener(move |_this, _event, _window, cx| {
|
||||
community.update(cx, |community, cx| community.set_active_channel(id, cx));
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.when(selected, |this| this.bg(cx.theme().ghost_element_active))
|
||||
Nav::new(id.to_hex())
|
||||
.label(name)
|
||||
.prefix(nav_icon(icon, cx))
|
||||
.text_sm()
|
||||
.font_medium()
|
||||
.selected(selected)
|
||||
.on_click(cx.listener(move |_this, _event, _window, cx| {
|
||||
community.update(cx, |community, cx| {
|
||||
community.set_active_channel(id, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn member_row(&self, prefix: &str, public_key: PublicKey, cx: &App) -> AnyElement {
|
||||
fn member_row(&self, public_key: &PublicKey, cx: &App) -> AnyElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let person = persons.read(cx).get(&public_key, cx);
|
||||
let person = persons.read(cx).get(public_key, cx);
|
||||
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.child(
|
||||
TreeRow::new(
|
||||
ElementId::Name(SharedString::from(format!(
|
||||
"{prefix}-{}",
|
||||
public_key.to_hex()
|
||||
))),
|
||||
TreeRowKind::Room,
|
||||
person.name(),
|
||||
)
|
||||
.avatar(person.avatar_seed())
|
||||
.picture(person.avatar()),
|
||||
Nav::new(public_key.to_hex())
|
||||
.label(person.name())
|
||||
.text_sm()
|
||||
.font_medium()
|
||||
.when_some(
|
||||
nav_avatar(Some(person.avatar_seed()), person.avatar(), cx),
|
||||
|this, avatar| this.prefix(avatar),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -770,27 +791,32 @@ impl Render for Sidebar {
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
div().absolute().inset_0().child(
|
||||
img(self.banner.clone())
|
||||
.size_full()
|
||||
.object_fit(ObjectFit::Cover),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.justify_end()
|
||||
.gap_4()
|
||||
.p_4()
|
||||
.child(img("brand/headline.png").max_w_48())
|
||||
.child(
|
||||
v_flex().child(div().font_semibold().child("Coop")).child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child("TODO"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("import-identity")
|
||||
.label("Import identity")
|
||||
.primary()
|
||||
Button::new("import")
|
||||
.label("Import Identity")
|
||||
.custom(
|
||||
ButtonCustomVariant::new(window, cx)
|
||||
.color(gpui::white())
|
||||
.foreground(gpui::black())
|
||||
.hover(gpui::white().opacity(0.9))
|
||||
.active(gpui::white().opacity(0.8)),
|
||||
)
|
||||
.large()
|
||||
.font_semibold()
|
||||
.h_8()
|
||||
.w_full()
|
||||
.on_click(|_, window, cx| {
|
||||
import::open(window, cx);
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user