1use crate::prelude::Navigation;
4use std::ops::{Deref, DerefMut, Range};
5use std::rc::Rc;
6use yew::prelude::*;
7
8pub const DEFAULT_PER_PAGE: usize = 10;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
12pub struct PaginationControl {
13 pub page: usize,
14 pub per_page: usize,
15}
16
17#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19pub struct PaginationState {
20 pub control: PaginationControl,
21 pub total: Option<usize>,
22}
23
24impl PaginationState {
25 fn change_page(mut self, page: usize) -> Self {
26 self.control.page = page;
27 self
28 }
29
30 fn change_per_page(mut self, per_page: usize) -> Self {
31 let current_offset = self.control.page * self.control.per_page;
33
34 self.control.per_page = per_page.max(1);
35
36 self.control.page = current_offset / self.control.per_page;
38
39 self
40 }
41
42 fn change_total(mut self, total: Option<usize>) -> Self {
43 self.total = total;
45
46 if let Some(total_pages) = self.total_pages() {
48 if total_pages > 0 {
49 self.control.page = self.control.page.min(total_pages - 1);
50 } else {
51 self.control.page = 0;
52 }
53 }
54
55 self
56 }
57
58 pub fn navigate(self, navigation: Navigation) -> Self {
59 let mut newpage = self.control.page;
60 match navigation {
61 Navigation::First => newpage = 0,
62 Navigation::Last => {
63 if let Some(total_pages) = self.total_pages() {
64 newpage = total_pages.saturating_sub(1);
65 }
66 }
67 Navigation::Next => {
68 newpage = newpage.saturating_add(1);
69 if let Some(total_pages) = self.total_pages() {
70 newpage = newpage.min(total_pages.max(1) - 1);
71 }
72 }
73 Navigation::Previous => {
74 newpage = newpage.saturating_sub(1);
75 }
76 Navigation::Page(page) => {
77 if let Some(total_pages) = self.total_pages() {
78 if page < total_pages {
79 newpage = page;
80 }
81 } else {
82 newpage = page;
83 }
84 }
85 };
86
87 self.change_page(newpage)
88 }
89
90 pub fn offset(&self) -> usize {
91 self.control.per_page * self.control.page
92 }
93
94 pub fn range(&self) -> Range<usize> {
95 let start = self.offset();
96 let mut end = start + self.control.per_page;
97 if let Some(total) = self.total {
98 end = end.min(total);
99 }
100
101 Range { start, end }
102 }
103
104 pub fn total_pages(&self) -> Option<usize> {
105 self.total
106 .map(|total| (total + self.control.per_page - 1) / self.control.per_page)
107 }
108}
109
110impl Default for PaginationControl {
111 fn default() -> Self {
112 Self {
113 page: 0,
114 per_page: DEFAULT_PER_PAGE,
115 }
116 }
117}
118
119#[derive(Debug, PartialEq, Clone)]
120pub struct UsePagination {
121 pub state: UseStateHandle<PaginationState>,
122 pub onnavigation: Callback<Navigation>,
123 pub onperpagechange: Callback<usize>,
124}
125
126impl Deref for UsePagination {
127 type Target = PaginationState;
128
129 fn deref(&self) -> &Self::Target {
130 &self.state
131 }
132}
133
134impl Deref for PaginationState {
135 type Target = PaginationControl;
136
137 fn deref(&self) -> &Self::Target {
138 &self.control
139 }
140}
141
142impl DerefMut for PaginationState {
143 fn deref_mut(&mut self) -> &mut Self::Target {
144 &mut self.control
145 }
146}
147
148#[hook]
187pub fn use_pagination<T>(total: Option<usize>, init: T) -> UsePagination
188where
189 T: FnOnce() -> PaginationControl,
190{
191 let state = use_state_eq(|| PaginationState {
192 control: init(),
193 total,
194 });
195
196 use_effect_with((total, state.clone()), move |(total, state)| {
197 state.set((**state).change_total(*total));
198 });
199
200 let onnavigation = use_callback(state.clone(), |nav: Navigation, state| {
201 state.set((**state).navigate(nav))
202 });
203
204 let onperpagechange = use_callback(state.clone(), |per_page, state| {
205 state.set((**state).change_per_page(per_page))
206 });
207
208 UsePagination {
209 state,
210 onnavigation,
211 onperpagechange,
212 }
213}
214
215#[hook]
223fn use_apply_pagination<T>(entries: Rc<Vec<T>>, control: PaginationControl) -> Rc<Vec<T>>
224where
225 T: Clone + PartialEq + 'static,
226{
227 use_memo((entries, control), |(entries, control)| {
228 let offset = control.per_page * control.page;
229 let limit = control.per_page;
230 entries
231 .iter()
232 .skip(offset)
234 .take(limit)
235 .cloned()
236 .collect::<Vec<_>>()
237 })
238}
239
240#[cfg(test)]
241mod test {
242
243 use super::*;
244
245 fn state(page: usize, per_page: usize, total: Option<usize>) -> PaginationState {
246 PaginationState {
247 control: PaginationControl { per_page, page },
248 total,
249 }
250 }
251
252 #[test]
253 fn test_navigate() {
254 let state = state(0, 10, Some(23));
255 assert_eq!(state.total_pages(), Some(3));
256 assert_eq!(state.control.page, 0);
257 assert_eq!(state.offset(), 0);
258 assert_eq!(state.range(), 0..10);
259
260 let state = state.navigate(Navigation::First);
261 assert_eq!(state.total_pages(), Some(3));
262 assert_eq!(state.control.page, 0);
263 assert_eq!(state.offset(), 0);
264 assert_eq!(state.range(), 0..10);
265
266 let state = state.navigate(Navigation::Last);
267 assert_eq!(state.total_pages(), Some(3));
268 assert_eq!(state.control.page, 2);
269 assert_eq!(state.offset(), 20);
270 assert_eq!(state.range(), 20..23);
271
272 let state = state.navigate(Navigation::Previous);
273 assert_eq!(state.total_pages(), Some(3));
274 assert_eq!(state.control.page, 1);
275 assert_eq!(state.offset(), 10);
276 assert_eq!(state.range(), 10..20);
277
278 let state = state.navigate(Navigation::Previous);
279 assert_eq!(state.total_pages(), Some(3));
280 assert_eq!(state.control.page, 0);
281 assert_eq!(state.offset(), 0);
282 assert_eq!(state.range(), 0..10);
283 }
284
285 #[test]
287 fn test_underflow() {
288 let state = state(0, 10, Some(23));
289
290 let state = state.navigate(Navigation::Previous);
291 assert_eq!(state.total_pages(), Some(3));
292 assert_eq!(state.control.page, 0);
293 assert_eq!(state.offset(), 0);
294 assert_eq!(state.range(), 0..10);
295 }
296
297 #[test]
299 fn test_overflow_1() {
300 let state = state(0, 10, Some(23));
301
302 let state = state.navigate(Navigation::Last);
303 assert_eq!(state.total_pages(), Some(3));
304 assert_eq!(state.control.page, 2);
305 assert_eq!(state.offset(), 20);
306 assert_eq!(state.range(), 20..23);
307
308 let state = state.navigate(Navigation::Next);
309 assert_eq!(state.total_pages(), Some(3));
310 assert_eq!(state.control.page, 2);
311 assert_eq!(state.offset(), 20);
312 assert_eq!(state.range(), 20..23);
313 }
314
315 #[test]
317 fn test_overflow_2() {
318 let state = state(0, 10, Some(23));
319 assert_eq!(state.total_pages(), Some(3));
320
321 let state = state.navigate(Navigation::Page(5));
322 assert_eq!(state.total_pages(), Some(3));
323 assert_eq!(state.control.page, 0);
324 assert_eq!(state.offset(), 0);
325 assert_eq!(state.range(), 0..10);
326 }
327
328 #[test]
329 fn test_change_page_size() {
330 let state = state(0, 10, Some(23));
331 assert_eq!(state.total_pages(), Some(3));
332 assert_eq!(state.control.page, 0);
333 assert_eq!(state.offset(), 0);
334 assert_eq!(state.range(), 0..10);
335
336 let state = state.navigate(Navigation::Next);
337 assert_eq!(state.total_pages(), Some(3));
338 assert_eq!(state.control.page, 1);
339 assert_eq!(state.offset(), 10);
340 assert_eq!(state.range(), 10..20);
341
342 let state = state.change_per_page(5);
343 assert_eq!(state.total_pages(), Some(5));
344 assert_eq!(state.control.page, 2);
345 assert_eq!(state.offset(), 10);
346 assert_eq!(state.range(), 10..15);
347 }
348
349 #[test]
350 fn test_change_none() {
351 let state = state(0, 10, None);
352 assert_eq!(state.total_pages(), None);
353 assert_eq!(state.control.page, 0);
354 assert_eq!(state.offset(), 0);
355 assert_eq!(state.range(), 0..10);
356 }
357
358 #[test]
359 fn test_change_empty() {
360 let state = state(0, 10, Some(0));
361 assert_eq!(state.total_pages(), Some(0));
362 assert_eq!(state.control.page, 0);
363 assert_eq!(state.offset(), 0);
364 assert_eq!(state.range(), 0..0);
365 }
366
367 #[test]
368 fn test_total_pages() {
369 for i in 0..100 {
370 let state = state(0, 10, Some(i));
371 assert_eq!(
372 state.total_pages(),
373 Some((i as f64 / 10f64).ceil() as usize)
374 );
375 }
376 }
377}