mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
wip change limit based on size of gallery
This commit is contained in:
parent
6a4b4ee340
commit
e0a241fa4f
@ -12,6 +12,8 @@ import BoardsList from './Boards/BoardsList/BoardsList';
|
|||||||
import GalleryBoardName from './GalleryBoardName';
|
import GalleryBoardName from './GalleryBoardName';
|
||||||
import GallerySettingsPopover from './GallerySettingsPopover';
|
import GallerySettingsPopover from './GallerySettingsPopover';
|
||||||
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
||||||
|
import { GalleryImageGridContainer } from './ImageGrid/GalleryImageGridContainer';
|
||||||
|
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
||||||
|
|
||||||
const ImageGalleryContent = () => {
|
const ImageGalleryContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -31,7 +33,7 @@ const ImageGalleryContent = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack layerStyle="first" flexDirection="column" h="full" w="full" borderRadius="base" p={2}>
|
<Flex layerStyle="first" flexDirection="column" h="full" w="full" borderRadius="base" p={2}>
|
||||||
{galleryHeader}
|
{galleryHeader}
|
||||||
<Box w="full">
|
<Box w="full">
|
||||||
<Flex ref={resizeObserverRef} alignItems="center" justifyContent="space-between" gap={2}>
|
<Flex ref={resizeObserverRef} alignItems="center" justifyContent="space-between" gap={2}>
|
||||||
@ -73,9 +75,10 @@ const ImageGalleryContent = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Flex>
|
</Flex>
|
||||||
<GalleryImageGrid />
|
<GalleryImageGridContainer />
|
||||||
</Flex>
|
</Flex>
|
||||||
</VStack>
|
<GalleryPagination />
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelecto
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { 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 GalleryImage from './GalleryImage';
|
||||||
import { useListImagesQuery } from '../../../../services/api/endpoints/images';
|
import { useListImagesQuery } from '../../../../services/api/endpoints/images';
|
||||||
@ -19,7 +18,8 @@ export const imageItemContainerTestId = 'image-item-container';
|
|||||||
const GalleryImageGrid = () => {
|
const GalleryImageGrid = () => {
|
||||||
useGalleryHotkeys();
|
useGalleryHotkeys();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth);
|
|
||||||
|
const { galleryImageMinimumWidth, limit } = useAppSelector((s) => s.gallery);
|
||||||
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
||||||
const { imageDTOs, isLoading, isSuccess, isError } = useListImagesQuery(queryArgs, {
|
const { imageDTOs, isLoading, isSuccess, isError } = useListImagesQuery(queryArgs, {
|
||||||
selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({
|
selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({
|
||||||
@ -38,7 +38,7 @@ const GalleryImageGrid = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || !limit) {
|
||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
||||||
<IAINoContentFallback label={t('gallery.loading')} icon={PiImageBold} />
|
<IAINoContentFallback label={t('gallery.loading')} icon={PiImageBold} />
|
||||||
@ -67,19 +67,9 @@ const GalleryImageGrid = () => {
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
<GalleryPagination />
|
{/* <GalleryPagination /> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(GalleryImageGrid);
|
export default memo(GalleryImageGrid);
|
||||||
|
|
||||||
const GalleryImageContainer = memo(({ imageDTO, index }: { imageDTO: ImageDTO; index: number }) => {
|
|
||||||
return (
|
|
||||||
<Box className="item-container" p={1.5} data-testid={imageItemContainerTestId}>
|
|
||||||
<GalleryImage imageDTO={imageDTO} index={index} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
GalleryImageContainer.displayName = 'GalleryImageContainer';
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||||
|
import GalleryImageGrid from './GalleryImageGrid';
|
||||||
|
import { useAppSelector, useAppDispatch } from '../../../../app/store/storeHooks';
|
||||||
|
import { limitChanged } from '../../store/gallerySlice';
|
||||||
|
|
||||||
|
export const GalleryImageGridContainer = () => {
|
||||||
|
const { galleryImageMinimumWidth, limit } = useAppSelector((s) => s.gallery);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const calculateItemsPerPage = useCallback(() => {
|
||||||
|
const containerWidth = containerRef.current?.clientWidth;
|
||||||
|
const containerHeight = containerRef.current?.clientHeight;
|
||||||
|
console.log({ containerWidth, containerHeight, galleryImageMinimumWidth });
|
||||||
|
if (containerHeight && containerWidth) {
|
||||||
|
const numberHorizontal = Math.floor(containerWidth / galleryImageMinimumWidth);
|
||||||
|
const imageWidth = containerWidth / numberHorizontal;
|
||||||
|
const numberAllowedVertical = Math.floor(containerHeight / imageWidth);
|
||||||
|
console.log({ numberAllowedVertical, numberHorizontal });
|
||||||
|
dispatch(limitChanged(numberAllowedVertical * numberHorizontal));
|
||||||
|
}
|
||||||
|
}, [containerRef, galleryImageMinimumWidth]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(limitChanged(undefined));
|
||||||
|
calculateItemsPerPage();
|
||||||
|
}, [galleryImageMinimumWidth]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
console.log('resize');
|
||||||
|
if (!containerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(limitChanged(undefined));
|
||||||
|
calculateItemsPerPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
dispatch(limitChanged(undefined));
|
||||||
|
calculateItemsPerPage();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" w="full" h="full" overflow="hidden" ref={containerRef}>
|
||||||
|
{limit && <GalleryImageGrid />}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -3,12 +3,15 @@ import { useGalleryPagination } from '../../hooks/useGalleryPagination';
|
|||||||
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
|
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const GalleryPagination = () => {
|
export const GalleryPagination = () => {
|
||||||
const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, rangeDisplay } =
|
const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, rangeDisplay, total } =
|
||||||
useGalleryPagination();
|
useGalleryPagination();
|
||||||
console.log({ currentPage, pageButtons });
|
|
||||||
|
if (!total) {
|
||||||
|
return <Flex flexDir="column" alignItems="center" gap="2" height="48px"></Flex>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" alignItems="center" gap="2">
|
<Flex flexDir="column" alignItems="center" gap="2" height="48px">
|
||||||
<Flex gap={2} alignItems="flex-end">
|
<Flex gap={2} alignItems="flex-end">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
|
@ -4,19 +4,18 @@ import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from "../../
|
|||||||
import { useListImagesQuery } from "../../../services/api/endpoints/images";
|
import { useListImagesQuery } from "../../../services/api/endpoints/images";
|
||||||
import { selectListImagesQueryArgs } from "../store/gallerySelectors";
|
import { selectListImagesQueryArgs } from "../store/gallerySelectors";
|
||||||
import { offsetChanged } from "../store/gallerySlice";
|
import { offsetChanged } from "../store/gallerySlice";
|
||||||
import { IMAGE_LIMIT } from "../store/types";
|
|
||||||
|
|
||||||
export const useGalleryPagination = () => {
|
export const useGalleryPagination = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { offset } = useAppSelector((s) => s.gallery);
|
const { offset, limit } = useAppSelector((s) => s.gallery);
|
||||||
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
||||||
|
|
||||||
const { count, total } = useListImagesQuery(queryArgs, {
|
const { count, total } = useListImagesQuery(queryArgs, {
|
||||||
selectFromResult: ({ data }) => ({ count: data?.items.length ?? 0, total: data?.total ?? 0 }),
|
selectFromResult: ({ data }) => ({ count: data?.items.length ?? 0, total: data?.total ?? 0 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentPage = useMemo(() => Math.ceil(offset / IMAGE_LIMIT), [offset]);
|
const currentPage = useMemo(() => Math.ceil(offset / (limit || 0)), [offset, limit]);
|
||||||
const pages = useMemo(() => Math.ceil(total / IMAGE_LIMIT), [total]);
|
const pages = useMemo(() => Math.ceil(total / (limit || 0)), [total, limit]);
|
||||||
|
|
||||||
const isNextEnabled = useMemo(() => {
|
const isNextEnabled = useMemo(() => {
|
||||||
if (!count) {
|
if (!count) {
|
||||||
@ -32,26 +31,26 @@ export const useGalleryPagination = () => {
|
|||||||
}, [count, offset]);
|
}, [count, offset]);
|
||||||
|
|
||||||
const goNext = useCallback(() => {
|
const goNext = useCallback(() => {
|
||||||
dispatch(offsetChanged(offset + IMAGE_LIMIT));
|
dispatch(offsetChanged(offset + (limit || 0)));
|
||||||
}, [dispatch, offset]);
|
}, [dispatch, offset, limit]);
|
||||||
|
|
||||||
const goPrev = useCallback(() => {
|
const goPrev = useCallback(() => {
|
||||||
dispatch(offsetChanged(Math.max(offset - IMAGE_LIMIT, 0)));
|
dispatch(offsetChanged(Math.max(offset - (limit || 0), 0)));
|
||||||
}, [dispatch, offset]);
|
}, [dispatch, offset, limit]);
|
||||||
|
|
||||||
const goToPage = useCallback(
|
const goToPage = useCallback(
|
||||||
(page: number) => {
|
(page: number) => {
|
||||||
const p = Math.max(0, Math.min(page, pages - 1));
|
const p = Math.max(0, Math.min(page, pages - 1));
|
||||||
dispatch(offsetChanged(page * IMAGE_LIMIT));
|
dispatch(offsetChanged(page * (limit || 0)));
|
||||||
},
|
},
|
||||||
[dispatch, pages]
|
[dispatch, pages, limit]
|
||||||
);
|
);
|
||||||
const goToFirst = useCallback(() => {
|
const goToFirst = useCallback(() => {
|
||||||
dispatch(offsetChanged(0));
|
dispatch(offsetChanged(0));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
const goToLast = useCallback(() => {
|
const goToLast = useCallback(() => {
|
||||||
dispatch(offsetChanged(pages * IMAGE_LIMIT));
|
dispatch(offsetChanged(pages * (limit || 0)));
|
||||||
}, [dispatch, pages]);
|
}, [dispatch, pages, limit]);
|
||||||
|
|
||||||
// calculate the page buttons to display - current page with 3 around it
|
// calculate the page buttons to display - current page with 3 around it
|
||||||
const pageButtons = useMemo(() => {
|
const pageButtons = useMemo(() => {
|
||||||
@ -89,10 +88,10 @@ export const useGalleryPagination = () => {
|
|||||||
const isLastEnabled = useMemo(() => currentPage < pages - 1, [currentPage, pages]);
|
const isLastEnabled = useMemo(() => currentPage < pages - 1, [currentPage, pages]);
|
||||||
|
|
||||||
const rangeDisplay = useMemo(() => {
|
const rangeDisplay = useMemo(() => {
|
||||||
const startItem = currentPage * IMAGE_LIMIT + 1;
|
const startItem = currentPage * (limit || 0) + 1;
|
||||||
const endItem = Math.min((currentPage + 1) * IMAGE_LIMIT, total);
|
const endItem = Math.min((currentPage + 1) * (limit || 0), total);
|
||||||
return `${startItem}-${endItem} of ${total}`;
|
return `${startItem}-${endItem} of ${total}`;
|
||||||
}, [total, currentPage]);
|
}, [total, currentPage, limit]);
|
||||||
|
|
||||||
const api = useMemo(
|
const api = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SkipToken, skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
@ -10,11 +11,11 @@ export const selectLastSelectedImage = createMemoizedSelector(
|
|||||||
|
|
||||||
export const selectListImagesQueryArgs = createMemoizedSelector(
|
export const selectListImagesQueryArgs = createMemoizedSelector(
|
||||||
selectGallerySlice,
|
selectGallerySlice,
|
||||||
(gallery): ListImagesArgs => ({
|
(gallery): ListImagesArgs | SkipToken => (gallery.limit ? {
|
||||||
board_id: gallery.selectedBoardId,
|
board_id: gallery.selectedBoardId,
|
||||||
categories: gallery.galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
categories: gallery.galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
||||||
offset: gallery.offset,
|
offset: gallery.offset,
|
||||||
limit: gallery.limit,
|
limit: gallery.limit,
|
||||||
is_intermediate: false,
|
is_intermediate: false,
|
||||||
})
|
} : skipToken)
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,7 @@ const initialGalleryState: GalleryState = {
|
|||||||
selectedBoardId: 'none',
|
selectedBoardId: 'none',
|
||||||
galleryView: 'images',
|
galleryView: 'images',
|
||||||
boardSearchText: '',
|
boardSearchText: '',
|
||||||
limit: IMAGE_LIMIT,
|
limit: undefined,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
isImageViewerOpen: true,
|
isImageViewerOpen: true,
|
||||||
imageToCompare: null,
|
imageToCompare: null,
|
||||||
@ -72,7 +72,6 @@ export const gallerySlice = createSlice({
|
|||||||
state.selectedBoardId = action.payload.boardId;
|
state.selectedBoardId = action.payload.boardId;
|
||||||
state.galleryView = 'images';
|
state.galleryView = 'images';
|
||||||
state.offset = 0;
|
state.offset = 0;
|
||||||
state.limit = IMAGE_LIMIT;
|
|
||||||
},
|
},
|
||||||
autoAddBoardIdChanged: (state, action: PayloadAction<BoardId>) => {
|
autoAddBoardIdChanged: (state, action: PayloadAction<BoardId>) => {
|
||||||
if (!action.payload) {
|
if (!action.payload) {
|
||||||
@ -108,6 +107,9 @@ export const gallerySlice = createSlice({
|
|||||||
offsetChanged: (state, action: PayloadAction<number>) => {
|
offsetChanged: (state, action: PayloadAction<number>) => {
|
||||||
state.offset = action.payload;
|
state.offset = action.payload;
|
||||||
},
|
},
|
||||||
|
limitChanged: (state, action: PayloadAction<number | undefined>) => {
|
||||||
|
state.limit = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addMatcher(isAnyBoardDeleted, (state, action) => {
|
builder.addMatcher(isAnyBoardDeleted, (state, action) => {
|
||||||
@ -150,7 +152,8 @@ export const {
|
|||||||
comparedImagesSwapped,
|
comparedImagesSwapped,
|
||||||
comparisonFitChanged,
|
comparisonFitChanged,
|
||||||
comparisonModeCycled,
|
comparisonModeCycled,
|
||||||
offsetChanged
|
offsetChanged,
|
||||||
|
limitChanged
|
||||||
} = gallerySlice.actions;
|
} = gallerySlice.actions;
|
||||||
|
|
||||||
const isAnyBoardDeleted = isAnyOf(
|
const isAnyBoardDeleted = isAnyOf(
|
||||||
|
@ -2,7 +2,7 @@ import type { ImageCategory, ImageDTO } from 'services/api/types';
|
|||||||
|
|
||||||
export const IMAGE_CATEGORIES: ImageCategory[] = ['general'];
|
export const IMAGE_CATEGORIES: ImageCategory[] = ['general'];
|
||||||
export const ASSETS_CATEGORIES: ImageCategory[] = ['control', 'mask', 'user', 'other'];
|
export const ASSETS_CATEGORIES: ImageCategory[] = ['control', 'mask', 'user', 'other'];
|
||||||
export const IMAGE_LIMIT = 20;
|
export const IMAGE_LIMIT = 15;
|
||||||
|
|
||||||
export type GalleryView = 'images' | 'assets';
|
export type GalleryView = 'images' | 'assets';
|
||||||
export type BoardId = 'none' | (string & Record<never, never>);
|
export type BoardId = 'none' | (string & Record<never, never>);
|
||||||
@ -19,7 +19,7 @@ export type GalleryState = {
|
|||||||
galleryView: GalleryView;
|
galleryView: GalleryView;
|
||||||
boardSearchText: string;
|
boardSearchText: string;
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number | undefined;
|
||||||
alwaysShowImageSizeBadge: boolean;
|
alwaysShowImageSizeBadge: boolean;
|
||||||
imageToCompare: ImageDTO | null;
|
imageToCompare: ImageDTO | null;
|
||||||
comparisonMode: ComparisonMode;
|
comparisonMode: ComparisonMode;
|
||||||
|
Loading…
Reference in New Issue
Block a user