1use std::{fmt::Debug, ops::Range};
2
3use leptos::prelude::*;
4
5use crate::{InternalLoader, ItemWindow, cache::Cache};
6
7#[must_use]
28pub fn use_load_on_demand<T, L, Q, E, M>(
29 range_to_load: impl Into<Signal<Range<usize>>>,
30 range_to_display: impl Into<Signal<Range<usize>>>,
31 loader: L,
32 query: impl Into<Signal<Q>>,
33) -> UseLoadOnDemandResult<T, E>
34where
35 T: Send + Sync + 'static,
36 L: InternalLoader<M, Item = T, Query = Q, Error = E> + 'static,
37 Q: Send + Sync + 'static,
38 E: Send + Sync + Debug + 'static,
39{
40 #[cfg(not(feature = "ssr"))]
41 {
42 use crate::cache::CacheStoreFields;
43 use leptos::task::spawn_local;
44
45 let range_to_load = range_to_load.into();
46 let range_to_display = range_to_display.into();
47
48 let cached_range_to_display = RwSignal::new(0..0);
49
50 let cache = Cache::new_store();
51
52 let loader = Signal::stored_local(loader);
53 let query = query.into();
54
55 let item_count_result = RwSignal::new(Ok(None));
56
57 let set_item_count = move |count: Result<Option<usize>, E>| {
58 cache
59 .item_count()
60 .set(count.as_ref().ok().flatten().copied());
61 item_count_result.set(count);
62 };
63
64 let reload_counter = RwSignal::new(0_usize);
65
66 Effect::new(move || {
68 query.track();
69 Cache::clear(cache);
70 reload_counter.update(|counter| *counter = counter.wrapping_add(1));
71 });
72
73 Effect::new(move || {
75 reload_counter.track();
78
79 spawn_local(async move {
80 let latest_reload_count = reload_counter.try_get_untracked();
81
82 let count = loader.read().item_count(&*query.read_untracked()).await;
83
84 if latest_reload_count == reload_counter.try_get_untracked() {
86 set_item_count(count);
87 }
88 });
89 });
90
91 Effect::new(move || {
93 reload_counter.track();
95
96 let missing_range = cache.read().missing_range(range_to_load.get());
97
98 if let Some(missing_range) = missing_range {
99 Cache::write_loading(cache, missing_range.clone());
100
101 spawn_local(async move {
102 let latest_reload_count = reload_counter.try_get_untracked();
103
104 let result = loader
105 .read()
106 .load_items(missing_range.clone(), &*query.read_untracked())
107 .await;
108
109 if latest_reload_count == reload_counter.try_get_untracked() {
111 if let Ok(loaded_items) = &result
112 && loaded_items.range.end < missing_range.end
113 {
114 set_item_count(Ok(Some(loaded_items.range.end)));
115 }
116
117 Cache::write_loaded(
118 cache,
119 result.map_err(|e| format!("{e:?}")),
120 missing_range,
121 );
122 }
123 });
124 }
125
126 let Range { start, end } = range_to_display.get();
128 cached_range_to_display
129 .set(start..end.min(cache.item_count().get().unwrap_or(usize::MAX)));
130 });
131
132 UseLoadOnDemandResult {
133 item_count_result: item_count_result.into(),
134 item_window: ItemWindow {
135 cache,
136 range: cached_range_to_display.into(),
137 },
138 }
139 }
140
141 #[cfg(feature = "ssr")]
142 {
143 let _ = range_to_load;
144 let _ = range_to_display;
145 let _ = loader;
146 let _ = query;
147
148 UseLoadOnDemandResult {
149 item_count_result: Signal::stored(Ok(None)),
150 item_window: ItemWindow {
151 cache: Cache::new_store(),
152 range: Signal::stored(0..0),
153 },
154 }
155 }
156}
157
158pub struct UseLoadOnDemandResult<T, E>
160where
161 T: Send + Sync + 'static,
162 E: Send + Sync + Debug + 'static,
163{
164 pub item_count_result: Signal<Result<Option<usize>, E>>,
165 pub item_window: ItemWindow<T>,
166}