update
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run
Rust / build (macos-latest, stable) (pull_request) Waiting to run
Rust / build (ubuntu-latest, stable) (pull_request) Waiting to run
Rust / build (windows-latest, stable) (pull_request) Waiting to run
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run
Rust / build (macos-latest, stable) (pull_request) Waiting to run
Rust / build (ubuntu-latest, stable) (pull_request) Waiting to run
Rust / build (windows-latest, stable) (pull_request) Waiting to run
This commit is contained in:
+148
-216
@@ -12,10 +12,8 @@ use gpui::{
|
||||
};
|
||||
use gpui_component::button::{Button, ButtonVariants};
|
||||
use gpui_component::{ActiveTheme, Icon, IconName, IconNamed, Sizable, StyledExt, h_flex, v_flex};
|
||||
use nostr::prelude::{Event, EventId, Kind, Timestamp};
|
||||
use signed_core::{
|
||||
COVER_NOTE_KIND, InboxItem, InboxReadState, RepoAddr, activity_subject, filters,
|
||||
};
|
||||
use nostr::prelude::{Event, EventId, Kind, PublicKey, Timestamp};
|
||||
use signed_core::{COVER_NOTE_KIND, InboxItem, InboxReadState, RepoAddr, filters};
|
||||
use signed_state::{
|
||||
Backend, BackendEvent, ProfileStore, RefreshGate, RefreshRequest, RepoListStore, query_inbox,
|
||||
};
|
||||
@@ -30,29 +28,21 @@ const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||
/// Extra list rows measured above and below the visible area.
|
||||
const LIST_OVERDRAW: Pixels = px(400.);
|
||||
|
||||
/// A repository's slice of the inbox: its notification groups and own activity.
|
||||
/// Maximum number of sub-activity lines shown under a thread row.
|
||||
const MAX_SUB_ACTIVITIES: usize = 5;
|
||||
|
||||
/// A repository's slice of the inbox: the threads that belong to it.
|
||||
struct InboxSection {
|
||||
/// Repository the section groups, `None` for items without one.
|
||||
address: Option<RepoAddr>,
|
||||
/// Number of notification groups with an unread event.
|
||||
/// Number of threads with an unread event.
|
||||
unread: usize,
|
||||
/// Notification and activity rows, newest first.
|
||||
entries: Vec<InboxEntry>,
|
||||
/// Indices into the threads, newest activity first.
|
||||
entries: Vec<usize>,
|
||||
/// Timestamp of the newest entry, used to order the sections.
|
||||
latest: Timestamp,
|
||||
}
|
||||
|
||||
/// One row inside a repository section, as an index into the inbox's own lists.
|
||||
#[derive(Clone, Copy)]
|
||||
enum InboxEntry {
|
||||
/// Index into the notification groups.
|
||||
Notification(usize),
|
||||
/// Index into the user's own activity.
|
||||
Activity(usize),
|
||||
}
|
||||
|
||||
/// One row of the flattened inbox: a repository header, one of its entries, or
|
||||
/// the empty state of a repository without any.
|
||||
#[derive(Clone, Copy)]
|
||||
enum InboxRow {
|
||||
Repo(usize),
|
||||
@@ -63,15 +53,13 @@ enum InboxRow {
|
||||
pub struct InboxView {
|
||||
focus_handle: FocusHandle,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
/// Notifications grouped by thread root, newest activity first.
|
||||
notifications: Arc<Vec<InboxItem>>,
|
||||
/// The user's own recent git activity, newest first.
|
||||
activity: Arc<Vec<Event>>,
|
||||
/// The notification and activity lists grouped by repository, newest first.
|
||||
/// One row per thread, merging notifications and own activity, newest first.
|
||||
threads: Arc<Vec<InboxItem>>,
|
||||
/// The threads grouped by repository, newest first.
|
||||
sections: Arc<Vec<InboxSection>>,
|
||||
/// The flattened repository headers and rows of the list.
|
||||
rows: Arc<Vec<InboxRow>>,
|
||||
/// Number of non-archived groups with an unread event.
|
||||
/// Number of non-archived threads with an unread event.
|
||||
unread_count: usize,
|
||||
/// Copy of the global read state the current lists were derived with.
|
||||
state: InboxReadState,
|
||||
@@ -118,8 +106,7 @@ impl InboxView {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
dock_area,
|
||||
notifications: Arc::new(Vec::new()),
|
||||
activity: Arc::new(Vec::new()),
|
||||
threads: Arc::new(Vec::new()),
|
||||
sections: Arc::new(Vec::new()),
|
||||
rows: Arc::new(Vec::new()),
|
||||
unread_count: 0,
|
||||
@@ -144,17 +131,17 @@ impl InboxView {
|
||||
|
||||
/// Re-derive from the global state when it is loaded or changes.
|
||||
pub fn sync_state(&mut self, cx: &mut Context<Self>) {
|
||||
let inbox = Backend::global(cx).read(cx).inbox();
|
||||
let backend = Backend::global(cx);
|
||||
let inbox = backend.read(cx).inbox();
|
||||
|
||||
let (loaded, state) = {
|
||||
let inbox = inbox.read(cx);
|
||||
(inbox.is_loaded(), inbox.state().clone())
|
||||
};
|
||||
|
||||
if !loaded {
|
||||
let was_present = self.state_loaded
|
||||
|| !self.notifications.is_empty()
|
||||
|| !self.activity.is_empty()
|
||||
|| !self.sections.is_empty();
|
||||
let was_present =
|
||||
self.state_loaded || !self.threads.is_empty() || !self.sections.is_empty();
|
||||
self.clear();
|
||||
if was_present {
|
||||
cx.notify();
|
||||
@@ -240,7 +227,7 @@ impl InboxView {
|
||||
let work = cx.background_spawn(async move { query_inbox(&client, me, &state).await });
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let (notifications, activity, unread_count) = match work.await {
|
||||
let (threads, unread_count) = match work.await {
|
||||
Ok(results) => results,
|
||||
Err(error) => {
|
||||
log::warn!("inbox refresh failed: {error}");
|
||||
@@ -254,8 +241,7 @@ impl InboxView {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.notifications = Arc::new(notifications);
|
||||
this.activity = Arc::new(activity);
|
||||
this.threads = Arc::new(threads);
|
||||
this.unread_count = unread_count;
|
||||
this.rebuild(cx);
|
||||
cx.notify();
|
||||
@@ -273,22 +259,22 @@ impl InboxView {
|
||||
|
||||
/// Recompute the unread and archived flags from the current state.
|
||||
fn regroup(&mut self, cx: &mut Context<Self>) {
|
||||
let mut items = (*self.notifications).clone();
|
||||
let mut items = (*self.threads).clone();
|
||||
|
||||
for item in items.iter_mut() {
|
||||
item.apply_state(&self.state);
|
||||
}
|
||||
|
||||
self.unread_count = items.iter().filter(|item| item.is_unread()).count();
|
||||
self.notifications = Arc::new(items);
|
||||
self.threads = Arc::new(items);
|
||||
self.rebuild(cx);
|
||||
}
|
||||
|
||||
/// Regroup the current lists by repository and flatten them into rows.
|
||||
/// Regroup the current threads by repository and flatten them into rows.
|
||||
fn rebuild(&mut self, cx: &mut Context<Self>) {
|
||||
let backend = Backend::global(cx);
|
||||
let repo_list = RepoListStore::global(cx);
|
||||
let mut sections = group_sections(&self.notifications, &self.activity);
|
||||
let mut sections = group_sections(&self.threads);
|
||||
|
||||
if let Some(me) = backend.read(cx).current_user() {
|
||||
for announcement in repo_list.read(cx).announcements_of(&me) {
|
||||
@@ -317,8 +303,7 @@ impl InboxView {
|
||||
|
||||
/// Forget everything derived for the current user.
|
||||
fn clear(&mut self) {
|
||||
self.notifications = Arc::new(Vec::new());
|
||||
self.activity = Arc::new(Vec::new());
|
||||
self.threads = Arc::new(Vec::new());
|
||||
self.sections = Arc::new(Vec::new());
|
||||
self.rows = Arc::new(Vec::new());
|
||||
self.unread_count = 0;
|
||||
@@ -328,9 +313,9 @@ impl InboxView {
|
||||
self.refresh = RefreshGate::default();
|
||||
}
|
||||
|
||||
/// Every event in every group, archived groups included.
|
||||
/// Every notification event in every thread, archived threads included.
|
||||
fn all_notification_events(&self) -> Vec<Event> {
|
||||
self.notifications
|
||||
self.threads
|
||||
.iter()
|
||||
.flat_map(|item| item.events.iter().cloned())
|
||||
.collect()
|
||||
@@ -354,44 +339,36 @@ impl InboxView {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let Some(entry) = section.entries.get(entry_ix) else {
|
||||
let Some(&thread_ix) = section.entries.get(entry_ix) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
match *entry {
|
||||
InboxEntry::Notification(item_ix) => {
|
||||
let Some(item) = self.notifications.get(item_ix) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
let Some(item) = self.threads.get(thread_ix) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let root = item.root;
|
||||
let kind = item.root_kind;
|
||||
let address = section.address.clone();
|
||||
let dock_area = self.dock_area.clone();
|
||||
let root = item.root;
|
||||
let kind = item.root_kind;
|
||||
let address = section.address.clone();
|
||||
let dock_area = self.dock_area.clone();
|
||||
let first = entry_ix == 0;
|
||||
let last = entry_ix + 1 == section.entries.len();
|
||||
|
||||
notification_row("inbox-row", ix, item, cx)
|
||||
.on_click(move |_, window, cx| {
|
||||
open_item(&dock_area, root, kind, address.clone(), window, cx);
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
InboxEntry::Activity(event_ix) => {
|
||||
let Some(event) = self.activity.get(event_ix) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
activity_row(ix, event, cx)
|
||||
}
|
||||
}
|
||||
thread("inbox-row", ix, item, first, last, cx)
|
||||
.on_click(move |_, window, cx| {
|
||||
open_item(&dock_area, root, kind, address.clone(), window, cx);
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Group the notification groups and own activity into one section per repository.
|
||||
fn group_sections(notifications: &[InboxItem], activity: &[Event]) -> Vec<InboxSection> {
|
||||
/// Group the threads into one section per repository.
|
||||
fn group_sections(threads: &[InboxItem]) -> Vec<InboxSection> {
|
||||
let mut by_repo: HashMap<Option<RepoAddr>, InboxSection> = HashMap::new();
|
||||
|
||||
for (ix, item) in notifications.iter().enumerate() {
|
||||
for (ix, item) in threads.iter().enumerate() {
|
||||
if item.archived {
|
||||
continue;
|
||||
}
|
||||
@@ -411,29 +388,16 @@ fn group_sections(notifications: &[InboxItem], activity: &[Event]) -> Vec<InboxS
|
||||
}
|
||||
|
||||
section.latest = section.latest.max(item.latest_activity());
|
||||
section.entries.push(InboxEntry::Notification(ix));
|
||||
}
|
||||
|
||||
for (ix, event) in activity.iter().enumerate() {
|
||||
let address = repo_address(event);
|
||||
let section = by_repo
|
||||
.entry(address.clone())
|
||||
.or_insert_with(move || InboxSection {
|
||||
address,
|
||||
unread: 0,
|
||||
entries: Vec::new(),
|
||||
latest: Timestamp::default(),
|
||||
});
|
||||
|
||||
section.latest = section.latest.max(event.created_at);
|
||||
section.entries.push(InboxEntry::Activity(ix));
|
||||
section.entries.push(ix);
|
||||
}
|
||||
|
||||
let mut sections: Vec<InboxSection> = by_repo.into_values().collect();
|
||||
|
||||
for section in &mut sections {
|
||||
section.entries.sort_by(|a, b| {
|
||||
entry_time(b, notifications, activity).cmp(&entry_time(a, notifications, activity))
|
||||
threads[*b]
|
||||
.latest_activity()
|
||||
.cmp(&threads[*a].latest_activity())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -441,20 +405,6 @@ fn group_sections(notifications: &[InboxItem], activity: &[Event]) -> Vec<InboxS
|
||||
sections
|
||||
}
|
||||
|
||||
/// Timestamp an entry is ordered by.
|
||||
fn entry_time(entry: &InboxEntry, notifications: &[InboxItem], activity: &[Event]) -> Timestamp {
|
||||
match entry {
|
||||
InboxEntry::Notification(ix) => notifications
|
||||
.get(*ix)
|
||||
.map(InboxItem::latest_activity)
|
||||
.unwrap_or_default(),
|
||||
InboxEntry::Activity(ix) => activity
|
||||
.get(*ix)
|
||||
.map(|event| event.created_at)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Flatten the sections into the list of repository headers and their rows.
|
||||
fn flatten_rows(sections: &[InboxSection]) -> Vec<InboxRow> {
|
||||
let mut rows = Vec::new();
|
||||
@@ -475,14 +425,6 @@ fn flatten_rows(sections: &[InboxSection]) -> Vec<InboxRow> {
|
||||
rows
|
||||
}
|
||||
|
||||
/// Repository an activity event belongs to, from its `a` tag.
|
||||
fn repo_address(event: &Event) -> Option<RepoAddr> {
|
||||
event
|
||||
.tags
|
||||
.coordinates()
|
||||
.find(|address| address.kind == Kind::GitRepoAnnouncement)
|
||||
}
|
||||
|
||||
/// Display name of the repository at `addr`, from the announcement store.
|
||||
fn repo_name(addr: Option<&RepoAddr>, cx: &App) -> Option<SharedString> {
|
||||
let repo_list = RepoListStore::global(cx);
|
||||
@@ -526,15 +468,13 @@ fn empty_section_row(cx: &App) -> AnyElement {
|
||||
.w_full()
|
||||
.px_3()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.bg(cx.theme().secondary)
|
||||
.text_color(cx.theme().secondary_foreground)
|
||||
.bg(cx.theme().secondary.alpha(0.6))
|
||||
.rounded(cx.theme().radius)
|
||||
.child(SharedString::from("No activity yet."))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Open the repository of a notification group,
|
||||
/// and the issue or pull request detail when the group's root is one.
|
||||
fn open_item(
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
root: EventId,
|
||||
@@ -572,30 +512,40 @@ fn open_item(
|
||||
open_repo_item(dock_area, store, item, window, cx);
|
||||
}
|
||||
|
||||
fn notification_row(prefix: &'static str, ix: usize, item: &InboxItem, cx: &App) -> Stateful<Div> {
|
||||
let Some(newest) = item.events.first() else {
|
||||
return div().id((prefix, ix));
|
||||
};
|
||||
|
||||
let profile_store = ProfileStore::global(cx);
|
||||
let profile = profile_store.read(cx).get(&newest.pubkey);
|
||||
let author = profile.name();
|
||||
|
||||
let picture = profile.picture();
|
||||
let kind = item.root_kind.unwrap_or(newest.kind);
|
||||
let subject = SharedString::from(activity_subject(newest));
|
||||
let age = relative_time(item.latest_activity());
|
||||
fn thread(
|
||||
prefix: &'static str,
|
||||
ix: usize,
|
||||
item: &InboxItem,
|
||||
first: bool,
|
||||
last: bool,
|
||||
cx: &App,
|
||||
) -> Stateful<Div> {
|
||||
let title = SharedString::from(item.title());
|
||||
let kind = item.kind().unwrap_or(Kind::Comment);
|
||||
let unread = item.is_unread();
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let me = backend.read(cx).current_user();
|
||||
|
||||
let mut timeline = v_flex().gap_2().w_full();
|
||||
|
||||
for event in item.timeline(MAX_SUB_ACTIVITIES) {
|
||||
timeline = timeline.child(sub_activity_line(&event, me, cx));
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.id((prefix, ix))
|
||||
.h_16()
|
||||
.w_full()
|
||||
.px_3()
|
||||
.gap_1()
|
||||
.justify_center()
|
||||
.bg(cx.theme().secondary)
|
||||
.hover(|this| this.bg(cx.theme().secondary_hover))
|
||||
.py_2()
|
||||
.gap_2()
|
||||
.bg(cx.theme().secondary.alpha(0.6))
|
||||
.when(first, |this| this.rounded_t(cx.theme().radius))
|
||||
.when(last, |this| this.rounded_b(cx.theme().radius))
|
||||
.when(!last, |this| {
|
||||
this.border_b_1().border_color(cx.theme().border)
|
||||
})
|
||||
.hover(|this| this.bg(cx.theme().secondary_hover.alpha(0.8)))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
@@ -613,7 +563,7 @@ fn notification_row(prefix: &'static str, ix: usize, item: &InboxItem, cx: &App)
|
||||
.min_w_0()
|
||||
.whitespace_nowrap()
|
||||
.text_ellipsis()
|
||||
.child(subject),
|
||||
.child(title),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.when(unread, |this| {
|
||||
@@ -626,72 +576,44 @@ fn notification_row(prefix: &'static str, ix: usize, item: &InboxItem, cx: &App)
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(div().w_6().flex_shrink_0())
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(UserAvatar::new(author.clone()).picture(picture))
|
||||
.child(author.clone()),
|
||||
)
|
||||
.child("opened")
|
||||
.child(SharedString::from(kind_label(kind)))
|
||||
.child(div().flex_1())
|
||||
.child(SharedString::from(age)),
|
||||
),
|
||||
)
|
||||
.child(timeline)
|
||||
}
|
||||
|
||||
fn activity_row(ix: usize, event: &Event, cx: &App) -> AnyElement {
|
||||
let kind = event.kind;
|
||||
let subject = SharedString::from(activity_subject(event));
|
||||
let age = relative_time(event.created_at);
|
||||
fn sub_activity_line(event: &Event, me: Option<PublicKey>, cx: &App) -> AnyElement {
|
||||
let profile_store = ProfileStore::global(cx).read(cx);
|
||||
let profile = profile_store.get(&event.pubkey);
|
||||
|
||||
v_flex()
|
||||
.id(("activity-row", ix))
|
||||
.h_16()
|
||||
let name = if Some(event.pubkey) == me {
|
||||
SharedString::from("You")
|
||||
} else {
|
||||
profile.name()
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.px_3()
|
||||
.gap_1()
|
||||
.justify_center()
|
||||
.bg(cx.theme().secondary)
|
||||
.hover(|this| this.bg(cx.theme().secondary_hover))
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(div().w_6().flex_shrink_0())
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.whitespace_nowrap()
|
||||
.text_ellipsis()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.text_xs()
|
||||
.child(
|
||||
h_flex()
|
||||
.size_6()
|
||||
.flex_shrink_0()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(kind_icon(kind)),
|
||||
UserAvatar::new(name.clone())
|
||||
.picture(profile.picture())
|
||||
.xsmall(),
|
||||
)
|
||||
.child(subject),
|
||||
)
|
||||
.child(
|
||||
h_flex().gap_2().child(div().w_6().flex_shrink_0()).child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(kind_label(kind)))
|
||||
.child("·")
|
||||
.child(SharedString::from(age)),
|
||||
),
|
||||
.child(name)
|
||||
.child(SharedString::from(activity_phrase(event.kind)))
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(relative_time(event.created_at))),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -718,23 +640,23 @@ fn kind_icon(kind: Kind) -> Icon {
|
||||
.small()
|
||||
}
|
||||
|
||||
/// Short noun for a notification or activity kind.
|
||||
fn kind_label(kind: Kind) -> &'static str {
|
||||
/// Phrase describing an activity event, read as `[name] [phrase]`.
|
||||
fn activity_phrase(kind: Kind) -> &'static str {
|
||||
if kind == COVER_NOTE_KIND {
|
||||
return "note";
|
||||
return "added a note";
|
||||
}
|
||||
|
||||
match kind {
|
||||
Kind::GitIssue => "issue",
|
||||
Kind::GitPullRequest => "PR",
|
||||
Kind::GitPullRequestUpdate => "PR update",
|
||||
Kind::GitPatch => "patch",
|
||||
Kind::Comment => "comment",
|
||||
Kind::GitStatusOpen
|
||||
| Kind::GitStatusApplied
|
||||
| Kind::GitStatusClosed
|
||||
| Kind::GitStatusDraft => "status",
|
||||
_ => "activity",
|
||||
Kind::GitIssue => "opened an issue",
|
||||
Kind::GitPullRequest => "opened a PR",
|
||||
Kind::GitPullRequestUpdate => "updated a PR",
|
||||
Kind::GitPatch => "created a patch",
|
||||
Kind::Comment => "commented",
|
||||
Kind::GitStatusOpen => "opened a status",
|
||||
Kind::GitStatusApplied => "applied a status",
|
||||
Kind::GitStatusClosed => "closed a status",
|
||||
Kind::GitStatusDraft => "drafted a status",
|
||||
_ => "did something",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,20 +740,30 @@ impl Render for InboxView {
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(div().relative().flex_1().min_h_0().px_4().pb_4().when_else(
|
||||
rows.is_empty(),
|
||||
|this| this.child(empty_state(IconName::Inbox, "You're all caught up.", cx)),
|
||||
|this| {
|
||||
this.child(
|
||||
list(
|
||||
self.list.clone(),
|
||||
cx.processor(|this, ix, _window, cx| this.render_entry(ix, cx)),
|
||||
)
|
||||
.size_full()
|
||||
.min_h_0()
|
||||
.into_any_element(),
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.px_4()
|
||||
.when_else(
|
||||
rows.is_empty(),
|
||||
|this| {
|
||||
this.child(empty_state(IconName::Inbox, "You're all caught up.", cx))
|
||||
},
|
||||
|this| {
|
||||
this.child(
|
||||
list(
|
||||
self.list.clone(),
|
||||
cx.processor(|this, ix, _window, cx| this.render_entry(ix, cx)),
|
||||
)
|
||||
.size_full()
|
||||
.min_h_0()
|
||||
.into_any_element(),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
))
|
||||
.child(div().h_6().w_full().flex_shrink_0()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user