update community sidebar
This commit is contained in:
+58
-189
@@ -3,12 +3,9 @@ use community::{ChannelId, ChatMessage, Community, CommunityEvent};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ListAlignment, ListState, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window, div, list, px,
|
||||
IntoElement, ListAlignment, ListState, ParentElement, Render, SharedString, Styled,
|
||||
Subscription, Task, WeakEntity, Window, div, list, px,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
@@ -16,8 +13,8 @@ use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock::{Panel, PanelEvent};
|
||||
use ui::input::{InputEvent, Textarea, TextareaState};
|
||||
use ui::notification::Notification;
|
||||
use ui::scroll::{ScrollableElement, Scrollbar};
|
||||
use ui::{Icon, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||
use ui::scroll::Scrollbar;
|
||||
use ui::{IconName, Sizable, WindowExtension, h_flex, v_flex};
|
||||
|
||||
mod message;
|
||||
|
||||
@@ -64,7 +61,7 @@ impl CommunityPanel {
|
||||
(
|
||||
SharedString::from(format!("community-{}", community.id().to_hex())),
|
||||
community.name(),
|
||||
community.channels().first().map(|channel| channel.id),
|
||||
community.active_channel(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -93,11 +90,15 @@ impl CommunityPanel {
|
||||
CommunityEvent::Updated(_) => {
|
||||
cx.defer_in(window, |this, window, cx| this.reload(window, cx));
|
||||
}
|
||||
// The sidebar picked another channel while the community was updating.
|
||||
CommunityEvent::Channel(..) => {
|
||||
cx.defer_in(window, |this, window, cx| this.load(window, cx));
|
||||
}
|
||||
CommunityEvent::Error(error) => {
|
||||
window
|
||||
.push_notification(Notification::error(error.clone()).autohide(false), cx);
|
||||
}
|
||||
CommunityEvent::Open(_) => {}
|
||||
CommunityEvent::Open(_) | CommunityEvent::Close(_) => {}
|
||||
},
|
||||
));
|
||||
|
||||
@@ -118,9 +119,26 @@ impl CommunityPanel {
|
||||
panel
|
||||
}
|
||||
|
||||
/// The channel to show, following the community's selection.
|
||||
fn resolve_channel(&mut self, cx: &App) -> Option<ChannelId> {
|
||||
let channel = self
|
||||
.community
|
||||
.read_with(cx, |community, _cx| community.active_channel())
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if channel != self.channel {
|
||||
self.channel = channel;
|
||||
self.messages.clear();
|
||||
self.list_state.reset(0);
|
||||
}
|
||||
|
||||
channel
|
||||
}
|
||||
|
||||
/// Page the selected channel's history into the cache, then read it back.
|
||||
fn load(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(channel) = self.channel else {
|
||||
let Some(channel) = self.resolve_channel(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -144,7 +162,7 @@ impl CommunityPanel {
|
||||
|
||||
/// Replace the timeline with the selected channel's folded messages.
|
||||
fn reload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(channel) = self.channel else {
|
||||
let Some(channel) = self.resolve_channel(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -179,19 +197,6 @@ impl CommunityPanel {
|
||||
}));
|
||||
}
|
||||
|
||||
fn select_channel(&mut self, channel: ChannelId, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.channel == Some(channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.channel = Some(channel);
|
||||
self.messages.clear();
|
||||
self.list_state.reset(0);
|
||||
cx.notify();
|
||||
|
||||
self.load(window, cx);
|
||||
}
|
||||
|
||||
fn send(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let content = self.input.read(cx).value().trim().to_owned();
|
||||
|
||||
@@ -200,7 +205,7 @@ impl CommunityPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(channel) = self.channel else {
|
||||
let Some(channel) = self.resolve_channel(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -238,88 +243,6 @@ impl CommunityPanel {
|
||||
}));
|
||||
}
|
||||
|
||||
fn render_channel(
|
||||
&self,
|
||||
id: ChannelId,
|
||||
name: &str,
|
||||
private: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let selected = self.channel == Some(id);
|
||||
|
||||
h_flex()
|
||||
.id(SharedString::from(format!(
|
||||
"community-channel-{}",
|
||||
id.to_hex()
|
||||
)))
|
||||
.w_full()
|
||||
.h_8()
|
||||
.flex_shrink_0()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.rounded(cx.theme().radius)
|
||||
.cursor_pointer()
|
||||
.when(selected, |this| this.bg(cx.theme().ghost_element_selected))
|
||||
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||
.child(
|
||||
Icon::new(if private {
|
||||
IconName::Lock
|
||||
} else {
|
||||
IconName::Message
|
||||
})
|
||||
.small()
|
||||
.text_color(cx.theme().icon_muted),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_ellipsis()
|
||||
.child(SharedString::from(name.to_owned())),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _event, window, cx| {
|
||||
this.select_channel(id, window, cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_timeline(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.h_full()
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.relative()
|
||||
.map(|this| {
|
||||
if self.messages.is_empty() {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child("No messages yet"),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, ix, window, cx| {
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(Scrollbar::vertical(&self.list_state)),
|
||||
)
|
||||
.child(self.render_composer(cx))
|
||||
}
|
||||
|
||||
fn render_message(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
@@ -329,7 +252,6 @@ impl CommunityPanel {
|
||||
let Some(message) = self.messages.get(ix) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
message::render(ix, message, cx)
|
||||
}
|
||||
|
||||
@@ -340,8 +262,6 @@ impl CommunityPanel {
|
||||
.p_2()
|
||||
.gap_1()
|
||||
.items_end()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
.child(Textarea::new(&self.input).appearance(false).flex_1())
|
||||
.child(
|
||||
Button::new("send")
|
||||
@@ -394,89 +314,38 @@ impl Focusable for CommunityPanel {
|
||||
|
||||
impl Render for CommunityPanel {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(community) = self.community.upgrade() else {
|
||||
return div().size_full();
|
||||
};
|
||||
|
||||
let (channels, members) = {
|
||||
let community = community.read(cx);
|
||||
|
||||
(
|
||||
community
|
||||
.channels()
|
||||
.iter()
|
||||
.map(|channel| (channel.id, channel.name.clone(), channel.private))
|
||||
.collect::<Vec<_>>(),
|
||||
community.members().iter().copied().collect::<Vec<_>>(),
|
||||
)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.flex_row()
|
||||
.min_w_0()
|
||||
.child(
|
||||
v_flex()
|
||||
.id("community-sidebar")
|
||||
.w(px(220.))
|
||||
.h_full()
|
||||
.flex_shrink_0()
|
||||
.gap_1()
|
||||
.p_2()
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().border)
|
||||
.overflow_y_scrollbar()
|
||||
.child(section_label("Channels", cx))
|
||||
.children(
|
||||
channels.iter().map(|(id, name, private)| {
|
||||
self.render_channel(*id, name, *private, cx)
|
||||
}),
|
||||
)
|
||||
.child(section_label("Members", cx))
|
||||
.children(
|
||||
members
|
||||
.iter()
|
||||
.map(|public_key| render_member(*public_key, cx)),
|
||||
),
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.relative()
|
||||
.map(|this| {
|
||||
if self.messages.is_empty() {
|
||||
this.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child("No messages yet"),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, ix, window, cx| {
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(Scrollbar::vertical(&self.list_state)),
|
||||
)
|
||||
.child(self.render_timeline(cx))
|
||||
.child(self.render_composer(cx))
|
||||
}
|
||||
}
|
||||
|
||||
fn section_label(label: &str, cx: &App) -> impl IntoElement {
|
||||
div()
|
||||
.px_2()
|
||||
.pt_2()
|
||||
.pb_1()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::from(label.to_owned()))
|
||||
}
|
||||
|
||||
fn render_member(public_key: PublicKey, cx: &App) -> AnyElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let person = persons.read(cx).get(&public_key, cx);
|
||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.flex_shrink_0()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.when(!hide_avatar, |this| {
|
||||
this.child(
|
||||
Avatar::new(person.avatar())
|
||||
.seed(person.avatar_seed())
|
||||
.xsmall(),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_ellipsis()
|
||||
.child(person.name()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user