diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index 91118801..17bfc95b 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -1,12 +1,16 @@ +use std::time::Duration; + use fake::Fake; use gpui::{ - div, img, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, View, ViewContext, - VisualContext as _, WindowContext, + div, img, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Timer, View, + ViewContext, VisualContext as _, WindowContext, }; use ui::{ checkbox::Checkbox, h_flex, + indicator::Indicator, label::Label, + prelude::FluentBuilder as _, table::{ColSort, Table, TableDelegate, TableEvent}, theme::ActiveTheme as _, v_flex, Icon, IconName, Selectable, @@ -84,6 +88,8 @@ struct CustomerTableDelegate { col_order: bool, col_sort: bool, col_selection: bool, + loading: bool, + is_eof: bool, } impl CustomerTableDelegate { @@ -111,6 +117,8 @@ impl CustomerTableDelegate { col_order: true, col_sort: true, col_selection: true, + loading: false, + is_eof: false, } } } @@ -322,6 +330,32 @@ impl TableDelegate for CustomerTableDelegate { } } } + + fn can_load_more(&self) -> bool { + return !self.loading && !self.is_eof; + } + + fn load_more_threshold(&self) -> usize { + 150 + } + + fn load_more(&mut self, cx: &mut ViewContext>) { + self.loading = true; + + cx.spawn(|view, mut cx| async move { + // Simulate network request, delay 1s to load data. + Timer::after(Duration::from_secs(1)).await; + + cx.update(|cx| { + let _ = view.update(cx, |view, _| { + view.delegate_mut().customers.extend(randome_customers(200)); + view.delegate_mut().loading = false; + view.delegate_mut().is_eof = view.delegate().customers.len() >= 6000; + }); + }) + }) + .detach(); + } } pub struct TableStory { @@ -438,7 +472,12 @@ impl Render for TableStory { .label("Column Selection") .selected(delegate.col_selection) .on_click(cx.listener(Self::toggle_col_selection)), - ), + ) + .when(delegate.loading, |this| { + this.child(h_flex().gap_1().child(Indicator::new()).child("Loading...")) + }) + .child(format!("Total Rows: {}", delegate.rows_count())) + .when(delegate.is_eof, |this| this.child("Is loaded all data.")), ) .child(self.table.clone()) } diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs index 40799fd5..0359fcfa 100644 --- a/crates/ui/src/table.rs +++ b/crates/ui/src/table.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, rc::Rc}; +use std::{cell::Cell, ops::Range, rc::Rc}; use crate::{ h_flex, @@ -198,6 +198,29 @@ pub trait TableDelegate: Sized + 'static { .child(Icon::new(IconName::Inbox).size_12()) .into_any_element() } + + /// Return true to enable load more data when scrolling to the bottom. + /// + /// Default: true + fn can_load_more(&self) -> bool { + true + } + + /// Returns a threshold value (n rows), of course, when scrolling to the bottom, + /// the remaining number of rows triggers `load_more`. + /// + /// Default: 50 rows + fn load_more_threshold(&self) -> usize { + 50 + } + + /// Load more data when the table is scrolled to the bottom. + /// + /// This will performed in a background task. + /// + /// This is always called when the table is near the bottom, + /// so you must check if there is more data to load or lock the loading state. + fn load_more(&mut self, cx: &mut ViewContext>) {} } impl Table @@ -650,6 +673,24 @@ where }) } + /// Dispatch delegate's `load_more` method when the visible range is near the end. + fn load_more(&mut self, visible_range: Range, cx: &mut ViewContext) { + if !self.delegate.can_load_more() { + return; + } + + if visible_range.end >= self.delegate.rows_count() - self.delegate.load_more_threshold() { + cx.spawn(|view, mut cx| async move { + cx.update(|cx| { + view.update(cx, |view, cx| { + view.delegate.load_more(cx); + }) + }) + }) + .detach(); + } + } + fn move_col(&mut self, col_ix: usize, to_ix: usize, cx: &mut ViewContext) { if col_ix == to_ix { return; @@ -746,6 +787,8 @@ where uniform_list(view, "table-uniform-list", rows_count, { let horizontal_scroll_handle = horizontal_scroll_handle.clone(); move |table, visible_range, cx| { + table.load_more(visible_range.clone(), cx); + visible_range .map(|row_ix| { table