Skip to content

Commit 32ebf82

Browse files
feat(ui): better pagination buttons
1 parent 2dd172c commit 32ebf82

File tree

2 files changed

+95
-75
lines changed

2 files changed

+95
-75
lines changed
Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,54 @@
1-
import { Button, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
2-
import { useGalleryPagination } from 'features/gallery/hooks/useGalleryPagination';
3-
import { PiCaretDoubleLeftBold, PiCaretDoubleRightBold, PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
1+
import { Button, Flex, IconButton, Spacer } from '@invoke-ai/ui-library';
2+
import { ELLIPSIS, useGalleryPagination } from 'features/gallery/hooks/useGalleryPagination';
3+
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
44

55
export const GalleryPagination = () => {
6-
const {
7-
goPrev,
8-
goNext,
9-
goToFirst,
10-
goToLast,
11-
isFirstEnabled,
12-
isLastEnabled,
13-
isPrevEnabled,
14-
isNextEnabled,
15-
pageButtons,
16-
goToPage,
17-
currentPage,
18-
rangeDisplay,
19-
total,
20-
} = useGalleryPagination();
6+
const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, total } =
7+
useGalleryPagination();
218

229
if (!total) {
23-
return <Flex flexDir="column" alignItems="center" gap="2" height="48px"></Flex>;
10+
return null;
2411
}
2512

2613
return (
27-
<Flex flexDir="column" alignItems="center" gap="2" height="48px">
28-
<Flex gap={2} alignItems="center" w="full">
29-
<IconButton
30-
size="sm"
31-
aria-label="prev"
32-
icon={<PiCaretDoubleLeftBold />}
33-
onClick={goToFirst}
34-
isDisabled={!isFirstEnabled}
35-
/>
36-
<IconButton
37-
size="sm"
38-
aria-label="prev"
39-
icon={<PiCaretLeftBold />}
40-
onClick={goPrev}
41-
isDisabled={!isPrevEnabled}
42-
/>
43-
<Spacer />
44-
{pageButtons.map((page) => (
14+
<Flex gap={2} alignItems="center" w="full">
15+
<IconButton
16+
size="sm"
17+
aria-label="prev"
18+
icon={<PiCaretLeftBold />}
19+
onClick={goPrev}
20+
isDisabled={!isPrevEnabled}
21+
variant="ghost"
22+
/>
23+
<Spacer />
24+
{pageButtons.map((page, i) => {
25+
if (page === ELLIPSIS) {
26+
return (
27+
<Button size="sm" key={`ellipsis_${i}`} variant="link" isDisabled>
28+
...
29+
</Button>
30+
);
31+
}
32+
return (
4533
<Button
4634
size="sm"
4735
key={page}
48-
onClick={goToPage.bind(null, page)}
49-
variant={currentPage === page ? 'solid' : 'outline'}
36+
onClick={goToPage.bind(null, page - 1)}
37+
variant={currentPage === page - 1 ? 'solid' : 'outline'}
5038
>
51-
{page + 1}
39+
{page}
5240
</Button>
53-
))}
54-
<Spacer />
55-
<IconButton
56-
size="sm"
57-
aria-label="next"
58-
icon={<PiCaretRightBold />}
59-
onClick={goNext}
60-
isDisabled={!isNextEnabled}
61-
/>
62-
<IconButton
63-
size="sm"
64-
aria-label="next"
65-
icon={<PiCaretDoubleRightBold />}
66-
onClick={goToLast}
67-
isDisabled={!isLastEnabled}
68-
/>
69-
</Flex>
70-
<Text>{rangeDisplay}</Text>
41+
);
42+
})}
43+
<Spacer />
44+
<IconButton
45+
size="sm"
46+
aria-label="next"
47+
icon={<PiCaretRightBold />}
48+
onClick={goNext}
49+
isDisabled={!isNextEnabled}
50+
variant="ghost"
51+
/>
7152
</Flex>
7253
);
7354
};

invokeai/frontend/web/src/features/gallery/hooks/useGalleryPagination.ts

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,58 @@ import { offsetChanged } from 'features/gallery/store/gallerySlice';
44
import { useCallback, useEffect, useMemo } from 'react';
55
import { useListImagesQuery } from 'services/api/endpoints/images';
66

7-
export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
7+
// Some logic copied from https://github.com/chakra-ui/zag/blob/1925b7342dc76fb06a7ec59a5a4c0063a4620422/packages/machines/pagination/src/pagination.utils.ts
8+
9+
export const range = (start: number, end: number) => {
10+
const length = end - start + 1;
11+
return Array.from({ length }, (_, idx) => idx + start);
12+
};
13+
14+
export const ELLIPSIS = 'ellipsis' as const;
15+
16+
export const getRange = (currentPage: number, totalPages: number, siblingCount: number) => {
17+
/**
18+
* `2 * ctx.siblingCount + 5` explanation:
19+
* 2 * ctx.siblingCount for left/right siblings
20+
* 5 for 2x left/right ellipsis, 2x first/last page + 1x current page
21+
*
22+
* For some page counts (e.g. totalPages: 8, siblingCount: 2),
23+
* calculated max page is higher than total pages,
24+
* so we need to take the minimum of both.
25+
*/
26+
const totalPageNumbers = Math.min(2 * siblingCount + 5, totalPages);
27+
28+
const firstPageIndex = 1;
29+
const lastPageIndex = totalPages;
30+
31+
const leftSiblingIndex = Math.max(currentPage - siblingCount, firstPageIndex);
32+
const rightSiblingIndex = Math.min(currentPage + siblingCount, lastPageIndex);
33+
34+
const showLeftEllipsis = leftSiblingIndex > firstPageIndex + 1;
35+
const showRightEllipsis = rightSiblingIndex < lastPageIndex - 1;
36+
37+
const itemCount = totalPageNumbers - 2; // 2 stands for one ellipsis and either first or last page
38+
39+
if (!showLeftEllipsis && showRightEllipsis) {
40+
const leftRange = range(1, itemCount);
41+
return [...leftRange, ELLIPSIS, lastPageIndex];
42+
}
43+
44+
if (showLeftEllipsis && !showRightEllipsis) {
45+
const rightRange = range(lastPageIndex - itemCount + 1, lastPageIndex);
46+
return [firstPageIndex, ELLIPSIS, ...rightRange];
47+
}
48+
49+
if (showLeftEllipsis && showRightEllipsis) {
50+
const middleRange = range(leftSiblingIndex, rightSiblingIndex);
51+
return [firstPageIndex, ELLIPSIS, ...middleRange, ELLIPSIS, lastPageIndex];
52+
}
53+
54+
const fullRange = range(firstPageIndex, lastPageIndex);
55+
return fullRange as (number | 'ellipsis')[];
56+
};
57+
58+
export const useGalleryPagination = () => {
859
const dispatch = useAppDispatch();
960
const { offset, limit } = useAppSelector((s) => s.gallery);
1061
const queryArgs = useAppSelector(selectListImagesQueryArgs);
@@ -57,24 +108,12 @@ export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
57108
}
58109
}, [currentPage, pages, goToLast]);
59110

60-
// calculate the page buttons to display - current page with 3 around it
61111
const pageButtons = useMemo(() => {
62-
const buttons = [];
63-
const maxPageButtons = pageButtonsPerSide * 2 + 1;
64-
let startPage = Math.max(currentPage - Math.floor(maxPageButtons / 2), 0);
65-
const endPage = Math.min(startPage + maxPageButtons - 1, pages - 1);
66-
67-
if (endPage - startPage < maxPageButtons - 1) {
68-
startPage = Math.max(endPage - maxPageButtons + 1, 0);
112+
if (pages > 7) {
113+
return getRange(currentPage + 1, pages, 1);
69114
}
70-
71-
for (let i = startPage; i <= endPage; i++) {
72-
buttons.push(i);
73-
}
74-
75-
return buttons;
76-
}, [currentPage, pageButtonsPerSide, pages]);
77-
115+
return range(1, pages);
116+
}, [currentPage, pages]);
78117
const isFirstEnabled = useMemo(() => currentPage > 0, [currentPage]);
79118
const isLastEnabled = useMemo(() => currentPage < pages - 1, [currentPage, pages]);
80119

0 commit comments

Comments
 (0)