some cleanup, add page buttons

This commit is contained in:
Mary Hipp 2024-06-20 13:29:16 -04:00 committed by psychedelicious
parent 98c77a3ed1
commit 5101dc4bef
6 changed files with 206 additions and 142 deletions

View File

@ -1,24 +1,17 @@
import { Box, Flex, Grid, IconButton } from '@invoke-ai/ui-library';
import { Box, Flex, Grid } from '@invoke-ai/ui-library';
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { useGalleryHotkeys } from 'features/gallery/hooks/useGalleryHotkeys';
import { useGalleryPagination } from 'features/gallery/hooks/useGalleryImages';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import {
PiCaretDoubleLeftBold,
PiCaretDoubleRightBold,
PiCaretLeftBold,
PiCaretRightBold,
PiImageBold,
PiWarningCircleBold,
} from 'react-icons/pi';
import { PiImageBold, PiWarningCircleBold } from 'react-icons/pi';
import type { ImageDTO } from 'services/api/types';
import GalleryImage from './GalleryImage';
import { useListImagesQuery } from '../../../../services/api/endpoints/images';
import { GalleryPagination } from './GalleryPagination';
export const imageListContainerTestId = 'image-list-container';
export const imageItemContainerTestId = 'image-item-container';
@ -90,18 +83,3 @@ const GalleryImageContainer = memo(({ imageDTO, index }: { imageDTO: ImageDTO; i
});
GalleryImageContainer.displayName = 'GalleryImageContainer';
const GalleryPagination = memo(() => {
const { first, prev, next, last, isFirstEnabled, isPrevEnabled, isNextEnabled, isLastEnabled } =
useGalleryPagination();
return (
<Flex gap={2}>
<IconButton aria-label="prev" icon={<PiCaretDoubleLeftBold />} onClick={first} isDisabled={!isFirstEnabled} />
<IconButton aria-label="prev" icon={<PiCaretLeftBold />} onClick={prev} isDisabled={!isPrevEnabled} />
<IconButton aria-label="next" icon={<PiCaretRightBold />} onClick={next} isDisabled={!isNextEnabled} />
<IconButton aria-label="next" icon={<PiCaretDoubleRightBold />} onClick={last} isDisabled={!isLastEnabled} />
</Flex>
);
});
GalleryPagination.displayName = 'GalleryPagination';

View File

@ -0,0 +1,45 @@
import { Button, Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useGalleryPagination } from '../../hooks/useGalleryPagination';
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
export const GalleryPagination = () => {
const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, rangeDisplay } =
useGalleryPagination();
console.log({ currentPage, pageButtons });
return (
<Flex flexDir="column" alignItems="center" gap="2">
<Flex gap={2} alignItems="flex-end">
<IconButton
size="sm"
aria-label="prev"
icon={<PiCaretLeftBold />}
onClick={goPrev}
isDisabled={!isPrevEnabled}
/>
{pageButtons.map((page) =>
typeof page === 'number' ? (
<Button
size="sm"
key={page}
onClick={goToPage.bind(null, page)}
variant={currentPage === page ? 'solid' : 'outline'}
>
{page + 1}
</Button>
) : (
<Text fontSize="md">...</Text>
)
)}
<IconButton
size="sm"
aria-label="next"
icon={<PiCaretRightBold />}
onClick={goNext}
isDisabled={!isNextEnabled}
/>
</Flex>
<Text>{rangeDisplay} Images</Text>
</Flex>
);
};

View File

