use std::{fmt::Debug, ops::Range};
use leptos::prelude::*;
use crate::{InternalLoader, ItemWindow, cache::Cache};
#[must_use]
pub fn use_load_on_demand<T, L, Q, E, M>(
range_to_load: impl Into<Signal<Range<usize>>>,
range_to_display: impl Into<Signal<Range<usize>>>,
loader: L,
query: impl Into<Signal<Q>>,
) -> UseLoadOnDemandResult<T, E>
where
T: Send + Sync + 'static,
L: InternalLoader<M, Item = T, Query = Q, Error = E> + 'static,
Q: Send + Sync + 'static,
E: Send + Sync + Debug + 'static,
{
#[cfg(not(feature = "ssr"))]
{
use leptos::task::spawn_local;
use leptos_use::{WatchPausableReturn, watch_pausable};
let range_to_load = range_to_load.into();
let range_to_display = range_to_display.into();
let cached_range_to_display = RwSignal::new(0..0);
let mut cache = Cache::new();
let loader = StoredValue::new_local(loader);
let query = query.into();
let item_count_result = RwSignal::new(Ok(None));
let set_item_count = move |count: Result<Option<usize>, E>| {
cache
.item_count()
.set(count.as_ref().ok().flatten().copied());
item_count_result.set(count);
};
let reload_counter = RwSignal::new(0_usize);
Effect::new(move || {
query.track();
cache.clear();
reload_counter.update(|counter| *counter = counter.wrapping_add(1));
});
Effect::new(move || {
reload_counter.track();
spawn_local(async move {
let latest_reload_count = reload_counter.try_get_untracked();
let count = loader
.read_value()
.item_count(&*query.read_untracked())
.await;
if latest_reload_count == reload_counter.try_get_untracked() {
set_item_count(count);
}
});
});
let WatchPausableReturn {
pause,
resume,
is_active,
..
} = watch_pausable(
move || {
reload_counter.track();
cache.track();
},
move |_, _, _| {
let missing_range = cache.missing_range(range_to_load.get());
if let Some(missing_range) = missing_range {
cache.write_loading(missing_range.clone());
spawn_local(async move {
let latest_reload_count = reload_counter.try_get_untracked();
let result = loader
.read_value()
.load_items(missing_range.clone(), &*query.read_untracked())
.await;
if latest_reload_count == reload_counter.try_get_untracked() {
if let Ok(loaded_items) = &result
&& loaded_items.range.end < missing_range.end
{
set_item_count(Ok(Some(loaded_items.range.end)));
}
cache.write_loaded(result.map_err(|e| format!("{e:?}")), missing_range);
}
});
}
let Range { start, end } = range_to_display.get();
cached_range_to_display
.set(start..end.min(cache.item_count().get().unwrap_or(usize::MAX)));
},
);
cache.pause_reactive_loading = pause.into();
cache.resume_reactive_loading = resume.into();
cache.is_reactive_loading_active = is_active;
UseLoadOnDemandResult {
item_count_result: item_count_result.into(),
item_window: ItemWindow {
cache,
range: cached_range_to_display.into(),
},
}
}
#[cfg(feature = "ssr")]
{
let _ = range_to_load;
let _ = range_to_display;
let _ = loader;
let _ = query;
UseLoadOnDemandResult {
item_count_result: Signal::stored(Ok(None)),
item_window: ItemWindow {
cache: Cache::new(),
range: Signal::stored(0..0),
},
}
}
}
pub struct UseLoadOnDemandResult<T, E>
where
T: Send + Sync + 'static,
E: Send + Sync + Debug + 'static,
{
pub item_count_result: Signal<Result<Option<usize>, E>>,
pub item_window: ItemWindow<T>,
}
impl<T, E> Clone for UseLoadOnDemandResult<T, E>
where
T: Send + Sync + 'static,
E: Send + Sync + Debug + 'static,
{
fn clone(&self) -> Self {
*self
}
}
impl<T, E> Copy for UseLoadOnDemandResult<T, E>
where
T: Send + Sync + 'static,
E: Send + Sync + Debug + 'static,
{
}