use crate::Page;
use crate::PageRequest;
use leptos::html::Div;
use leptos::prelude::*;
use serde::{de::DeserializeOwned, Serialize};
use std::future::Future;
pub struct UsePagedInfiniteScroll<T: Send + Sync + 'static> {
pub items: RwSignal<Vec<T>>,
pub has_more: RwSignal<bool>,
pub loading: RwSignal<bool>,
pub ever_loaded: RwSignal<bool>,
pub total_count: RwSignal<Option<u64>>,
pub next_request_offset: RwSignal<u32>,
pub scroll_el: NodeRef<Div>,
}
impl<T: Send + Sync + 'static> UsePagedInfiniteScroll<T> {
pub fn reset(&self) {
self.items.set(Vec::new());
self.has_more.set(true);
self.ever_loaded.set(false);
self.loading.set(false);
self.total_count.set(None);
self.next_request_offset.set(0);
}
}
pub fn page_fetch_from_tuple<F, Fut>(
f: F,
) -> impl Fn(PageRequest) -> Fut + Clone + Send + Sync + 'static
where
F: Fn(u32, u32) -> Fut + Clone + Send + Sync + 'static,
Fut: Future + Send + 'static,
{
move |request: PageRequest| f(request.offset, request.limit)
}
pub fn use_paged_infinite_scroll<T, Fut, F>(
scroll_el: NodeRef<Div>,
page_size: u32,
refresh: Signal<u32>,
fetch: F,
) -> UsePagedInfiniteScroll<T>
where
T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
Fut: Future<Output = Result<Page<T>, ServerFnError>> + Send + 'static,
F: Fn(PageRequest) -> Fut + Send + Sync + Clone + 'static,
{
let items = RwSignal::new(Vec::<T>::new());
let has_more = RwSignal::new(false);
let ever_loaded = RwSignal::new(false);
let loading = RwSignal::new(false);
let total_count = RwSignal::new(None::<u64>);
let next_request_offset = RwSignal::new(0u32);
let skip_initial_refresh_reset = StoredValue::new(true);
Effect::new(move || {
let _ = refresh.get();
if skip_initial_refresh_reset.get_value() {
skip_initial_refresh_reset.set_value(false);
return;
}
items.set(Vec::new());
has_more.set(false);
ever_loaded.set(false);
loading.set(false);
total_count.set(None);
next_request_offset.set(0);
});
let fetch_first = fetch.clone();
let first_page = Resource::new(
move || refresh.get(),
move |_| {
let fetch = fetch_first.clone();
async move { fetch(PageRequest::new(0, page_size)).await }
},
);
Effect::new(move || {
if let Some(Ok(page)) = first_page.get() {
has_more.set(page.has_more);
ever_loaded.set(true);
if let Some(count) = page.total_count {
total_count.set(Some(count));
}
items.set(page.items.clone());
next_request_offset.set(page.next_request_offset.unwrap_or(page.items.len() as u32));
}
});
#[cfg(not(feature = "hydrate"))]
Effect::new(move || {
loading.set(first_page.get().is_none());
});
#[cfg(feature = "hydrate")]
{
use leptos_use::{use_infinite_scroll_with_options, UseInfiniteScrollOptions};
let hook_loading =
use_infinite_scroll_with_options(
scroll_el,
move |_| {
let fetch = fetch.clone();
async move {
if !has_more.get_untracked() {
return;
}
let request_offset = next_request_offset.get_untracked();
match fetch(PageRequest::new(request_offset, page_size)).await {
Ok(page) => {
has_more.set(page.has_more);
ever_loaded.set(true);
if let Some(count) = page.total_count {
total_count.set(Some(count));
}
items.update(|existing| existing.extend(page.items.clone()));
next_request_offset.set(page.next_request_offset.unwrap_or(
request_offset.saturating_add(page.items.len() as u32),
));
}
Err(_) => {
ever_loaded.set(true);
has_more.set(false);
}
}
}
},
UseInfiniteScrollOptions::default().distance(10.0),
);
Effect::new(move || {
loading.set(first_page.get().is_none() || hook_loading.get());
});
}
UsePagedInfiniteScroll {
items,
has_more,
loading,
ever_loaded,
total_count,
next_request_offset,
scroll_el,
}
}