From e0a241fa4f626cda3333baf8135ec093bdd67a01 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 20 Jun 2024 21:13:48 -0400 Subject: [PATCH] wip change limit based on size of gallery --- .../components/ImageGalleryContent.tsx | 9 ++- .../components/ImageGrid/GalleryImageGrid.tsx | 18 ++---- .../ImageGrid/GalleryImageGridContainer.tsx | 58 +++++++++++++++++++ .../ImageGrid/GalleryPagination.tsx | 9 ++- .../gallery/hooks/useGalleryPagination.ts | 29 +++++----- .../gallery/store/gallerySelectors.ts | 5 +- .../features/gallery/store/gallerySlice.ts | 9 ++- .../web/src/features/gallery/store/types.ts | 4 +- 8 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGridContainer.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index bd9afcd417..2354423210 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -12,6 +12,8 @@ import BoardsList from './Boards/BoardsList/BoardsList'; import GalleryBoardName from './GalleryBoardName'; import GallerySettingsPopover from './GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; +import { GalleryImageGridContainer } from './ImageGrid/GalleryImageGridContainer'; +import { GalleryPagination } from './ImageGrid/GalleryPagination'; const ImageGalleryContent = () => { const { t } = useTranslation(); @@ -31,7 +33,7 @@ const ImageGalleryContent = () => { }, [dispatch]); return ( - + {galleryHeader} @@ -73,9 +75,10 @@ const ImageGalleryContent = () => { - + - + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index 7d6f6cf4e2..1e5674dd43 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -7,7 +7,6 @@ import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelecto import { memo } from 'react'; import { useTranslation } from 'react-i18next'; 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'; @@ -19,7 +18,8 @@ export const imageItemContainerTestId = 'image-item-container'; const GalleryImageGrid = () => { useGalleryHotkeys(); const { t } = useTranslation(); - const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth); + + const { galleryImageMinimumWidth, limit } = useAppSelector((s) => s.gallery); const queryArgs = useAppSelector(selectListImagesQueryArgs); const { imageDTOs, isLoading, isSuccess, isError } = useListImagesQuery(queryArgs, { selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({ @@ -38,7 +38,7 @@ const GalleryImageGrid = () => { ); } - if (isLoading) { + if (isLoading || !limit) { return ( @@ -67,19 +67,9 @@ const GalleryImageGrid = () => { ))} - + {/* */} ); }; export default memo(GalleryImageGrid); - -const GalleryImageContainer = memo(({ imageDTO, index }: { imageDTO: ImageDTO; index: number }) => { - return ( - - - - ); -}); - -GalleryImageContainer.displayName = 'GalleryImageContainer'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGridContainer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGridContainer.tsx new file mode 100644 index 0000000000..7b9670b8e0 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGridContainer.tsx @@ -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(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 ( + + {limit && } + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx index 56a0d5a60a..cfac6a728a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryPagination.tsx @@ -3,12 +3,15 @@ import { useGalleryPagination } from '../../hooks/useGalleryPagination'; import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi'; export const GalleryPagination = () => { - const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, rangeDisplay } = + const { goPrev, goNext, isPrevEnabled, isNextEnabled, pageButtons, goToPage, currentPage, rangeDisplay, total } = useGalleryPagination(); - console.log({ currentPage, pageButtons }); + + if (!total) { + return ; + } return ( - + { const dispatch = useAppDispatch(); - const { offset } = useAppSelector((s) => s.gallery); + const { offset, limit } = useAppSelector((s) => s.gallery); const queryArgs = useAppSelector(selectListImagesQueryArgs); const { count, total } = useListImagesQuery(queryArgs, { selectFromResult: ({ data }) => ({ count: data?.items.length ?? 0, total: data?.total ?? 0 }), }); - const currentPage = useMemo(() => Math.ceil(offset / IMAGE_LIMIT), [offset]); - const pages = useMemo(() => Math.ceil(total / IMAGE_LIMIT), [total]); + const currentPage = useMemo(() => Math.ceil(offset / (limit || 0)), [offset, limit]); + const pages = useMemo(() => Math.ceil(total / (limit || 0)), [total, limit]); const isNextEnabled = useMemo(() => { if (!count) { @@ -32,26 +31,26 @@ export const useGalleryPagination = () => { }, [count, offset]); const goNext = useCallback(() => { - dispatch(offsetChanged(offset + IMAGE_LIMIT)); - }, [dispatch, offset]); + dispatch(offsetChanged(offset + (limit || 0))); + }, [dispatch, offset, limit]); const goPrev = useCallback(() => { - dispatch(offsetChanged(Math.max(offset - IMAGE_LIMIT, 0))); - }, [dispatch, offset]); + dispatch(offsetChanged(Math.max(offset - (limit || 0), 0))); + }, [dispatch, offset, limit]); const goToPage = useCallback( (page: number) => { 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(() => { dispatch(offsetChanged(0)); }, [dispatch]); const goToLast = useCallback(() => { - dispatch(offsetChanged(pages * IMAGE_LIMIT)); - }, [dispatch, pages]); + dispatch(offsetChanged(pages * (limit || 0))); + }, [dispatch, pages, limit]); // calculate the page buttons to display - current page with 3 around it const pageButtons = useMemo(() => { @@ -89,10 +88,10 @@ export const useGalleryPagination = () => { 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); + const startItem = currentPage * (limit || 0) + 1; + const endItem = Math.min((currentPage + 1) * (limit || 0), total); return `${startItem}-${endItem} of ${total}`; - }, [total, currentPage]); + }, [total, currentPage, limit]); const api = useMemo( () => ({ diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index 1c64fc7809..bd9eb33e1c 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -1,3 +1,4 @@ +import { SkipToken, skipToken } from '@reduxjs/toolkit/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types'; @@ -10,11 +11,11 @@ export const selectLastSelectedImage = createMemoizedSelector( export const selectListImagesQueryArgs = createMemoizedSelector( selectGallerySlice, - (gallery): ListImagesArgs => ({ + (gallery): ListImagesArgs | SkipToken => (gallery.limit ? { board_id: gallery.selectedBoardId, categories: gallery.galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES, offset: gallery.offset, limit: gallery.limit, is_intermediate: false, - }) + } : skipToken) ); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 573cc7ab02..7ed948cbb0 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -19,7 +19,7 @@ const initialGalleryState: GalleryState = { selectedBoardId: 'none', galleryView: 'images', boardSearchText: '', - limit: IMAGE_LIMIT, + limit: undefined, offset: 0, isImageViewerOpen: true, imageToCompare: null, @@ -72,7 +72,6 @@ export const gallerySlice = createSlice({ state.selectedBoardId = action.payload.boardId; state.galleryView = 'images'; state.offset = 0; - state.limit = IMAGE_LIMIT; }, autoAddBoardIdChanged: (state, action: PayloadAction) => { if (!action.payload) { @@ -108,6 +107,9 @@ export const gallerySlice = createSlice({ offsetChanged: (state, action: PayloadAction) => { state.offset = action.payload; }, + limitChanged: (state, action: PayloadAction) => { + state.limit = action.payload; + }, }, extraReducers: (builder) => { builder.addMatcher(isAnyBoardDeleted, (state, action) => { @@ -150,7 +152,8 @@ export const { comparedImagesSwapped, comparisonFitChanged, comparisonModeCycled, - offsetChanged + offsetChanged, + limitChanged } = gallerySlice.actions; const isAnyBoardDeleted = isAnyOf( diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts index c929ff992e..9a4bc58db4 100644 --- a/invokeai/frontend/web/src/features/gallery/store/types.ts +++ b/invokeai/frontend/web/src/features/gallery/store/types.ts @@ -2,7 +2,7 @@ import type { ImageCategory, ImageDTO } from 'services/api/types'; export const IMAGE_CATEGORIES: ImageCategory[] = ['general']; 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 BoardId = 'none' | (string & Record); @@ -19,7 +19,7 @@ export type GalleryState = { galleryView: GalleryView; boardSearchText: string; offset: number; - limit: number; + limit: number | undefined; alwaysShowImageSizeBadge: boolean; imageToCompare: ImageDTO | null; comparisonMode: ComparisonMode;