mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
some cleanup, add page buttons
This commit is contained in:
parent
98c77a3ed1
commit
5101dc4bef
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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}
|
||||
/>
|
||||
|
@ -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]
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
@ -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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user