@ -1,10 +1,11 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, Flex, IconButton, Spinner } from '@invoke-ai/ui-library';
import { useGalleryImages, useGalleryPagination } from 'features/gallery/hooks/useGalleryImages';
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
import { useGalleryNavigation } from 'features/gallery/hooks/useGalleryNavigation';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCaretDoubleRightBold, PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
import { useGalleryPagination } from '../hooks/useGalleryPagination';
const nextPrevButtonStyles: ChakraProps['sx'] = {
color: 'base.100',
@ -17,7 +18,7 @@ const NextPrevImageButtons = () => {
const { prevImage, nextImage, isOnFirstImage, isOnLastImage } = useGalleryNavigation();
const { isFetching } = useGalleryImages().queryResult;
const { isNextEnabled, next } = useGalleryPagination();
const { isNextEnabled, goNext } = useGalleryPagination();
return (
<Box pos="relative" h="full" w="full">
@ -49,7 +50,7 @@ const NextPrevImageButtons = () => {
aria-label={t('accessibility.loadMore')}
icon={<PiCaretDoubleRightBold size={64} />}
variant="unstyled"
onClick={next}
onClick={goNext}
boxSize={16}
sx={nextPrevButtonStyles}
/>

View File

@ -1,6 +1,6 @@
import { useAppSelector } from 'app/store/storeHooks';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { useGalleryPagination } from 'features/gallery/hooks/useGalleryImages';
import { useGalleryPagination } from 'features/gallery/hooks/useGalleryPagination';
import { useGalleryNavigation } from 'features/gallery/hooks/useGalleryNavigation';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useMemo } from 'react';
@ -19,7 +19,7 @@ export const useGalleryHotkeys = () => {
return activeTabName !== 'canvas' || !isStaging;
}, [activeTabName, isStaging]);
const { next, prev, isNextEnabled, isPrevEnabled } = useGalleryPagination();
const { goNext, goPrev, isNextEnabled, isPrevEnabled } = useGalleryPagination();
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const queryResult = useListImagesQuery(queryArgs);
@ -37,12 +37,12 @@ export const useGalleryHotkeys = () => {
['left', 'alt+left'],
(e) => {
if (isOnFirstImageOfView && isPrevEnabled && !queryResult.isFetching) {
prev();
goPrev();
return;
}
canNavigateGallery && handleLeftImage(e.altKey);
},
[handleLeftImage, canNavigateGallery, isOnFirstImageOfView]
[handleLeftImage, canNavigateGallery, isOnFirstImageOfView, goPrev, isPrevEnabled, queryResult.isFetching]
);
useHotkeys(
@ -52,14 +52,14 @@ export const useGalleryHotkeys = () => {
return;
}
if (isOnLastImageOfView && isNextEnabled && !queryResult.isFetching) {
next();
goNext();
return;
}
if (!isOnLastImageOfView) {
handleRightImage(e.altKey);
}
},
[isOnLastImageOfView, next, isNextEnabled, queryResult.isFetching, handleRightImage, canNavigateGallery]
[isOnLastImageOfView, goNext, isNextEnabled, queryResult.isFetching, handleRightImage, canNavigateGallery]
);
useHotkeys(
@ -75,12 +75,12 @@ export const useGalleryHotkeys = () => {
['down', 'alt+down'],
(e) => {
if (!areImagesBelowCurrent && isNextEnabled && !queryResult.isFetching) {
next();
goNext();
return;
}
handleDownImage(e.altKey);
},
{ preventDefault: true },
[areImagesBelowCurrent, next, isNextEnabled, queryResult.isFetching, handleDownImage]
[areImagesBelowCurrent, goNext, isNextEnabled, queryResult.isFetching, handleDownImage]
);
};

View File

@ -1,13 +1,9 @@
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { offsetChanged } from 'features/gallery/store/gallerySlice';
import { useCallback, useMemo } from 'react';
import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
import { useMemo } from 'react';
import { useListImagesQuery } from 'services/api/endpoints/images';
const LIMIT = 20;
export const useGalleryImages = () => {
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const queryResult = useListImagesQuery(queryArgs);
@ -18,103 +14,3 @@ export const useGalleryImages = () => {
};
};
export const useGalleryPagination = () => {
const dispatch = useAppDispatch();
const offset = useAppSelector((s) => s.gallery.offset);
const galleryView = useAppSelector((s) => s.gallery.galleryView);
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { count } = useListImagesQuery(queryArgs, {
selectFromResult: ({ data }) => ({ count: data?.items.length ?? 0 }),
});
const { data: assetsTotal } = useGetBoardAssetsTotalQuery(selectedBoardId);
const { data: imagesTotal } = useGetBoardImagesTotalQuery(selectedBoardId);
const total = useMemo(() => {
if (galleryView === 'images') {
return imagesTotal?.total ?? 0;
} else {
return assetsTotal?.total ?? 0;
}
}, [assetsTotal?.total, galleryView, imagesTotal?.total]);
const page = useMemo(() => Math.floor(offset / LIMIT), [offset]);
const pages = useMemo(() => Math.floor(total / LIMIT), [total]);
const isNextEnabled = useMemo(() => {
if (!count) {
return false;
}
return page < pages;
}, [count, page, pages]);
const isPrevEnabled = useMemo(() => {
if (!count) {
return false;
}
return offset > 0;
}, [count, offset]);
const next = useCallback(() => {
dispatch(offsetChanged(offset + LIMIT));
}, [dispatch, offset]);
const prev = useCallback(() => {
dispatch(offsetChanged(Math.max(offset - LIMIT, 0)));
}, [dispatch, offset]);
const goToPage = useCallback(
(page: number) => {
const p = Math.max(0, Math.min(page, pages - 1));
dispatch(offsetChanged(p));
},
[dispatch, pages]
);
const first = useCallback(() => {
dispatch(offsetChanged(0));
}, [dispatch]);
const last = useCallback(() => {
dispatch(offsetChanged(pages * LIMIT));
}, [dispatch, pages]);
// calculate the page buttons to display - current page with 3 around it
const pageButtons = useMemo(() => {
const buttons = [];
const start = Math.max(0, page - 3);
const end = Math.min(pages, start + 6);
for (let i = start; i < end; i++) {
buttons.push(i);
}
return buttons;
}, [page, pages]);
const isFirstEnabled = useMemo(() => page > 0, [page]);
const isLastEnabled = useMemo(() => page < pages - 1, [page, pages]);
const api = useMemo(
() => ({
count,
total,
page,
pages,
isNextEnabled,
isPrevEnabled,
next,
prev,
goToPage,
first,
last,
pageButtons,
isFirstEnabled,
isLastEnabled,
}),
[
count,
total,
page,
pages,
isNextEnabled,
isPrevEnabled,
next,
prev,
goToPage,
first,
last,
pageButtons,
isFirstEnabled,
isLastEnabled,
]
);
return api;
};

View File

@ -0,0 +1,144 @@
import { useMemo, useCallback } from "react";
import { useAppDispatch, useAppSelector } from "../../../app/store/storeHooks";
import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from "../../../services/api/endpoints/boards";
import { useListImagesQuery } from "../../../services/api/endpoints/images";
import { selectListImagesQueryArgs } from "../store/gallerySelectors";
import { offsetChanged } from "../store/gallerySlice";
import { IMAGE_LIMIT } from "../store/types";
export const useGalleryPagination = () => {
const dispatch = useAppDispatch();
const { offset, galleryView, selectedBoardId } = useAppSelector((s) => s.gallery);
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { count } = useListImagesQuery(queryArgs, {
selectFromResult: ({ data }) => ({ count: data?.items.length ?? 0 }),
});
const { data: assetsTotal } = useGetBoardAssetsTotalQuery(selectedBoardId);
const { data: imagesTotal } = useGetBoardImagesTotalQuery(selectedBoardId);
const total = useMemo(() => {
if (galleryView === 'images') {
return imagesTotal?.total ?? 0;
} else {
return assetsTotal?.total ?? 0;
}
}, [assetsTotal?.total, galleryView, imagesTotal?.total]);
console.log({ offset })
const currentPage = useMemo(() => Math.ceil(offset / IMAGE_LIMIT), [offset]);
const pages = useMemo(() => Math.ceil(total / IMAGE_LIMIT), [total]);
const isNextEnabled = useMemo(() => {
if (!count) {
return false;
}
return currentPage < pages;
}, [count, currentPage, pages]);
const isPrevEnabled = useMemo(() => {
if (!count) {
return false;
}
return offset > 0;
}, [count, offset]);
const goNext = useCallback(() => {
dispatch(offsetChanged(offset + IMAGE_LIMIT));
}, [dispatch, offset]);
const goPrev = useCallback(() => {
dispatch(offsetChanged(Math.max(offset - IMAGE_LIMIT, 0)));
}, [dispatch, offset]);
const goToPage = useCallback(
(page: number) => {
const p = Math.max(0, Math.min(page, pages - 1));
dispatch(offsetChanged(page * IMAGE_LIMIT));
},
[dispatch, pages]
);
const goToFirst = useCallback(() => {
dispatch(offsetChanged(0));
}, [dispatch]);
const goToLast = useCallback(() => {
dispatch(offsetChanged(pages * IMAGE_LIMIT));
}, [dispatch, pages]);
// calculate the page buttons to display - current page with 3 around it
const pageButtons = useMemo(() => {
const buttons = [];
const maxPageButtons = 5;
let startPage = Math.max(currentPage - (Math.floor(maxPageButtons / 2)), 0);
let endPage = Math.min(startPage + maxPageButtons - 1, pages - 1);
console.log({ startPage })
if (endPage - startPage < maxPageButtons - 1) {
startPage = Math.max(endPage - maxPageButtons + 1, 0);
}
if (startPage > 0) {
buttons.push(0);
if (startPage > 1) {
buttons.push('...');
}
}
for (let i = startPage; i <= endPage; i++) {
buttons.push(i);
}
if (endPage < pages - 1) {
if (endPage < pages - 2) {
buttons.push('...');
}
buttons.push(pages - 1);
}
return buttons;
}, [currentPage, pages]);
const isFirstEnabled = useMemo(() => currentPage > 0, [currentPage]);
const isLastEnabled = useMemo(() => currentPage < pages - 1, [currentPage, pages]);
const rangeDisplay = useMemo(() => {
const startItem = currentPage * IMAGE_LIMIT + 1;
const endItem = Math.min((currentPage + 1) * IMAGE_LIMIT, total);
return `${startItem}-${endItem} of ${total}`;
}, [total, currentPage]);
const api = useMemo(
() => ({
count,
total,
currentPage,
pages,
isNextEnabled,
isPrevEnabled,
goNext,
goPrev,
goToPage,
goToFirst,
goToLast,
pageButtons,
isFirstEnabled,
isLastEnabled,
rangeDisplay
}),
[
count,
total,
currentPage,
pages,
isNextEnabled,
isPrevEnabled,
goNext,
goPrev,
goToPage,
goToFirst,
goToLast,
pageButtons,
isFirstEnabled,
isLastEnabled,
rangeDisplay
]
);
return api;
};