feat(ui): remove all calls to getBoardImagesTotals/getBoardAssetsTotals

This caused a crapload of network requests any time an image was generated.

The counts are necessary to handle the logic for inserting images into existing image list caches; we have to keep track of the counts.

Replace tag invalidation with manual cache updates in all cases, except the initial request (which is necessary to get the initial image counts).

One subtle change is to make the counts an object instead of a number. This is required for `immer` to handle draft states. This should be raised as a bug with RTK Query, as no error is thrown when attempting to update a primitive immer draft.
This commit is contained in:
psychedelicious 2023-10-04 20:32:04 +11:00
parent fbb61f2334
commit 2acc93eb8e
7 changed files with 370 additions and 173 deletions

View File

@ -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' },
])
);

View File

@ -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(

View File

@ -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) => {
>
<BoardContextMenu board_id="none">
{(ref) => (
<Flex
ref={ref}
onClick={handleSelectBoard}
sx={{
w: 'full',
h: 'full',
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 'base',
cursor: 'pointer',
bg: 'base.200',
_dark: {
bg: 'base.800',
},
}}
>
<Tooltip label={tooltip} openDelay={1000} hasArrow>
<Flex
ref={ref}
onClick={handleSelectBoard}
sx={{
w: 'full',
h: 'full',
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 'base',
cursor: 'pointer',
bg: 'base.200',
_dark: {
bg: 'base.800',
},
}}
>
<Image
src={InvokeAILogoImage}
alt="invoke-ai-logo"
<Flex
sx={{
opacity: 0.4,
filter: 'grayscale(1)',
mt: -6,
w: 16,
h: 16,
minW: 16,
minH: 16,
userSelect: 'none',
w: 'full',
h: 'full',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Image
src={InvokeAILogoImage}
alt="invoke-ai-logo"
sx={{
opacity: 0.4,
filter: 'grayscale(1)',
mt: -6,
w: 16,
h: 16,
minW: 16,
minH: 16,
userSelect: 'none',
}}
/>
</Flex>
{autoAddBoardId === 'none' && <AutoAddIcon />}
<Flex
sx={{
position: 'absolute',
bottom: 0,
left: 0,
p: 1,
justifyContent: 'center',
alignItems: 'center',
w: 'full',
maxW: 'full',
borderBottomRadius: 'base',
bg: isSelected ? 'accent.400' : 'base.500',
color: isSelected ? 'base.50' : 'base.100',
_dark: {
bg: isSelected ? 'accent.500' : 'base.600',
color: isSelected ? 'base.50' : 'base.100',
},
lineHeight: 'short',
fontSize: 'xs',
fontWeight: isSelected ? 700 : 500,
}}
>
{boardName}
</Flex>
<SelectionOverlay
isSelected={isSelected}
isHovered={isHovered}
/>
<IAIDroppable
data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>}
/>
</Flex>
{autoAddBoardId === 'none' && <AutoAddIcon />}
<Flex
sx={{
position: 'absolute',
bottom: 0,
left: 0,
p: 1,
justifyContent: 'center',
alignItems: 'center',
w: 'full',
maxW: 'full',
borderBottomRadius: 'base',
bg: isSelected ? 'accent.400' : 'base.500',
color: isSelected ? 'base.50' : 'base.100',
_dark: {
bg: isSelected ? 'accent.500' : 'base.600',
color: isSelected ? 'base.50' : 'base.100',
},
lineHeight: 'short',
fontSize: 'xs',
fontWeight: isSelected ? 700 : 500,
}}
>
{boardName}
</Flex>
<SelectionOverlay isSelected={isSelected} isHovered={isHovered} />
<IAIDroppable
data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>}
/>
</Flex>
</Tooltip>
)}
</BoardContextMenu>
</Flex>

View File

@ -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,

View File

@ -70,7 +70,7 @@ export const boardsApi = api.injectEndpoints({
keepUnusedDataFor: 0,
}),
getBoardImagesTotal: build.query<number, string | undefined>({
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<number, string | undefined>({
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 };
},
}),

View File

@ -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;

View File

@ -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]
);