.
This commit is contained in:
@@ -3,14 +3,12 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use assets::CustomIconName;
|
use assets::CustomIconName;
|
||||||
use dock::{BasePanel, DockArea, DockPlacement, Panel, PanelEvent, add_bottom_panel, panel_handle};
|
use dock::{BasePanel, DockArea, Panel, PanelEvent};
|
||||||
use gpui::prelude::*;
|
use gpui::prelude::*;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, ListAlignment,
|
AnyElement, App, Context, Div, EventEmitter, FocusHandle, Focusable, ListAlignment, ListState,
|
||||||
ListState, Pixels, Render, SharedString, Stateful, Subscription, Task, WeakEntity, Window, div,
|
Pixels, Render, SharedString, Stateful, Subscription, Task, WeakEntity, Window, div, list, px,
|
||||||
list, px,
|
|
||||||
};
|
};
|
||||||
use gpui_component::button::{Button, ButtonVariants};
|
|
||||||
use gpui_component::scroll::ScrollableElement;
|
use gpui_component::scroll::ScrollableElement;
|
||||||
use gpui_component::{ActiveTheme, Icon, IconName, IconNamed, Sizable, StyledExt, h_flex, v_flex};
|
use gpui_component::{ActiveTheme, Icon, IconName, IconNamed, Sizable, StyledExt, h_flex, v_flex};
|
||||||
use nostr::prelude::{Event, EventId, Kind};
|
use nostr::prelude::{Event, EventId, Kind};
|
||||||
@@ -33,11 +31,7 @@ const LIST_OVERDRAW: Pixels = px(400.);
|
|||||||
|
|
||||||
pub struct InboxView {
|
pub struct InboxView {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
/// The dock area the Unread / Archived sub-view is added to.
|
|
||||||
dock_area: WeakEntity<DockArea>,
|
dock_area: WeakEntity<DockArea>,
|
||||||
/// The open Unread / Archived sub-view, if any. Reused instead of adding a
|
|
||||||
/// duplicate panel on every header click.
|
|
||||||
filter_view: Option<WeakEntity<InboxFilterView>>,
|
|
||||||
/// Notifications grouped by thread root, newest activity first.
|
/// Notifications grouped by thread root, newest activity first.
|
||||||
notifications: Arc<Vec<InboxItem>>,
|
notifications: Arc<Vec<InboxItem>>,
|
||||||
/// The user's own recent git activity, newest first.
|
/// The user's own recent git activity, newest first.
|
||||||
@@ -49,11 +43,11 @@ pub struct InboxView {
|
|||||||
/// Set once the global state has been read for the current user.
|
/// Set once the global state has been read for the current user.
|
||||||
state_loaded: bool,
|
state_loaded: bool,
|
||||||
refresh: RefreshGate,
|
refresh: RefreshGate,
|
||||||
/// Virtual-list state of the notification list, kept in sync with the
|
/// Virtual-list state of the notification list.
|
||||||
/// rendered (non-archived) notifications.
|
|
||||||
notifications_list: ListState,
|
notifications_list: ListState,
|
||||||
/// Virtual-list state of the activity list.
|
/// Virtual-list state of the activity list.
|
||||||
activity_list: ListState,
|
activity_list: ListState,
|
||||||
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,27 +71,26 @@ impl InboxView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Drive the lists from the global inbox state and from backend events.
|
// Drive the lists from the global inbox state and from backend events.
|
||||||
let _subscriptions = vec![
|
let mut subscriptions = vec![];
|
||||||
cx.observe(&inbox, |this, _inbox, cx| this.sync_state(cx)),
|
|
||||||
cx.subscribe(&backend, |this, _backend, event, cx| {
|
subscriptions.push(cx.observe(&inbox, |this, _inbox, cx| {
|
||||||
this.handle_backend_event(event, cx);
|
this.sync_state(cx);
|
||||||
}),
|
}));
|
||||||
];
|
|
||||||
|
subscriptions.push(cx.subscribe(&backend, |this, _backend, event, cx| {
|
||||||
|
this.handle_backend_event(event, cx);
|
||||||
|
}));
|
||||||
|
|
||||||
// Derive the lists once the panel exists.
|
// Derive the lists once the panel exists.
|
||||||
cx.defer({
|
cx.defer(move |cx| {
|
||||||
let weak = weak.clone();
|
if let Err(error) = weak.update(cx, |this, cx| this.sync_state(cx)) {
|
||||||
move |cx| {
|
log::warn!("inbox dropped before bootstrap could run: {error}");
|
||||||
if let Err(error) = weak.update(cx, |this, cx| this.sync_state(cx)) {
|
|
||||||
log::warn!("inbox dropped before bootstrap could run: {error}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
dock_area,
|
dock_area,
|
||||||
filter_view: None,
|
|
||||||
notifications: Arc::new(Vec::new()),
|
notifications: Arc::new(Vec::new()),
|
||||||
activity: Arc::new(Vec::new()),
|
activity: Arc::new(Vec::new()),
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
@@ -106,80 +99,21 @@ impl InboxView {
|
|||||||
refresh: RefreshGate::default(),
|
refresh: RefreshGate::default(),
|
||||||
notifications_list,
|
notifications_list,
|
||||||
activity_list,
|
activity_list,
|
||||||
_subscriptions,
|
tasks: vec![],
|
||||||
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark every event in the group rooted at `root` read.
|
|
||||||
pub fn mark_read(&mut self, root: EventId, cx: &mut Context<Self>) {
|
|
||||||
let Some(me) = Backend::global(cx).read(cx).current_user() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(group) = self.group_events(root) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let all = self.all_notification_events();
|
|
||||||
let inbox = Backend::global(cx).read(cx).inbox();
|
|
||||||
inbox.update(cx, |inbox, cx| inbox.mark_read(&group, &all, me, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Archive the group rooted at `root`.
|
|
||||||
pub fn mark_archived(&mut self, root: EventId, cx: &mut Context<Self>) {
|
|
||||||
let Some(me) = Backend::global(cx).read(cx).current_user() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(group) = self.group_events(root) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let all = self.all_notification_events();
|
|
||||||
let inbox = Backend::global(cx).read(cx).inbox();
|
|
||||||
inbox.update(cx, |inbox, cx| inbox.mark_archived(&group, &all, me, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark every known notification read.
|
/// Mark every known notification read.
|
||||||
pub fn mark_all_read(&mut self, cx: &mut Context<Self>) {
|
pub fn mark_all_read(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(me) = Backend::global(cx).read(cx).current_user() else {
|
let Some(me) = Backend::global(cx).read(cx).current_user() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let all = self.all_notification_events();
|
let all = self.all_notification_events();
|
||||||
let inbox = Backend::global(cx).read(cx).inbox();
|
let inbox = Backend::global(cx).read(cx).inbox();
|
||||||
inbox.update(cx, |inbox, cx| inbox.mark_all_read(&all, me, cx));
|
inbox.update(cx, |inbox, cx| inbox.mark_all_read(&all, me, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show `mode` in the bottom dock, reusing the panel when it is already open.
|
|
||||||
fn open_filter(&mut self, mode: InboxFilter, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let Some(dock_area) = self.dock_area.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(filter) = self.filter_view.as_ref().and_then(WeakEntity::upgrade) {
|
|
||||||
filter.update(cx, |filter, cx| filter.set_mode(mode, cx));
|
|
||||||
|
|
||||||
let handle = filter.read(cx).focus_handle.clone();
|
|
||||||
window.focus(&handle, cx);
|
|
||||||
|
|
||||||
dock_area.update(cx, |dock_area, cx| {
|
|
||||||
if !dock_area.is_dock_open(DockPlacement::Bottom) {
|
|
||||||
dock_area.toggle_dock(DockPlacement::Bottom, window, cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let inbox = cx.entity();
|
|
||||||
let panel = cx.new(|cx| InboxFilterView::new(mode, inbox, cx));
|
|
||||||
self.filter_view = Some(panel.downgrade());
|
|
||||||
|
|
||||||
dock_area.update(cx, |dock_area, cx| {
|
|
||||||
add_bottom_panel(dock_area, panel_handle(panel), window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Re-derive from the global state when it is loaded or changes.
|
/// Re-derive from the global state when it is loaded or changes.
|
||||||
pub fn sync_state(&mut self, cx: &mut Context<Self>) {
|
pub fn sync_state(&mut self, cx: &mut Context<Self>) {
|
||||||
let inbox = Backend::global(cx).read(cx).inbox();
|
let inbox = Backend::global(cx).read(cx).inbox();
|
||||||
@@ -216,7 +150,6 @@ impl InboxView {
|
|||||||
/// Handle a backend event that can change the derived lists.
|
/// Handle a backend event that can change the derived lists.
|
||||||
fn handle_backend_event(&mut self, event: &BackendEvent, cx: &mut Context<Self>) {
|
fn handle_backend_event(&mut self, event: &BackendEvent, cx: &mut Context<Self>) {
|
||||||
match event {
|
match event {
|
||||||
BackendEvent::Synced | BackendEvent::Published(_) => self.refresh(cx),
|
|
||||||
BackendEvent::NostrUpdate(updates) => {
|
BackendEvent::NostrUpdate(updates) => {
|
||||||
let relevant = updates.iter().any(|update| {
|
let relevant = updates.iter().any(|update| {
|
||||||
let is_notification = filters::NOTIFICATION_KINDS.contains(&update.kind);
|
let is_notification = filters::NOTIFICATION_KINDS.contains(&update.kind);
|
||||||
@@ -226,10 +159,12 @@ impl InboxView {
|
|||||||
|
|
||||||
is_notification || is_comment || is_event_deletion || is_request_to_vanish
|
is_notification || is_comment || is_event_deletion || is_request_to_vanish
|
||||||
});
|
});
|
||||||
|
|
||||||
if relevant {
|
if relevant {
|
||||||
self.refresh(cx);
|
self.refresh(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BackendEvent::Synced | BackendEvent::Published(_) => self.refresh(cx),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +176,6 @@ impl InboxView {
|
|||||||
self.refresh.request();
|
self.refresh.request();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_refresh(cx);
|
self.run_refresh(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,11 +189,10 @@ impl InboxView {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
|
||||||
this.update(cx, |this, cx| this.run_refresh(cx))
|
this.update(cx, |this, cx| this.run_refresh(cx))
|
||||||
})
|
}));
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One query and apply cycle, the debounced entry point.
|
/// One query and apply cycle, the debounced entry point.
|
||||||
@@ -277,10 +210,9 @@ impl InboxView {
|
|||||||
|
|
||||||
let work = cx.background_spawn(async move { query_inbox(&client, me, &state).await });
|
let work = cx.background_spawn(async move { query_inbox(&client, me, &state).await });
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
let (notifications, activity, unread_count) = match work.await {
|
let (notifications, activity, unread_count) = match work.await {
|
||||||
Ok(results) => results,
|
Ok(results) => results,
|
||||||
// Database errors are transient, keep the last lists.
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("inbox refresh failed: {error}");
|
log::warn!("inbox refresh failed: {error}");
|
||||||
return this.update(cx, |this, _cx| this.refresh.abort());
|
return this.update(cx, |this, _cx| this.refresh.abort());
|
||||||
@@ -288,9 +220,7 @@ impl InboxView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let again = this.update(cx, |this, cx| {
|
let again = this.update(cx, |this, cx| {
|
||||||
// The signer may have changed while the query ran, making
|
if backend.read(cx).current_user() != Some(me) {
|
||||||
// these results belong to the previous user.
|
|
||||||
if Backend::global(cx).read(cx).current_user() != Some(me) {
|
|
||||||
this.refresh.abort();
|
this.refresh.abort();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -309,9 +239,7 @@ impl InboxView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
}));
|
||||||
|
|
||||||
task.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recompute the unread and archived flags from the current state.
|
/// Recompute the unread and archived flags from the current state.
|
||||||
@@ -344,14 +272,6 @@ impl InboxView {
|
|||||||
self.refresh = RefreshGate::default();
|
self.refresh = RefreshGate::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events of the group rooted at `root`.
|
|
||||||
fn group_events(&self, root: EventId) -> Option<Vec<Event>> {
|
|
||||||
self.notifications
|
|
||||||
.iter()
|
|
||||||
.find(|item| item.root == root)
|
|
||||||
.map(|item| item.events.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every event in every group, archived groups included.
|
/// Every event in every group, archived groups included.
|
||||||
fn all_notification_events(&self) -> Vec<Event> {
|
fn all_notification_events(&self) -> Vec<Event> {
|
||||||
self.notifications
|
self.notifications
|
||||||
@@ -360,10 +280,6 @@ impl InboxView {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bordered card with a header bar and a scrolling body.
|
|
||||||
///
|
|
||||||
/// Flexible so its body gets a definite height, which the virtual list
|
|
||||||
/// needs to know which rows to render.
|
|
||||||
fn section(&self, header: impl IntoElement, body: impl IntoElement, cx: &App) -> AnyElement {
|
fn section(&self, header: impl IntoElement, body: impl IntoElement, cx: &App) -> AnyElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -405,20 +321,6 @@ impl InboxView {
|
|||||||
)
|
)
|
||||||
.when(unread > 0, |this| this.child(CountBadge::new(unread)))
|
.when(unread > 0, |this| this.child(CountBadge::new(unread)))
|
||||||
.child(div().flex_1())
|
.child(div().flex_1())
|
||||||
.child(
|
|
||||||
SegmentButton::new("inbox-unread", "Unread")
|
|
||||||
.icon(Icon::new(IconName::Inbox).small())
|
|
||||||
.on_click(cx.listener(|this, _event, window, cx| {
|
|
||||||
this.open_filter(InboxFilter::Unread, window, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
SegmentButton::new("inbox-archived", "Archived")
|
|
||||||
.icon(Icon::new(IconName::FolderClosed).small())
|
|
||||||
.on_click(cx.listener(|this, _event, window, cx| {
|
|
||||||
this.open_filter(InboxFilter::Archived, window, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
SegmentButton::new("mark-all-read", "Mark all read")
|
SegmentButton::new("mark-all-read", "Mark all read")
|
||||||
.on_click(cx.listener(|this, _event, _window, cx| this.mark_all_read(cx))),
|
.on_click(cx.listener(|this, _event, _window, cx| this.mark_all_read(cx))),
|
||||||
@@ -678,7 +580,7 @@ fn display_name(announcement: &Announcement) -> SharedString {
|
|||||||
/// Leading icon for a notification or activity kind.
|
/// Leading icon for a notification or activity kind.
|
||||||
fn kind_icon(kind: Kind) -> Icon {
|
fn kind_icon(kind: Kind) -> Icon {
|
||||||
if kind == COVER_NOTE_KIND {
|
if kind == COVER_NOTE_KIND {
|
||||||
return Icon::new(IconName::FileText).small();
|
return Icon::new(IconName::File).small();
|
||||||
}
|
}
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
@@ -741,166 +643,6 @@ fn empty_state(icon: impl IconNamed, message: &str, cx: &App) -> AnyElement {
|
|||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which subset of the inbox a bottom-dock sub-view shows.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum InboxFilter {
|
|
||||||
Unread,
|
|
||||||
Archived,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InboxFilter {
|
|
||||||
/// Tab and empty-state label of the sub-view.
|
|
||||||
fn label(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Unread => "Unread",
|
|
||||||
Self::Archived => "Archived",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `item` belongs in this sub-view.
|
|
||||||
fn matches(self, item: &InboxItem) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Unread => item.is_unread(),
|
|
||||||
Self::Archived => item.archived,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Unread / Archived sub-view of the inbox, opened in the bottom dock.
|
|
||||||
struct InboxFilterView {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
/// The inbox panel whose groups are filtered. A strong handle: the panel
|
|
||||||
/// keeps only a weak one back, so the two entities do not form a cycle.
|
|
||||||
inbox: Entity<InboxView>,
|
|
||||||
mode: InboxFilter,
|
|
||||||
list: ListState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InboxFilterView {
|
|
||||||
fn new(mode: InboxFilter, inbox: Entity<InboxView>, cx: &mut Context<Self>) -> Self {
|
|
||||||
let list = ListState::new(0, ListAlignment::Top, LIST_OVERDRAW);
|
|
||||||
let weak = cx.entity().downgrade();
|
|
||||||
list.set_scroll_handler(move |_, _, cx| {
|
|
||||||
let weak = weak.clone();
|
|
||||||
cx.defer(move |cx| {
|
|
||||||
let _ = weak.update(cx, |_, cx| cx.notify());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
inbox,
|
|
||||||
mode,
|
|
||||||
list,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Switch which subset is shown, when the header asks for another mode.
|
|
||||||
fn set_mode(&mut self, mode: InboxFilter, cx: &mut Context<Self>) {
|
|
||||||
if self.mode == mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mode = mode;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasePanel for InboxFilterView {
|
|
||||||
fn panel_name(&self) -> &'static str {
|
|
||||||
"inbox_filter"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel for InboxFilterView {
|
|
||||||
fn title(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
div().text_sm().child(SharedString::from(self.mode.label()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for InboxFilterView {}
|
|
||||||
|
|
||||||
impl Focusable for InboxFilterView {
|
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for InboxFilterView {
|
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let mode = self.mode;
|
|
||||||
let notifications = self.inbox.read(cx).notifications.clone();
|
|
||||||
|
|
||||||
let visible: Vec<usize> = notifications
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, item)| mode.matches(item))
|
|
||||||
.map(|(ix, _)| ix)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if self.list.item_count() != visible.len() {
|
|
||||||
self.list.reset(visible.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
let list_state = self.list.clone();
|
|
||||||
let inbox = self.inbox.clone();
|
|
||||||
|
|
||||||
let body: AnyElement = if visible.is_empty() {
|
|
||||||
let (icon, message) = match mode {
|
|
||||||
InboxFilter::Unread => (IconName::Inbox, "Nothing unread."),
|
|
||||||
InboxFilter::Archived => (IconName::FolderClosed, "Nothing archived."),
|
|
||||||
};
|
|
||||||
empty_state(icon, message, cx)
|
|
||||||
} else {
|
|
||||||
let rows = list(list_state.clone(), move |ix, _window, cx| {
|
|
||||||
let Some(item) = visible.get(ix).and_then(|&ix| notifications.get(ix)) else {
|
|
||||||
return div().into_any_element();
|
|
||||||
};
|
|
||||||
|
|
||||||
match mode {
|
|
||||||
InboxFilter::Unread => {
|
|
||||||
let root = item.root;
|
|
||||||
let open = inbox.clone();
|
|
||||||
let archive = inbox.clone();
|
|
||||||
|
|
||||||
notification_row("inbox-filter-row", ix, item, cx)
|
|
||||||
.on_click(move |_, _, cx| {
|
|
||||||
open.update(cx, |view, cx| view.mark_read(root, cx));
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
Button::new(("inbox-filter-archive", ix))
|
|
||||||
.icon(IconName::FolderClosed)
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.tab_stop(false)
|
|
||||||
.tooltip("Archive")
|
|
||||||
.on_click(move |_, _, cx| {
|
|
||||||
cx.stop_propagation();
|
|
||||||
archive.update(cx, |view, cx| view.mark_archived(root, cx));
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
InboxFilter::Archived => {
|
|
||||||
notification_row("inbox-filter-row", ix, item, cx).into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.size_full()
|
|
||||||
.min_h_0();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.relative()
|
|
||||||
.flex_1()
|
|
||||||
.min_h_0()
|
|
||||||
.child(rows)
|
|
||||||
.vertical_scrollbar(&list_state)
|
|
||||||
.into_any_element()
|
|
||||||
};
|
|
||||||
|
|
||||||
v_flex().size_full().min_h_0().p_2().child(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasePanel for InboxView {
|
impl BasePanel for InboxView {
|
||||||
fn panel_name(&self) -> &'static str {
|
fn panel_name(&self) -> &'static str {
|
||||||
"inbox"
|
"inbox"
|
||||||
|
|||||||
Reference in New Issue
Block a user