diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 928deb6933..007a829bf9 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -8,6 +8,7 @@ import { } from 'features/gallery/store/gallerySlice'; import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { CANVAS_OUTPUT } from 'features/nodes/util/graphBuilders/constants'; +import { boardsApi } from 'services/api/endpoints/boards'; import { imagesApi } from 'services/api/endpoints/images'; import { isImageOutput } from 'services/api/guards'; import { imagesAdapter } from 'services/api/util'; @@ -70,11 +71,21 @@ export const addInvocationCompleteEventListener = () => { ) ); + // update the total images for the board + dispatch( + boardsApi.util.updateQueryData( + 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + draft.total += 1; + } + ) + ); + dispatch( imagesApi.util.invalidateTags([ - { type: 'BoardImagesTotal', id: imageDTO.board_id }, - { type: 'BoardAssetsTotal', id: imageDTO.board_id }, - { type: 'Board', id: imageDTO.board_id }, + { type: 'Board', id: imageDTO.board_id ?? 'none' }, ]) ); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index 104512a9c6..33f5a7c5b3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -77,12 +77,12 @@ const GalleryBoard = ({ const { data: imagesTotal } = useGetBoardImagesTotalQuery(board.board_id); const { data: assetsTotal } = useGetBoardAssetsTotalQuery(board.board_id); const tooltip = useMemo(() => { - if (!imagesTotal || !assetsTotal) { + if (!imagesTotal?.total || !assetsTotal?.total) { return undefined; } - return `${imagesTotal} image${ - imagesTotal > 1 ? 's' : '' - }, ${assetsTotal} asset${assetsTotal > 1 ? 's' : ''}`; + return `${imagesTotal.total} image${imagesTotal.total > 1 ? 's' : ''}, ${ + assetsTotal.total + } asset${assetsTotal.total > 1 ? 's' : ''}`; }, [assetsTotal, imagesTotal]); const { currentData: coverImage } = useGetImageDTOQuery( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 6cea7d3eac..2d2d116928 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Image, Text } from '@chakra-ui/react'; +import { Box, Flex, Image, Text, Tooltip } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -15,6 +15,10 @@ import { memo, useCallback, useMemo, useState } from 'react'; import { useBoardName } from 'services/api/hooks/useBoardName'; import AutoAddIcon from '../AutoAddIcon'; import BoardContextMenu from '../BoardContextMenu'; +import { + useGetBoardAssetsTotalQuery, + useGetBoardImagesTotalQuery, +} from 'services/api/endpoints/boards'; interface Props { isSelected: boolean; @@ -41,6 +45,17 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { }, [dispatch, autoAssignBoardOnClick]); const [isHovered, setIsHovered] = useState(false); + const { data: imagesTotal } = useGetBoardImagesTotalQuery('none'); + const { data: assetsTotal } = useGetBoardAssetsTotalQuery('none'); + const tooltip = useMemo(() => { + if (!imagesTotal?.total || !assetsTotal?.total) { + return undefined; + } + return `${imagesTotal.total} image${imagesTotal.total > 1 ? 's' : ''}, ${ + assetsTotal.total + } asset${assetsTotal.total > 1 ? 's' : ''}`; + }, [assetsTotal, imagesTotal]); + const handleMouseOver = useCallback(() => { setIsHovered(true); }, []); @@ -74,77 +89,82 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { > {(ref) => ( - + - invoke-ai-logo + invoke-ai-logo + + {autoAddBoardId === 'none' && } + + {boardName} + + + Move} /> - {autoAddBoardId === 'none' && } - - {boardName} - - - Move} - /> - + )} diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts index 670dd7ee9f..fe30f0a7a7 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts @@ -20,7 +20,7 @@ export const nextPrevImageButtonsSelector = createSelector( const { data, status } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state); - const { data: total } = + const { data: totalsData } = state.gallery.galleryView === 'images' ? boardsApi.endpoints.getBoardImagesTotal.select( baseQueryArgs.board_id ?? 'none' @@ -34,7 +34,7 @@ export const nextPrevImageButtonsSelector = createSelector( const isFetching = status === 'pending'; - if (!data || !lastSelectedImage || total === 0) { + if (!data || !lastSelectedImage || totalsData?.total === 0) { return { isFetching, queryArgs: baseQueryArgs, @@ -74,7 +74,7 @@ export const nextPrevImageButtonsSelector = createSelector( return { loadedImagesCount: images.length, currentImageIndex, - areMoreImagesAvailable: (total ?? 0) > imagesLength, + areMoreImagesAvailable: (totalsData?.total ?? 0) > imagesLength, isFetching: status === 'pending', nextImage, prevImage, diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts index 53b3bfd59d..2e269be124 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -70,7 +70,7 @@ export const boardsApi = api.injectEndpoints({ keepUnusedDataFor: 0, }), - getBoardImagesTotal: build.query({ + getBoardImagesTotal: build.query<{ total: number }, string | undefined>({ query: (board_id) => ({ url: getListImagesUrl({ board_id: board_id ?? 'none', @@ -85,11 +85,11 @@ export const boardsApi = api.injectEndpoints({ { type: 'BoardImagesTotal', id: arg ?? 'none' }, ], transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => { - return response.total; + return { total: response.total }; }, }), - getBoardAssetsTotal: build.query({ + getBoardAssetsTotal: build.query<{ total: number }, string | undefined>({ query: (board_id) => ({ url: getListImagesUrl({ board_id: board_id ?? 'none', @@ -104,7 +104,7 @@ export const boardsApi = api.injectEndpoints({ { type: 'BoardAssetsTotal', id: arg ?? 'none' }, ], transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => { - return response.total; + return { total: response.total }; }, }), diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 239dfaa982..ce9207144d 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -103,6 +103,9 @@ export const imagesApi = api.injectEndpoints({ query: () => ({ url: getListImagesUrl({ is_intermediate: true }) }), providesTags: ['IntermediatesCount'], transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => { + // TODO: This is storing a primitive value in the cache. `immer` cannot track state changes, so + // attempts to use manual cache updates on this value will fail. This should be changed into an + // object. return response.total; }, }), @@ -191,35 +194,51 @@ export const imagesApi = api.injectEndpoints({ url: `images/i/${image_name}`, method: 'DELETE', }), - invalidatesTags: (result, error, { board_id }) => [ - { type: 'BoardImagesTotal', id: board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: board_id ?? 'none' }, - ], async onQueryStarted(imageDTO, { dispatch, queryFulfilled }) { /** * Cache changes for `deleteImage`: * - NOT POSSIBLE: *remove* from getImageDTO * - $cache = [board_id|no_board]/[images|assets] * - *remove* from $cache + * - decrement the image's board's total */ const { image_name, board_id } = imageDTO; + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); const queryArg = { board_id: board_id ?? 'none', categories: getCategories(imageDTO), }; - const patch = dispatch( - imagesApi.util.updateQueryData('listImages', queryArg, (draft) => { - imagesAdapter.removeOne(draft, image_name); - }) + const patches: PatchCollection[] = []; + + patches.push( + dispatch( + imagesApi.util.updateQueryData('listImages', queryArg, (draft) => { + imagesAdapter.removeOne(draft, image_name); + }) + ) ); + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ) + ); // decrement the image board's total + try { await queryFulfilled; } catch { - patch.undo(); + patches.forEach((patch) => { + patch.undo(); + }); } }, }), @@ -237,18 +256,11 @@ export const imagesApi = api.injectEndpoints({ }, }; }, - invalidatesTags: (result, error, { imageDTOs }) => { - // for now, assume bulk delete is all on one board - const boardId = imageDTOs[0]?.board_id; - return [ - { type: 'BoardImagesTotal', id: boardId ?? 'none' }, - { type: 'BoardAssetsTotal', id: boardId ?? 'none' }, - ]; - }, async onQueryStarted({ imageDTOs }, { dispatch, queryFulfilled }) { /** * Cache changes for `deleteImages`: * - *remove* the deleted images from their boards + * - decrement the images' board's totals * * Unfortunately we cannot do an optimistic update here due to how immer handles patching * arrays. You have to undo *all* patches, else the entity adapter's `ids` array is borked. @@ -279,6 +291,21 @@ export const imagesApi = api.injectEndpoints({ } ) ); + + const isAsset = ASSETS_CATEGORIES.includes( + imageDTO.image_category + ); + + // decrement the image board's total + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ); } }); } catch { @@ -298,10 +325,6 @@ export const imagesApi = api.injectEndpoints({ method: 'PATCH', body: { is_intermediate }, }), - invalidatesTags: (result, error, { imageDTO }) => [ - { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' }, - ], async onQueryStarted( { imageDTO, is_intermediate }, { dispatch, queryFulfilled, getState } @@ -312,9 +335,11 @@ export const imagesApi = api.injectEndpoints({ * - $cache = [board_id|no_board]/[images|assets] * - IF it is being changed to an intermediate: * - remove from $cache + * - decrement the image's board's total * - ELSE (it is being changed to a non-intermediate): * - IF it eligible for insertion into existing $cache: * - *upsert* to $cache + * - increment the image's board's total */ // Store patches so we can undo if the query fails @@ -335,6 +360,7 @@ export const imagesApi = api.injectEndpoints({ // $cache = [board_id|no_board]/[images|assets] const categories = getCategories(imageDTO); + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); if (is_intermediate) { // IF it is being changed to an intermediate: @@ -350,8 +376,35 @@ export const imagesApi = api.injectEndpoints({ ) ) ); + + // decrement the image board's total + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ) + ); } else { // ELSE (it is being changed to a non-intermediate): + + // increment the image board's total + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total += 1; + } + ) + ) + ); + const queryArgs = { board_id: imageDTO.board_id ?? 'none', categories, @@ -361,9 +414,7 @@ export const imagesApi = api.injectEndpoints({ getState() ); - const { data: total } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( imageDTO.board_id ?? 'none' )(getState()) @@ -378,7 +429,8 @@ export const imagesApi = api.injectEndpoints({ // - The image's `created_at` is within the range of the cached images const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (total ?? 0); + currentCache.data && + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = getIsImageInDateRange( currentCache.data, @@ -420,10 +472,6 @@ export const imagesApi = api.injectEndpoints({ method: 'PATCH', body: { session_id }, }), - invalidatesTags: (result, error, { imageDTO }) => [ - { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' }, - ], async onQueryStarted( { imageDTO, session_id }, { dispatch, queryFulfilled } @@ -535,9 +583,7 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: previousTotal } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( boardId ?? 'none' )(getState()) @@ -547,10 +593,10 @@ export const imagesApi = api.injectEndpoints({ const isCacheFullyPopulated = currentCache.data && - currentCache.data.ids.length >= (previousTotal ?? 0); + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = - (previousTotal || 0) >= IMAGE_LIMIT + (data?.total ?? 0) >= IMAGE_LIMIT ? getIsImageInDateRange(currentCache.data, imageDTO) : true; @@ -652,9 +698,7 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: previousTotal } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( boardId ?? 'none' )(getState()) @@ -664,10 +708,10 @@ export const imagesApi = api.injectEndpoints({ const isCacheFullyPopulated = currentCache.data && - currentCache.data.ids.length >= (previousTotal ?? 0); + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = - (previousTotal || 0) >= IMAGE_LIMIT + (data?.total ?? 0) >= IMAGE_LIMIT ? getIsImageInDateRange(currentCache.data, imageDTO) : true; @@ -736,6 +780,7 @@ export const imagesApi = api.injectEndpoints({ * - BAIL OUT * - *add* to `getImageDTO` * - *add* to no_board/assets + * - update the image's board's assets total */ const { data: imageDTO } = await queryFulfilled; @@ -770,11 +815,15 @@ export const imagesApi = api.injectEndpoints({ ) ); + // increment new board's total dispatch( - imagesApi.util.invalidateTags([ - { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' }, - ]) + boardsApi.util.updateQueryData( + 'getBoardAssetsTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total += 1; + } + ) ); } catch { // query failed, no action needed @@ -801,8 +850,6 @@ export const imagesApi = api.injectEndpoints({ categories: ASSETS_CATEGORIES, }), }, - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, ], async onQueryStarted(board_id, { dispatch, queryFulfilled }) { /** @@ -815,6 +862,7 @@ export const imagesApi = api.injectEndpoints({ * have access to the deleted images DTOs - only the names, and a network request * for all of a board's DTOs could be very large. Instead, we invalidate the 'No Board' * cache. + * - set the board's totals to zero */ try { @@ -834,6 +882,28 @@ export const imagesApi = api.injectEndpoints({ ); }); + // set the board's asset total to 0 (feels unnecessary since we are deleting it?) + dispatch( + boardsApi.util.updateQueryData( + 'getBoardAssetsTotal', + board_id, + (draft) => { + draft.total = 0; + } + ) + ); + + // set the board's images total to 0 (feels unnecessary since we are deleting it?) + dispatch( + boardsApi.util.updateQueryData( + 'getBoardImagesTotal', + board_id, + (draft) => { + draft.total = 0; + } + ) + ); + // update 'All Images' & 'All Assets' caches const queryArgsToUpdate = [ { @@ -890,8 +960,6 @@ export const imagesApi = api.injectEndpoints({ categories: ASSETS_CATEGORIES, }), }, - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, ], async onQueryStarted(board_id, { dispatch, queryFulfilled }) { /** @@ -901,6 +969,7 @@ export const imagesApi = api.injectEndpoints({ * Instead, we rely on the UI to remove all components that use the deleted images. * - Remove every image in the 'All Images' cache that has the board_id * - Remove every image in the 'All Assets' cache that has the board_id + * - set the board's totals to zero */ try { @@ -928,6 +997,28 @@ export const imagesApi = api.injectEndpoints({ ) ); }); + + // set the board's asset total to 0 (feels unnecessary since we are deleting it?) + dispatch( + boardsApi.util.updateQueryData( + 'getBoardAssetsTotal', + board_id, + (draft) => { + draft.total = 0; + } + ) + ); + + // set the board's images total to 0 (feels unnecessary since we are deleting it?) + dispatch( + boardsApi.util.updateQueryData( + 'getBoardImagesTotal', + board_id, + (draft) => { + draft.total = 0; + } + ) + ); } catch { //no-op } @@ -945,15 +1036,9 @@ export const imagesApi = api.injectEndpoints({ body: { board_id, image_name }, }; }, - invalidatesTags: (result, error, { board_id, imageDTO }) => [ + invalidatesTags: (result, error, { board_id }) => [ // refresh the board itself { type: 'Board', id: board_id }, - // update old board totals - { type: 'BoardImagesTotal', id: board_id }, - { type: 'BoardAssetsTotal', id: board_id }, - // update new board totals - { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' }, ], async onQueryStarted( { board_id, imageDTO }, @@ -970,11 +1055,13 @@ export const imagesApi = api.injectEndpoints({ * - $cache = board_id/[images|assets] * - IF it eligible for insertion into existing $cache: * - THEN *add* to $cache + * - decrement both old board's total + * - increment the new board's total */ const patches: PatchCollection[] = []; const categories = getCategories(imageDTO); - + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); // *update* getImageDTO patches.push( dispatch( @@ -1005,6 +1092,32 @@ export const imagesApi = api.injectEndpoints({ ) ); + // decrement old board's total + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ) + ); + + // increment new board's total + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + board_id ?? 'none', + (draft) => { + draft.total += 1; + } + ) + ) + ); + // $cache = board_id/[images|assets] const queryArgs = { board_id: board_id ?? 'none', categories }; const currentCache = imagesApi.endpoints.listImages.select(queryArgs)( @@ -1017,9 +1130,7 @@ export const imagesApi = api.injectEndpoints({ // OR // - The image's `created_at` is within the range of the cached images - const { data: total } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( imageDTO.board_id ?? 'none' )(getState()) @@ -1028,7 +1139,8 @@ export const imagesApi = api.injectEndpoints({ )(getState()); const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (total ?? 0); + currentCache.data && + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = getIsImageInDateRange( currentCache.data, @@ -1072,12 +1184,6 @@ export const imagesApi = api.injectEndpoints({ return [ // invalidate the image's old board { type: 'Board', id: board_id ?? 'none' }, - // update old board totals - { type: 'BoardImagesTotal', id: board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: board_id ?? 'none' }, - // update the no_board totals - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, ]; }, async onQueryStarted( @@ -1091,10 +1197,13 @@ export const imagesApi = api.injectEndpoints({ * - $cache = no_board/[images|assets] * - IF it eligible for insertion into existing $cache: * - THEN *upsert* to $cache + * - decrement old board's total + * - increment the new board's total (no board) */ const categories = getCategories(imageDTO); const patches: PatchCollection[] = []; + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); // *update* getImageDTO patches.push( @@ -1125,6 +1234,32 @@ export const imagesApi = api.injectEndpoints({ ) ); + // decrement old board's total + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ) + ); + + // increment new board's total (no board) + patches.push( + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + 'none', + (draft) => { + draft.total += 1; + } + ) + ) + ); + // $cache = no_board/[images|assets] const queryArgs = { board_id: 'none', categories }; const currentCache = imagesApi.endpoints.listImages.select(queryArgs)( @@ -1137,9 +1272,7 @@ export const imagesApi = api.injectEndpoints({ // OR // - The image's `created_at` is within the range of the cached images - const { data: total } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( imageDTO.board_id ?? 'none' )(getState()) @@ -1148,7 +1281,8 @@ export const imagesApi = api.injectEndpoints({ )(getState()); const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (total ?? 0); + currentCache.data && + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = getIsImageInDateRange( currentCache.data, @@ -1192,21 +1326,10 @@ export const imagesApi = api.injectEndpoints({ board_id, }, }), - invalidatesTags: (result, error, { imageDTOs, board_id }) => { - //assume all images are being moved from one board for now - const oldBoardId = imageDTOs[0]?.board_id; + invalidatesTags: (result, error, { board_id }) => { return [ // update the destination board { type: 'Board', id: board_id ?? 'none' }, - // update new board totals - { type: 'BoardImagesTotal', id: board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: board_id ?? 'none' }, - // update old board totals - { type: 'BoardImagesTotal', id: oldBoardId ?? 'none' }, - { type: 'BoardAssetsTotal', id: oldBoardId ?? 'none' }, - // update the no_board totals - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, ]; }, async onQueryStarted( @@ -1222,6 +1345,8 @@ export const imagesApi = api.injectEndpoints({ * - *update* getImageDTO for each image * - *add* to board_id/[images|assets] * - *remove* from [old_board_id|no_board]/[images|assets] + * - decrement old board's totals for each image + * - increment new board's totals for each image */ added_image_names.forEach((image_name) => { @@ -1230,7 +1355,8 @@ export const imagesApi = api.injectEndpoints({ 'getImageDTO', image_name, (draft) => { - draft.board_id = new_board_id; + draft.board_id = + new_board_id === 'none' ? undefined : new_board_id; } ) ); @@ -1243,6 +1369,7 @@ export const imagesApi = api.injectEndpoints({ const categories = getCategories(imageDTO); const old_board_id = imageDTO.board_id; + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); // remove from the old board dispatch( @@ -1255,6 +1382,28 @@ export const imagesApi = api.injectEndpoints({ ) ); + // decrement old board's total + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + old_board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ); + + // increment new board's total + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + new_board_id ?? 'none', + (draft) => { + draft.total += 1; + } + ) + ); + const queryArgs = { board_id: new_board_id, categories, @@ -1264,9 +1413,7 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: previousTotal } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( new_board_id ?? 'none' )(getState()) @@ -1276,10 +1423,10 @@ export const imagesApi = api.injectEndpoints({ const isCacheFullyPopulated = currentCache.data && - currentCache.data.ids.length >= (previousTotal ?? 0); + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = - (previousTotal || 0) >= IMAGE_LIMIT + (data?.total ?? 0) >= IMAGE_LIMIT ? getIsImageInDateRange(currentCache.data, imageDTO) : true; @@ -1319,10 +1466,7 @@ export const imagesApi = api.injectEndpoints({ }), invalidatesTags: (result, error, { imageDTOs }) => { const touchedBoardIds: string[] = []; - const tags: ApiTagDescription[] = [ - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, - ]; + const tags: ApiTagDescription[] = []; result?.removed_image_names.forEach((image_name) => { const board_id = imageDTOs.find((i) => i.image_name === image_name) @@ -1333,8 +1477,6 @@ export const imagesApi = api.injectEndpoints({ } tags.push({ type: 'Board', id: board_id }); - tags.push({ type: 'BoardImagesTotal', id: board_id }); - tags.push({ type: 'BoardAssetsTotal', id: board_id }); }); return tags; @@ -1352,6 +1494,8 @@ export const imagesApi = api.injectEndpoints({ * - *update* getImageDTO for each image * - *remove* from old_board_id/[images|assets] * - *add* to no_board/[images|assets] + * - decrement old board's totals for each image + * - increment new board's (no board) totals for each image */ removed_image_names.forEach((image_name) => { @@ -1372,6 +1516,7 @@ export const imagesApi = api.injectEndpoints({ } const categories = getCategories(imageDTO); + const isAsset = ASSETS_CATEGORIES.includes(imageDTO.image_category); // remove from the old board dispatch( @@ -1384,6 +1529,28 @@ export const imagesApi = api.injectEndpoints({ ) ); + // decrement old board's total + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + imageDTO.board_id ?? 'none', + (draft) => { + draft.total = Math.max(draft.total - 1, 0); + } + ) + ); + + // increment new board's total (no board) + dispatch( + boardsApi.util.updateQueryData( + isAsset ? 'getBoardAssetsTotal' : 'getBoardImagesTotal', + 'none', + (draft) => { + draft.total += 1; + } + ) + ); + // add to `no_board` const queryArgs = { board_id: 'none', @@ -1394,9 +1561,7 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: total } = IMAGE_CATEGORIES.includes( - imageDTO.image_category - ) + const { data } = IMAGE_CATEGORIES.includes(imageDTO.image_category) ? boardsApi.endpoints.getBoardImagesTotal.select( imageDTO.board_id ?? 'none' )(getState()) @@ -1405,10 +1570,11 @@ export const imagesApi = api.injectEndpoints({ )(getState()); const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (total ?? 0); + currentCache.data && + currentCache.data.ids.length >= (data?.total ?? 0); const isInDateRange = - (total || 0) >= IMAGE_LIMIT + (data?.total ?? 0) >= IMAGE_LIMIT ? getIsImageInDateRange(currentCache.data, imageDTO) : true; diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts index a350979b89..29162597c5 100644 --- a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts +++ b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts @@ -13,7 +13,7 @@ export const useBoardTotal = (board_id: BoardId) => { const { data: totalAssets } = useGetBoardAssetsTotalQuery(board_id); const currentViewTotal = useMemo( - () => (galleryView === 'images' ? totalImages : totalAssets), + () => (galleryView === 'images' ? totalImages?.total : totalAssets?.total), [galleryView, totalAssets, totalImages] );