diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
index af4b5bbe3b..1b2b10c897 100644
--- a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
+++ b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
@@ -175,9 +175,7 @@ export const isValidDrop = (
const destinationBoard = overData.context.boardId;
const isSameBoard = currentBoard === destinationBoard;
- const isDestinationValid = !currentBoard
- ? destinationBoard !== 'no_board'
- : true;
+ const isDestinationValid = !currentBoard ? destinationBoard : true;
return !isSameBoard && isDestinationValid;
}
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
index c3e789ff6e..a2ac34969f 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
@@ -1,20 +1,20 @@
import { log } from 'app/logging/useLogger';
import {
+ ASSETS_CATEGORIES,
+ IMAGE_CATEGORIES,
boardIdSelected,
+ galleryViewChanged,
imageSelected,
} from 'features/gallery/store/gallerySlice';
-import {
- getBoardIdQueryParamForBoard,
- getCategoriesQueryParamForBoard,
-} from 'features/gallery/store/util';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
+import { isAnyOf } from '@reduxjs/toolkit';
const moduleLog = log.child({ namespace: 'boards' });
export const addBoardIdSelectedListener = () => {
startAppListening({
- actionCreator: boardIdSelected,
+ matcher: isAnyOf(boardIdSelected, galleryViewChanged),
effect: async (
action,
{ getState, dispatch, condition, cancelActiveListeners }
@@ -22,12 +22,21 @@ export const addBoardIdSelectedListener = () => {
// Cancel any in-progress instances of this listener, we don't want to select an image from a previous board
cancelActiveListeners();
- const _board_id = action.payload;
- // when a board is selected, we need to wait until the board has loaded *some* images, then select the first one
+ const state = getState();
- const categories = getCategoriesQueryParamForBoard(_board_id);
- const board_id = getBoardIdQueryParamForBoard(_board_id);
- const queryArgs = { board_id, categories };
+ const board_id = boardIdSelected.match(action)
+ ? action.payload
+ : state.gallery.selectedBoardId;
+
+ const galleryView = galleryViewChanged.match(action)
+ ? action.payload
+ : state.gallery.galleryView;
+
+ // when a board is selected, we need to wait until the board has loaded *some* images, then select the first one
+ const categories =
+ galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
+
+ const queryArgs = { board_id: board_id ?? 'none', categories };
// wait until the board has some images - maybe it already has some from a previous fetch
// must use getState() to ensure we do not have stale state
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
index 4da7264cbb..a7c8306c64 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
@@ -156,14 +156,13 @@ export const addImageDroppedListener = () => {
if (
overData.actionType === 'MOVE_BOARD' &&
activeData.payloadType === 'IMAGE_DTO' &&
- activeData.payload.imageDTO &&
- overData.context.boardId
+ activeData.payload.imageDTO
) {
const { imageDTO } = activeData.payload;
const { boardId } = overData.context;
- // if the board is "No Board", this is a remove action
- if (boardId === 'no_board') {
+ // image was droppe on the "NoBoardBoard"
+ if (!boardId) {
dispatch(
imagesApi.endpoints.removeImageFromBoard.initiate({
imageDTO,
@@ -172,12 +171,7 @@ export const addImageDroppedListener = () => {
return;
}
- // Handle adding image to batch
- if (boardId === 'batch') {
- // TODO
- }
-
- // Otherwise, add the image to the board
+ // image was dropped on a user board
dispatch(
imagesApi.endpoints.addImageToBoard.initiate({
imageDTO,
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts
index d6a24cda24..851b6be33f 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUpdated.ts
@@ -5,30 +5,30 @@ import { startAppListening } from '..';
const moduleLog = log.child({ namespace: 'image' });
export const addImageUpdatedFulfilledListener = () => {
- startAppListening({
- matcher: imagesApi.endpoints.updateImage.matchFulfilled,
- effect: (action, { dispatch, getState }) => {
- moduleLog.debug(
- {
- data: {
- oldImage: action.meta.arg.originalArgs,
- updatedImage: action.payload,
- },
- },
- 'Image updated'
- );
- },
- });
+ // startAppListening({
+ // matcher: imagesApi.endpoints.updateImage.matchFulfilled,
+ // effect: (action, { dispatch, getState }) => {
+ // moduleLog.debug(
+ // {
+ // data: {
+ // oldImage: action.meta.arg.originalArgs,
+ // updatedImage: action.payload,
+ // },
+ // },
+ // 'Image updated'
+ // );
+ // },
+ // });
};
export const addImageUpdatedRejectedListener = () => {
- startAppListening({
- matcher: imagesApi.endpoints.updateImage.matchRejected,
- effect: (action, { dispatch }) => {
- moduleLog.debug(
- { data: action.meta.arg.originalArgs },
- 'Image update failed'
- );
- },
- });
+ // startAppListening({
+ // matcher: imagesApi.endpoints.updateImage.matchRejected,
+ // effect: (action, { dispatch }) => {
+ // moduleLog.debug(
+ // { data: action.meta.arg.originalArgs },
+ // 'Image update failed'
+ // );
+ // },
+ // });
};
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 97cccfa05c..df67f286d6 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
@@ -3,6 +3,7 @@ import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import {
IMAGE_CATEGORIES,
boardIdSelected,
+ galleryViewChanged,
imageSelected,
} from 'features/gallery/store/gallerySlice';
import { progressImageSet } from 'features/system/store/systemSlice';
@@ -55,34 +56,6 @@ export const addInvocationCompleteEventListener = () => {
}
if (!imageDTO.is_intermediate) {
- // update the cache for 'All Images'
- dispatch(
- imagesApi.util.updateQueryData(
- 'listImages',
- {
- categories: IMAGE_CATEGORIES,
- },
- (draft) => {
- imagesAdapter.addOne(draft, imageDTO);
- draft.total = draft.total + 1;
- }
- )
- );
-
- // update the cache for 'No Board'
- dispatch(
- imagesApi.util.updateQueryData(
- 'listImages',
- {
- board_id: 'none',
- },
- (draft) => {
- imagesAdapter.addOne(draft, imageDTO);
- draft.total = draft.total + 1;
- }
- )
- );
-
const { autoAddBoardId } = gallery;
// add image to the board if auto-add is enabled
@@ -93,8 +66,31 @@ export const addInvocationCompleteEventListener = () => {
imageDTO,
})
);
+ } else {
+ // add to no board board
+ // update the cache for 'No Board'
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ {
+ board_id: 'none',
+ categories: IMAGE_CATEGORIES,
+ },
+ (draft) => {
+ imagesAdapter.addOne(draft, imageDTO);
+ draft.total = draft.total + 1;
+ }
+ )
+ );
}
+ dispatch(
+ imagesApi.util.invalidateTags([
+ { type: 'BoardImagesTotal', id: autoAddBoardId ?? 'none' },
+ { type: 'BoardAssetsTotal', id: autoAddBoardId ?? 'none' },
+ ])
+ );
+
const { selectedBoardId, shouldAutoSwitch } = gallery;
// If auto-switch is enabled, select the new image
@@ -102,8 +98,9 @@ export const addInvocationCompleteEventListener = () => {
// if auto-add is enabled, switch the board as the image comes in
if (autoAddBoardId && autoAddBoardId !== selectedBoardId) {
dispatch(boardIdSelected(autoAddBoardId));
+ dispatch(galleryViewChanged('images'));
} else if (!autoAddBoardId) {
- dispatch(boardIdSelected('images'));
+ dispatch(galleryViewChanged('images'));
}
dispatch(imageSelected(imageDTO.image_name));
}
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx
new file mode 100644
index 0000000000..e9052a20c9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx
@@ -0,0 +1,23 @@
+import { Badge, Flex } from '@chakra-ui/react';
+
+const AutoAddIcon = () => {
+ return (
+
+
+ auto
+
+
+ );
+};
+
+export default AutoAddIcon;
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx
index 827d49c88e..ad0e5ab80d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx
@@ -52,7 +52,7 @@ const BoardAutoAddSelect = () => {
return;
}
- dispatch(autoAddBoardIdChanged(v === 'none' ? null : v));
+ dispatch(autoAddBoardIdChanged(v === 'none' ? undefined : v));
},
[dispatch]
);
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
index 3b3303f0c8..b23360555b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
@@ -1,4 +1,4 @@
-import { Box, MenuItem, MenuList } from '@chakra-ui/react';
+import { MenuItem, MenuList } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
@@ -7,11 +7,11 @@ import { FaFolder } from 'react-icons/fa';
import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
-import SystemBoardContextMenuItems from './SystemBoardContextMenuItems';
+import NoBoardContextMenuItems from './NoBoardContextMenuItems';
type Props = {
board?: BoardDTO;
- board_id: string;
+ board_id?: string;
children: ContextMenuProps['children'];
setBoardToDelete?: (board?: BoardDTO) => void;
};
@@ -19,9 +19,11 @@ type Props = {
const BoardContextMenu = memo(
({ board, board_id, setBoardToDelete, children }: Props) => {
const dispatch = useAppDispatch();
+
const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected(board?.board_id ?? board_id));
}, [board?.board_id, board_id, dispatch]);
+
return (
menuProps={{ size: 'sm', isLazy: true }}
@@ -37,7 +39,7 @@ const BoardContextMenu = memo(
} onClickCapture={handleSelectBoard}>
Select Board
- {!board && }
+ {!board && }
{board && (
{
)
: boards;
const [boardToDelete, setBoardToDelete] = useState();
- const [isSearching, setIsSearching] = useState(false);
- const handleClickSearchIcon = useCallback(() => {
- setIsSearching((v) => !v);
- }, []);
return (
<>
@@ -61,54 +58,7 @@ const BoardsList = (props: Props) => {
}}
>
-
- {isSearching ? (
-
-
-
- ) : (
-
-
-
-
-
-
-
- )}
-
- }
- />
+
{
maxH: 346,
}}
>
+
+
+
{filteredBoards &&
filteredBoards.map((board) => (
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx
index f556b83d24..800ffc651f 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx
@@ -28,12 +28,7 @@ const selector = createSelector(
defaultSelectorOptions
);
-type Props = {
- setIsSearching: (isSearching: boolean) => void;
-};
-
-const BoardsSearch = (props: Props) => {
- const { setIsSearching } = props;
+const BoardsSearch = () => {
const dispatch = useAppDispatch();
const { searchText } = useAppSelector(selector);
const inputRef = useRef(null);
@@ -47,8 +42,7 @@ const BoardsSearch = (props: Props) => {
const clearBoardSearch = useCallback(() => {
dispatch(setBoardSearchText(''));
- setIsSearching(false);
- }, [dispatch, setIsSearching]);
+ }, [dispatch]);
const handleKeydown = useCallback(
(e: KeyboardEvent) => {
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 5d76ad743c..8b5d871799 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
@@ -19,17 +19,14 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDroppable from 'common/components/IAIDroppable';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo, useState } from 'react';
-import { FaFolder } from 'react-icons/fa';
+import { FaUser } from 'react-icons/fa';
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
+import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
import { BoardDTO } from 'services/api/types';
+import AutoAddIcon from '../AutoAddIcon';
import BoardContextMenu from '../BoardContextMenu';
-const AUTO_ADD_BADGE_STYLES: ChakraProps['sx'] = {
- bg: 'accent.200',
- color: 'blackAlpha.900',
-};
-
const BASE_BADGE_STYLES: ChakraProps['sx'] = {
bg: 'base.500',
color: 'whiteAlpha.900',
@@ -64,6 +61,8 @@ const GalleryBoard = memo(
board.cover_image_name ?? skipToken
);
+ const { totalImages, totalAssets } = useBoardTotal(board.board_id);
+
const { board_name, board_id } = board;
const [localBoardName, setLocalBoardName] = useState(board_name);
@@ -143,56 +142,48 @@ const GalleryBoard = memo(
alignItems: 'center',
borderRadius: 'base',
cursor: 'pointer',
+ bg: 'base.200',
+ _dark: {
+ bg: 'base.800',
+ },
}}
>
-
- {coverImage?.thumbnail_url ? (
-
+ ) : (
+
+
- ) : (
-
-
-
- )}
-
+
+ )}
-
- {board.image_count}
+
+ {totalImages}/{totalAssets}
+ {isSelectedForAutoAdd && }
{
- const dispatch = useDispatch();
+const selector = createSelector(
+ stateSelector,
+ ({ gallery }) => {
+ const { autoAddBoardId } = gallery;
+ return { autoAddBoardId };
+ },
+ defaultSelectorOptions
+);
- const handleClick = () => {
- dispatch(boardIdSelected('no_board'));
- };
+const NoBoardBoard = memo(({ isSelected }: Props) => {
+ const dispatch = useAppDispatch();
+ const { totalImages, totalAssets } = useBoardTotal(undefined);
+ const { autoAddBoardId } = useAppSelector(selector);
+ const handleSelectBoard = useCallback(() => {
+ dispatch(boardIdSelected(undefined));
+ }, [dispatch]);
- const { total } = useListImagesQuery(baseQueryArg, {
- selectFromResult: ({ data }) => ({ total: data?.total ?? 0 }),
- });
-
- // TODO: Do we support making 'images' 'assets? if yes, we need to handle this
- const droppableData: MoveBoardDropData = {
- id: 'all-images-board',
- actionType: 'MOVE_BOARD',
- context: { boardId: 'no_board' },
- };
+ const droppableData: MoveBoardDropData = useMemo(
+ () => ({
+ id: 'no_board',
+ actionType: 'MOVE_BOARD',
+ context: { boardId: undefined },
+ }),
+ []
+ );
return (
- Move}
- onClick={handleClick}
- isSelected={isSelected}
- icon={FaFolderOpen}
- label="No Board"
- badgeCount={total}
- />
+
+
+
+ {(ref) => (
+
+
+
+
+
+
+ {totalImages}/{totalAssets}
+
+
+ {!autoAddBoardId && }
+
+ Move}
+ />
+
+ )}
+
+
+
);
-};
+});
+
+NoBoardBoard.displayName = 'HoverableBoard';
export default NoBoardBoard;
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx
index 5d39eaaf28..e8bd1be992 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx
@@ -5,7 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
-import { FaMinus, FaPlus, FaTrash } from 'react-icons/fa';
+import { FaPlus, FaTrash } from 'react-icons/fa';
import { BoardDTO } from 'services/api/types';
type Props = {
@@ -42,7 +42,7 @@ const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
const handleToggleAutoAdd = useCallback(() => {
dispatch(
- autoAddBoardIdChanged(isSelectedForAutoAdd ? null : board.board_id)
+ autoAddBoardIdChanged(isSelectedForAutoAdd ? undefined : board.board_id)
);
}, [board.board_id, dispatch, isSelectedForAutoAdd]);
@@ -59,16 +59,15 @@ const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
*/}
>
)}
- : }
- onClickCapture={handleToggleAutoAdd}
- >
- {isSelectedForAutoAdd ? 'Disable Auto-Add' : 'Auto-Add to this Board'}
-
+ {!isSelectedForAutoAdd && (
+ } onClick={handleToggleAutoAdd}>
+ Auto-add to this Board
+
+ )}
}
- onClickCapture={handleDelete}
+ onClick={handleDelete}
>
Delete Board
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx
new file mode 100644
index 0000000000..34b4c5f790
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx
@@ -0,0 +1,28 @@
+import { MenuItem } from '@chakra-ui/react';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
+import { memo, useCallback } from 'react';
+import { FaPlus } from 'react-icons/fa';
+
+const NoBoardContextMenuItems = () => {
+ const dispatch = useAppDispatch();
+
+ const autoAddBoardId = useAppSelector(
+ (state) => state.gallery.autoAddBoardId
+ );
+ const handleDisableAutoAdd = useCallback(() => {
+ dispatch(autoAddBoardIdChanged(undefined));
+ }, [dispatch]);
+
+ return (
+ <>
+ {autoAddBoardId && (
+ } onClick={handleDisableAutoAdd}>
+ Auto-add to this Board
+
+ )}
+ >
+ );
+};
+
+export default memo(NoBoardContextMenuItems);
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/SystemBoardContextMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/SystemBoardContextMenuItems.tsx
deleted file mode 100644
index 58eb6d2c0c..0000000000
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/SystemBoardContextMenuItems.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { BoardId } from 'features/gallery/store/gallerySlice';
-import { memo } from 'react';
-
-type Props = {
- board_id: BoardId;
-};
-
-const SystemBoardContextMenuItems = ({ board_id }: Props) => {
- return <>>;
-};
-
-export default memo(SystemBoardContextMenuItems);
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx
index 60926e165e..7e2048e628 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx
@@ -1,5 +1,5 @@
import { ChevronUpIcon } from '@chakra-ui/icons';
-import { Box, Button, Flex, Spacer, Text } from '@chakra-ui/react';
+import { Button, Flex, Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
@@ -27,52 +27,60 @@ const GalleryBoardName = (props: Props) => {
const { isOpen, onToggle } = props;
const { selectedBoardId } = useAppSelector(selector);
const boardName = useBoardName(selectedBoardId);
- const numOfBoardImages = useBoardTotal(selectedBoardId);
+ const { totalImages, totalAssets } = useBoardTotal(selectedBoardId);
const formattedBoardName = useMemo(() => {
- if (!boardName) return '';
- if (boardName && !numOfBoardImages) return boardName;
- if (boardName.length > 20) {
- return `${boardName.substring(0, 20)}... (${numOfBoardImages})`;
+ if (!boardName) {
+ return '';
}
- return `${boardName} (${numOfBoardImages})`;
- }, [boardName, numOfBoardImages]);
+
+ if (boardName && (totalImages === undefined || totalAssets === undefined)) {
+ return boardName;
+ }
+
+ const count = `${totalImages}/${totalAssets}`;
+
+ if (boardName.length > 20) {
+ return `${boardName.substring(0, 20)}... (${count})`;
+ }
+ return `${boardName} (${count})`;
+ }, [boardName, totalAssets, totalImages]);
return (
-
-
-
- {formattedBoardName}
-
-
-
+
+ {formattedBoardName}
+
{
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
- const { currentData } = useGetImageMetadataQuery(imageDTO.image_name);
+ const [debouncedMetadataQueryArg, debounceState] = useDebounce(
+ imageDTO.image_name,
+ 500
+ );
+
+ const { currentData } = useGetImageMetadataQuery(
+ debounceState.isPending()
+ ? skipToken
+ : debouncedMetadataQueryArg ?? skipToken
+ );
const { isClipboardAPIAvailable, copyImageToClipboard } =
useCopyImageToClipboard();
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
index 98b4c33408..1b3f220311 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
@@ -1,23 +1,37 @@
-import { Box, Flex, VStack, useDisclosure } from '@chakra-ui/react';
+import {
+ Box,
+ ButtonGroup,
+ Flex,
+ Spacer,
+ Tab,
+ TabList,
+ Tabs,
+ VStack,
+ useDisclosure,
+} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
-import { useAppSelector } from 'app/store/storeHooks';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
-import { memo, useRef } from 'react';
+import { memo, useCallback, useRef } from 'react';
import BoardsList from './Boards/BoardsList/BoardsList';
import GalleryBoardName from './GalleryBoardName';
import GalleryPinButton from './GalleryPinButton';
import GallerySettingsPopover from './GallerySettingsPopover';
import BatchImageGrid from './ImageGrid/BatchImageGrid';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
+import IAIButton from 'common/components/IAIButton';
+import { FaImages, FaServer } from 'react-icons/fa';
+import { galleryViewChanged } from '../store/gallerySlice';
const selector = createSelector(
[stateSelector],
(state) => {
- const { selectedBoardId } = state.gallery;
+ const { selectedBoardId, galleryView } = state.gallery;
return {
selectedBoardId,
+ galleryView,
};
},
defaultSelectorOptions
@@ -26,10 +40,19 @@ const selector = createSelector(
const ImageGalleryContent = () => {
const resizeObserverRef = useRef(null);
const galleryGridRef = useRef(null);
- const { selectedBoardId } = useAppSelector(selector);
+ const { selectedBoardId, galleryView } = useAppSelector(selector);
+ const dispatch = useAppDispatch();
const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } =
useDisclosure();
+ const handleClickImages = useCallback(() => {
+ dispatch(galleryViewChanged('images'));
+ }, [dispatch]);
+
+ const handleClickAssets = useCallback(() => {
+ dispatch(galleryViewChanged('assets'));
+ }, [dispatch]);
+
return (
{
gap: 2,
}}
>
-
+
@@ -60,6 +83,36 @@ const ImageGalleryContent = () => {
+
+
+
+
+ Images
+
+
+ Assets
+
+
+
+
+
{selectedBoardId === 'batch' ? (
) : (
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 a43f9ce07b..e4b996fc96 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx
@@ -19,6 +19,7 @@ import GalleryImage from './GalleryImage';
import ImageGridItemContainer from './ImageGridItemContainer';
import ImageGridListContainer from './ImageGridListContainer';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
+import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
defer: true,
@@ -40,7 +41,10 @@ const GalleryImageGrid = () => {
const [initialize, osInstance] = useOverlayScrollbars(
overlayScrollbarsConfig
);
-
+ const selectedBoardId = useAppSelector(
+ (state) => state.gallery.selectedBoardId
+ );
+ const { currentViewTotal } = useBoardTotal(selectedBoardId);
const queryArgs = useAppSelector(selectListImagesBaseQueryArgs);
const { currentData, isFetching, isSuccess, isError } =
@@ -49,19 +53,23 @@ const GalleryImageGrid = () => {
const [listImages] = useLazyListImagesQuery();
const areMoreAvailable = useMemo(() => {
- if (!currentData) {
+ if (!currentData || !currentViewTotal) {
return false;
}
- return currentData.ids.length < currentData.total;
- }, [currentData]);
+ return currentData.ids.length < currentViewTotal;
+ }, [currentData, currentViewTotal]);
const handleLoadMoreImages = useCallback(() => {
+ if (!areMoreAvailable) {
+ return;
+ }
+
listImages({
...queryArgs,
offset: currentData?.ids.length ?? 0,
limit: IMAGE_LIMIT,
});
- }, [listImages, queryArgs, currentData?.ids.length]);
+ }, [areMoreAvailable, listImages, queryArgs, currentData?.ids.length]);
useEffect(() => {
// Initialize the gallery's custom scrollbar
diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts
index b389ffff50..7b4b8bed7b 100644
--- a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts
+++ b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts
@@ -4,7 +4,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
IMAGE_LIMIT,
imageSelected,
- selectImagesById,
} from 'features/gallery/store/gallerySlice';
import { clamp, isEqual } from 'lodash-es';
import { useCallback } from 'react';
@@ -53,8 +52,8 @@ export const nextPrevImageButtonsSelector = createSelector(
const prevImageIndex = clamp(currentImageIndex - 1, 0, images.length - 1);
- const nextImageId = images[nextImageIndex].image_name;
- const prevImageId = images[prevImageIndex].image_name;
+ const nextImageId = images[nextImageIndex]?.image_name;
+ const prevImageId = images[prevImageIndex]?.image_name;
const nextImage = selectors.selectById(data, nextImageId);
const prevImage = selectors.selectById(data, prevImageId);
@@ -65,7 +64,7 @@ export const nextPrevImageButtonsSelector = createSelector(
isOnFirstImage: currentImageIndex === 0,
isOnLastImage:
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
- areMoreImagesAvailable: data?.total ?? 0 > imagesLength,
+ areMoreImagesAvailable: (data?.total ?? 0) > imagesLength,
isFetching: status === 'pending',
nextImage,
prevImage,
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index 19da92e083..db520c2f35 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -2,11 +2,11 @@ import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { ListImagesArgs } from 'services/api/endpoints/images';
-import { INITIAL_IMAGE_LIMIT } from './gallerySlice';
import {
- getBoardIdQueryParamForBoard,
- getCategoriesQueryParamForBoard,
-} from './util';
+ ASSETS_CATEGORIES,
+ IMAGE_CATEGORIES,
+ INITIAL_IMAGE_LIMIT,
+} from './gallerySlice';
export const gallerySelector = (state: RootState) => state.gallery;
@@ -19,14 +19,13 @@ export const selectLastSelectedImage = createSelector(
export const selectListImagesBaseQueryArgs = createSelector(
[(state: RootState) => state],
(state) => {
- const { selectedBoardId } = state.gallery;
-
- const categories = getCategoriesQueryParamForBoard(selectedBoardId);
- const board_id = getBoardIdQueryParamForBoard(selectedBoardId);
+ const { selectedBoardId, galleryView } = state.gallery;
+ const categories =
+ galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
const listImagesBaseQueryArgs: ListImagesArgs = {
+ board_id: selectedBoardId ?? 'none',
categories,
- board_id,
offset: 0,
limit: INITIAL_IMAGE_LIMIT,
is_intermediate: false,
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 314f933e9b..ab4942d927 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -14,20 +14,17 @@ export const ASSETS_CATEGORIES: ImageCategory[] = [
export const INITIAL_IMAGE_LIMIT = 100;
export const IMAGE_LIMIT = 20;
-// export type GalleryView = 'images' | 'assets';
-export type BoardId =
- | 'images'
- | 'assets'
- | 'no_board'
- | 'batch'
- | (string & Record);
+export type GalleryView = 'images' | 'assets';
+// export type BoardId = 'no_board' | (string & Record);
+export type BoardId = string | undefined;
type GalleryState = {
selection: string[];
shouldAutoSwitch: boolean;
- autoAddBoardId: string | null;
+ autoAddBoardId: string | undefined;
galleryImageMinimumWidth: number;
selectedBoardId: BoardId;
+ galleryView: GalleryView;
batchImageNames: string[];
isBatchEnabled: boolean;
};
@@ -35,9 +32,10 @@ type GalleryState = {
export const initialGalleryState: GalleryState = {
selection: [],
shouldAutoSwitch: true,
- autoAddBoardId: null,
+ autoAddBoardId: undefined,
galleryImageMinimumWidth: 96,
- selectedBoardId: 'images',
+ selectedBoardId: undefined,
+ galleryView: 'images',
batchImageNames: [],
isBatchEnabled: false,
};
@@ -96,6 +94,7 @@ export const gallerySlice = createSlice({
},
boardIdSelected: (state, action: PayloadAction) => {
state.selectedBoardId = action.payload;
+ state.galleryView = 'images';
},
isBatchEnabledChanged: (state, action: PayloadAction) => {
state.isBatchEnabled = action.payload;
@@ -125,9 +124,15 @@ export const gallerySlice = createSlice({
state.batchImageNames = [];
state.selection = [];
},
- autoAddBoardIdChanged: (state, action: PayloadAction) => {
+ autoAddBoardIdChanged: (
+ state,
+ action: PayloadAction
+ ) => {
state.autoAddBoardId = action.payload;
},
+ galleryViewChanged: (state, action: PayloadAction) => {
+ state.galleryView = action.payload;
+ },
},
extraReducers: (builder) => {
builder.addMatcher(
@@ -135,10 +140,11 @@ export const gallerySlice = createSlice({
(state, action) => {
const deletedBoardId = action.meta.arg.originalArgs;
if (deletedBoardId === state.selectedBoardId) {
- state.selectedBoardId = 'images';
+ state.selectedBoardId = undefined;
+ state.galleryView = 'images';
}
if (deletedBoardId === state.autoAddBoardId) {
- state.autoAddBoardId = null;
+ state.autoAddBoardId = undefined;
}
}
);
@@ -151,7 +157,7 @@ export const gallerySlice = createSlice({
}
if (!boards.map((b) => b.board_id).includes(state.autoAddBoardId)) {
- state.autoAddBoardId = null;
+ state.autoAddBoardId = undefined;
}
}
);
@@ -170,6 +176,7 @@ export const {
imagesAddedToBatch,
imagesRemovedFromBatch,
autoAddBoardIdChanged,
+ galleryViewChanged,
} = gallerySlice.actions;
export default gallerySlice.reducer;
diff --git a/invokeai/frontend/web/src/features/gallery/store/util.ts b/invokeai/frontend/web/src/features/gallery/store/util.ts
index fcc39bae82..dfe812822f 100644
--- a/invokeai/frontend/web/src/features/gallery/store/util.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/util.ts
@@ -1,6 +1,11 @@
-import { SYSTEM_BOARDS } from 'services/api/endpoints/images';
-import { ASSETS_CATEGORIES, BoardId, IMAGE_CATEGORIES } from './gallerySlice';
-import { ImageCategory } from 'services/api/types';
+import { ListImagesArgs, SYSTEM_BOARDS } from 'services/api/endpoints/images';
+import {
+ ASSETS_CATEGORIES,
+ BoardId,
+ GalleryView,
+ IMAGE_CATEGORIES,
+} from './gallerySlice';
+import { ImageCategory, ImageDTO } from 'services/api/types';
import { isEqual } from 'lodash-es';
export const getCategoriesQueryParamForBoard = (
@@ -20,16 +25,11 @@ export const getCategoriesQueryParamForBoard = (
export const getBoardIdQueryParamForBoard = (
board_id: BoardId
-): string | undefined => {
- if (board_id === 'no_board') {
+): string | null => {
+ if (board_id === undefined) {
return 'none';
}
- // system boards besides 'no_board'
- if (SYSTEM_BOARDS.includes(board_id)) {
- return undefined;
- }
-
// user boards
return board_id;
};
@@ -52,3 +52,10 @@ export const getBoardIdFromBoardAndCategoriesQueryParam = (
return board_id ?? 'UNKNOWN_BOARD';
};
+
+export const getCategories = (imageDTO: ImageDTO) => {
+ if (IMAGE_CATEGORIES.includes(imageDTO.image_category)) {
+ return IMAGE_CATEGORIES;
+ }
+ return ASSETS_CATEGORIES;
+};
diff --git a/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts b/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts
index 368303c7c5..2dc292321e 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/boardImages.ts
@@ -1,52 +1,36 @@
-import { ImageDTO, OffsetPaginatedResults_ImageDTO_ } from 'services/api/types';
-import { ApiFullTagDescription, LIST_TAG, api } from '..';
-import { paths } from '../schema';
-import { BoardId } from 'features/gallery/store/gallerySlice';
-
-type ListBoardImagesArg =
- paths['/api/v1/board_images/{board_id}']['get']['parameters']['path'] &
- paths['/api/v1/board_images/{board_id}']['get']['parameters']['query'];
-
-type AddImageToBoardArg =
- paths['/api/v1/board_images/']['post']['requestBody']['content']['application/json'];
-
-type RemoveImageFromBoardArg =
- paths['/api/v1/board_images/']['delete']['requestBody']['content']['application/json'];
+import { api } from '..';
export const boardImagesApi = api.injectEndpoints({
endpoints: (build) => ({
/**
* Board Images Queries
*/
-
- listBoardImages: build.query<
- OffsetPaginatedResults_ImageDTO_,
- ListBoardImagesArg
- >({
- query: ({ board_id, offset, limit }) => ({
- url: `board_images/${board_id}`,
- method: 'GET',
- }),
- providesTags: (result, error, arg) => {
- // any list of boardimages
- const tags: ApiFullTagDescription[] = [
- { type: 'BoardImage', id: `${arg.board_id}_${LIST_TAG}` },
- ];
-
- if (result) {
- // and individual tags for each boardimage
- tags.push(
- ...result.items.map(({ board_id, image_name }) => ({
- type: 'BoardImage' as const,
- id: `${board_id}_${image_name}`,
- }))
- );
- }
-
- return tags;
- },
- }),
+ // listBoardImages: build.query<
+ // OffsetPaginatedResults_ImageDTO_,
+ // ListBoardImagesArg
+ // >({
+ // query: ({ board_id, offset, limit }) => ({
+ // url: `board_images/${board_id}`,
+ // method: 'GET',
+ // }),
+ // providesTags: (result, error, arg) => {
+ // // any list of boardimages
+ // const tags: ApiFullTagDescription[] = [
+ // { type: 'BoardImage', id: `${arg.board_id}_${LIST_TAG}` },
+ // ];
+ // if (result) {
+ // // and individual tags for each boardimage
+ // tags.push(
+ // ...result.items.map(({ board_id, image_name }) => ({
+ // type: 'BoardImage' as const,
+ // id: `${board_id}_${image_name}`,
+ // }))
+ // );
+ // }
+ // return tags;
+ // },
+ // }),
}),
});
-export const { useListBoardImagesQuery } = boardImagesApi;
+// export const { useListBoardImagesQuery } = boardImagesApi;
diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts
index b019652ce5..779e5708fe 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts
@@ -109,10 +109,25 @@ export const boardsApi = api.injectEndpoints({
deleteBoard: build.mutation({
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
- invalidatesTags: (result, error, arg) => [
- { type: 'Board', id: arg },
+ invalidatesTags: (result, error, board_id) => [
+ { type: 'Board', id: LIST_TAG },
// invalidate the 'No Board' cache
- { type: 'ImageList', id: getListImagesUrl({ board_id: 'none' }) },
+ {
+ type: 'ImageList',
+ id: getListImagesUrl({
+ board_id: 'none',
+ categories: IMAGE_CATEGORIES,
+ }),
+ },
+ {
+ type: 'ImageList',
+ id: getListImagesUrl({
+ board_id: 'none',
+ categories: ASSETS_CATEGORIES,
+ }),
+ },
+ { type: 'BoardImagesTotal', id: 'none' },
+ { type: 'BoardAssetsTotal', id: 'none' },
],
async onQueryStarted(board_id, { dispatch, queryFulfilled, getState }) {
/**
@@ -167,24 +182,14 @@ export const boardsApi = api.injectEndpoints({
'listImages',
queryArgs,
(draft) => {
- const oldCount = imagesAdapter
- .getSelectors()
- .selectTotal(draft);
+ const oldTotal = draft.total;
const newState = imagesAdapter.updateMany(draft, updates);
- const newCount = imagesAdapter
- .getSelectors()
- .selectTotal(newState);
- draft.total = Math.max(
- draft.total - (oldCount - newCount),
- 0
- );
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
}
)
);
});
-
- // after deleting a board, select the 'All Images' board
- dispatch(boardIdSelected('images'));
} catch {
//no-op
}
@@ -197,9 +202,24 @@ export const boardsApi = api.injectEndpoints({
method: 'DELETE',
params: { include_images: true },
}),
- invalidatesTags: (result, error, arg) => [
- { type: 'Board', id: arg },
- { type: 'ImageList', id: getListImagesUrl({ board_id: 'none' }) },
+ invalidatesTags: (result, error, board_id) => [
+ { type: 'Board', id: LIST_TAG },
+ {
+ type: 'ImageList',
+ id: getListImagesUrl({
+ board_id: 'none',
+ categories: IMAGE_CATEGORIES,
+ }),
+ },
+ {
+ type: 'ImageList',
+ id: getListImagesUrl({
+ board_id: 'none',
+ categories: ASSETS_CATEGORIES,
+ }),
+ },
+ { type: 'BoardImagesTotal', id: 'none' },
+ { type: 'BoardAssetsTotal', id: 'none' },
],
async onQueryStarted(board_id, { dispatch, queryFulfilled, getState }) {
/**
@@ -231,27 +251,17 @@ export const boardsApi = api.injectEndpoints({
'listImages',
queryArgs,
(draft) => {
- const oldCount = imagesAdapter
- .getSelectors()
- .selectTotal(draft);
+ const oldTotal = draft.total;
const newState = imagesAdapter.removeMany(
draft,
deleted_images
);
- const newCount = imagesAdapter
- .getSelectors()
- .selectTotal(newState);
- draft.total = Math.max(
- draft.total - (oldCount - newCount),
- 0
- );
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
}
)
);
});
-
- // after deleting a board, select the 'All Images' board
- dispatch(boardIdSelected('images'));
} catch {
//no-op
}
diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts
index 5eeb86d9c5..d1bf8e3977 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/images.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts
@@ -6,18 +6,17 @@ import {
BoardId,
IMAGE_CATEGORIES,
} from 'features/gallery/store/gallerySlice';
-import { omit } from 'lodash-es';
+import { getCategories } from 'features/gallery/store/util';
import queryString from 'query-string';
import { ApiFullTagDescription, api } from '..';
import { components, paths } from '../schema';
import {
ImageCategory,
- ImageChanges,
ImageDTO,
OffsetPaginatedResults_ImageDTO_,
PostUploadAction,
} from '../types';
-import { getCacheAction } from './util';
+import { getIsImageInDateRange } from './util';
export type ListImagesArgs = NonNullable<
paths['/api/v1/images/']['get']['parameters']['query']
@@ -155,6 +154,42 @@ export const imagesApi = api.injectEndpoints({
},
keepUnusedDataFor: 86400, // 24 hours
}),
+ getBoardImagesTotal: build.query({
+ query: (board_id) => ({
+ url: getListImagesUrl({
+ board_id: board_id ?? 'none',
+ categories: IMAGE_CATEGORIES,
+ is_intermediate: false,
+ limit: 0,
+ offset: 0,
+ }),
+ method: 'GET',
+ }),
+ providesTags: (result, error, arg) => [
+ { type: 'BoardImagesTotal', id: arg ?? 'none' },
+ ],
+ transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => {
+ return response.total;
+ },
+ }),
+ getBoardAssetsTotal: build.query({
+ query: (board_id) => ({
+ url: getListImagesUrl({
+ board_id: board_id ?? 'none',
+ categories: ASSETS_CATEGORIES,
+ is_intermediate: false,
+ limit: 0,
+ offset: 0,
+ }),
+ method: 'GET',
+ }),
+ providesTags: (result, error, arg) => [
+ { type: 'BoardAssetsTotal', id: arg ?? 'none' },
+ ],
+ transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => {
+ return response.total;
+ },
+ }),
clearIntermediates: build.mutation({
query: () => ({ url: `images/clear-intermediates`, method: 'POST' }),
invalidatesTags: ['IntermediatesCount'],
@@ -164,56 +199,42 @@ export const imagesApi = api.injectEndpoints({
url: `images/${image_name}`,
method: 'DELETE',
}),
- invalidatesTags: (result, error, arg) => [
- { type: 'Image', id: arg.image_name },
+ 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`:
- * - *remove* from "All Images" / "All Assets"
- * - IF it has a board:
- * - THEN *remove* from it's own board
- * - ELSE *remove* from "No Board"
+ * - NOT POSSIBLE: *remove* from getImageDTO
+ * - $cache = [board_id|no_board]/[images|assets]
+ * - *remove* from $cache
*/
- const { image_name, board_id, image_category } = imageDTO;
+ const { image_name, board_id } = imageDTO;
- // Figure out the `listImages` caches that we need to update
- // That means constructing the possible query args that are serialized into the cache key...
-
- const removeFromCacheKeys: ListImagesArgs[] = [];
+ // Store patches so we can undo if the query fails
+ const patches: PatchCollection[] = [];
// determine `categories`, i.e. do we update "All Images" or "All Assets"
- const categories = IMAGE_CATEGORIES.includes(image_category)
- ? IMAGE_CATEGORIES
- : ASSETS_CATEGORIES;
+ // $cache = [board_id|no_board]/[images|assets]
+ const categories = getCategories(imageDTO);
- // remove from "All Images"
- removeFromCacheKeys.push({ categories });
-
- if (board_id) {
- // remove from it's own board
- removeFromCacheKeys.push({ board_id });
- } else {
- // remove from "No Board"
- removeFromCacheKeys.push({ board_id: 'none' });
- }
-
- const patches: PatchCollection[] = [];
- removeFromCacheKeys.forEach((cacheKey) => {
- patches.push(
- dispatch(
- imagesApi.util.updateQueryData(
- 'listImages',
- cacheKey,
- (draft) => {
- imagesAdapter.removeOne(draft, image_name);
- draft.total = Math.max(draft.total - 1, 0);
- }
- )
+ // *remove* from $cache
+ patches.push(
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ { board_id: board_id ?? 'none', categories },
+ (draft) => {
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.removeOne(draft, image_name);
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
+ }
)
- );
- });
+ )
+ );
try {
await queryFulfilled;
@@ -222,122 +243,168 @@ export const imagesApi = api.injectEndpoints({
}
},
}),
- updateImage: build.mutation<
+ /**
+ * Change an image's `is_intermediate` property.
+ */
+ changeImageIsIntermediate: build.mutation<
ImageDTO,
- {
- imageDTO: ImageDTO;
- // For now, we will not allow image categories to change
- changes: Omit;
- }
+ { imageDTO: ImageDTO; is_intermediate: boolean }
>({
- query: ({ imageDTO, changes }) => ({
+ query: ({ imageDTO, is_intermediate }) => ({
url: `images/${imageDTO.image_name}`,
method: 'PATCH',
- body: changes,
+ body: { is_intermediate },
}),
invalidatesTags: (result, error, { imageDTO }) => [
- { type: 'Image', id: imageDTO.image_name },
+ { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' },
+ { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' },
],
async onQueryStarted(
- { imageDTO: oldImageDTO, changes: _changes },
+ { imageDTO, is_intermediate },
{ dispatch, queryFulfilled, getState }
) {
- // let's be extra-sure we do not accidentally change categories
- const changes = omit(_changes, 'image_category');
-
/**
- * Cache changes for "updateImage":
- * - *update* "getImageDTO" cache
- * - for "All Images" || "All Assets":
- * - IF it is not already in the cache
- * - THEN *add* it to "All Images" / "All Assets" and update the total
- * - ELSE *update* it
- * - IF the image has a board:
- * - THEN *update* it's own board
- * - ELSE *update* the "No Board" board
+ * Cache changes for `changeImageIsIntermediate`:
+ * - *update* getImageDTO
+ * - $cache = [board_id|no_board]/[images|assets]
+ * - IF it is being changed to an intermediate:
+ * - remove from $cache
+ * - ELSE (it is being changed to a non-intermediate):
+ * - IF it eligible for insertion into existing $cache:
+ * - *upsert* to $cache
*/
+ // Store patches so we can undo if the query fails
const patches: PatchCollection[] = [];
- const { image_name, board_id, image_category, is_intermediate } =
- oldImageDTO;
- const isChangingFromIntermediate = changes.is_intermediate === false;
- // do not add intermediates to gallery cache
- if (is_intermediate && !isChangingFromIntermediate) {
- return;
- }
-
- // determine `categories`, i.e. do we update "All Images" or "All Assets"
- const categories = IMAGE_CATEGORIES.includes(image_category)
- ? IMAGE_CATEGORIES
- : ASSETS_CATEGORIES;
-
- // update `getImageDTO` cache
+ // *update* getImageDTO
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'getImageDTO',
- image_name,
+ imageDTO.image_name,
(draft) => {
- Object.assign(draft, changes);
+ Object.assign(draft, { is_intermediate });
}
)
)
);
- // Update the "All Image" or "All Assets" board
- const queryArgsToUpdate: ListImagesArgs[] = [{ categories }];
+ // $cache = [board_id|no_board]/[images|assets]
+ const categories = getCategories(imageDTO);
- // IF the image has a board:
- if (board_id) {
- // THEN update it's own board
- queryArgsToUpdate.push({ board_id });
+ if (is_intermediate) {
+ // IF it is being changed to an intermediate:
+ // remove from $cache
+ patches.push(
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ { board_id: imageDTO.board_id ?? 'none', categories },
+ (draft) => {
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.removeOne(
+ draft,
+ imageDTO.image_name
+ );
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
+ }
+ )
+ )
+ );
} else {
- // ELSE update the "No Board" board
- queryArgsToUpdate.push({ board_id: 'none' });
- }
+ // ELSE (it is being changed to a non-intermediate):
+ const queryArgs = {
+ board_id: imageDTO.board_id ?? 'none',
+ categories,
+ };
- queryArgsToUpdate.forEach((queryArg) => {
- const { data } = imagesApi.endpoints.listImages.select(queryArg)(
+ const currentCache = imagesApi.endpoints.listImages.select(queryArgs)(
getState()
);
- const cacheAction = getCacheAction(data, oldImageDTO);
+ // IF it eligible for insertion into existing $cache
+ // "eligible" means either:
+ // - The cache is fully populated, with all images in the db cached
+ // OR
+ // - The image's `created_at` is within the range of the cached images
- if (['update', 'add'].includes(cacheAction)) {
+ const isCacheFullyPopulated =
+ currentCache.data &&
+ currentCache.data.ids.length >= currentCache.data.total;
+
+ const isInDateRange = getIsImageInDateRange(
+ currentCache.data,
+ imageDTO
+ );
+
+ if (isCacheFullyPopulated || isInDateRange) {
+ // *upsert* to $cache
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'listImages',
- queryArg,
+ queryArgs,
(draft) => {
- // One of the common changes is to make a canvas intermediate a non-intermediate,
- // i.e. save a canvas image to the gallery.
- // If that was the change, need to add the image to the cache instead of updating
- // the existing cache entry.
- if (
- changes.is_intermediate === false ||
- cacheAction === 'add'
- ) {
- // add it to the cache
- imagesAdapter.addOne(draft, {
- ...oldImageDTO,
- ...changes,
- });
- draft.total += 1;
- } else if (cacheAction === 'update') {
- // just update it
- imagesAdapter.updateOne(draft, {
- id: image_name,
- changes,
- });
- }
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.upsertOne(draft, imageDTO);
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
}
)
)
);
}
- });
+ }
+
+ try {
+ await queryFulfilled;
+ } catch {
+ patches.forEach((patchResult) => patchResult.undo());
+ }
+ },
+ }),
+ /**
+ * Change an image's `session_id` association.
+ */
+ changeImageSessionId: build.mutation<
+ ImageDTO,
+ { imageDTO: ImageDTO; session_id: string }
+ >({
+ query: ({ imageDTO, session_id }) => ({
+ url: `images/${imageDTO.image_name}`,
+ 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, getState }
+ ) {
+ /**
+ * Cache changes for `changeImageSessionId`:
+ * - *update* getImageDTO
+ */
+
+ // Store patches so we can undo if the query fails
+ const patches: PatchCollection[] = [];
+
+ // *update* getImageDTO
+ patches.push(
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'getImageDTO',
+ imageDTO.image_name,
+ (draft) => {
+ Object.assign(draft, { session_id });
+ }
+ )
+ )
+ );
try {
await queryFulfilled;
@@ -375,6 +442,15 @@ export const imagesApi = api.injectEndpoints({
{ dispatch, queryFulfilled }
) {
try {
+ /**
+ * NOTE: PESSIMISTIC UPDATE
+ * Cache changes for `uploadImage`:
+ * - IF the image is an intermediate:
+ * - BAIL OUT
+ * - *add* to `getImageDTO`
+ * - *add* to no_board/assets
+ */
+
const { data: imageDTO } = await queryFulfilled;
if (imageDTO.is_intermediate) {
@@ -382,21 +458,37 @@ export const imagesApi = api.injectEndpoints({
return;
}
- // determine `categories`, i.e. do we update "All Images" or "All Assets"
- const categories = IMAGE_CATEGORIES.includes(image_category)
- ? IMAGE_CATEGORIES
- : ASSETS_CATEGORIES;
+ // *add* to `getImageDTO`
+ dispatch(
+ imagesApi.util.upsertQueryData(
+ 'getImageDTO',
+ imageDTO.image_name,
+ imageDTO
+ )
+ );
- const queryArg = { categories };
+ // *add* to no_board/assets
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ { board_id: 'none', categories: ASSETS_CATEGORIES },
+ (draft) => {
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.addOne(draft, imageDTO);
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
+ }
+ )
+ );
dispatch(
- imagesApi.util.updateQueryData('listImages', queryArg, (draft) => {
- imagesAdapter.addOne(draft, imageDTO);
- draft.total = draft.total + 1;
- })
+ imagesApi.util.invalidateTags([
+ { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' },
+ { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' },
+ ])
);
} catch {
- // no-op
+ // query failed, no action needed
}
},
}),
@@ -412,107 +504,103 @@ export const imagesApi = api.injectEndpoints({
body: { board_id, image_name },
};
},
- invalidatesTags: (result, error, arg) => [
- { type: 'BoardImage' },
- { type: 'Board', id: arg.board_id },
+ invalidatesTags: (result, error, { board_id, imageDTO }) => [
+ { type: 'Board', id: board_id },
+ { type: 'BoardImagesTotal', id: board_id },
+ { type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none' },
+ { type: 'BoardAssetsTotal', id: board_id },
+ { type: 'BoardAssetsTotal', id: imageDTO.board_id ?? 'none' },
],
async onQueryStarted(
- { board_id, imageDTO: oldImageDTO },
+ { board_id, imageDTO },
{ dispatch, queryFulfilled, getState }
) {
/**
* Cache changes for `addImageToBoard`:
- * - *update* the `getImageDTO` cache
- * - *remove* from "No Board"
- * - IF the image has an old `board_id`:
- * - THEN *remove* from it's old `board_id`
- * - IF the image's `created_at` is within the range of the board's cached images
- * - OR the board cache has length of 0 or 1
- * - THEN *add* it to new `board_id`
+ * - *update* getImageDTO
+ * - IF it has an old board_id:
+ * - THEN *remove* from old board_id/[images|assets]
+ * - ELSE *remove* from no_board/[images|assets]
+ * - $cache = board_id/[images|assets]
+ * - IF it eligible for insertion into existing $cache:
+ * - THEN *add* to $cache
*/
- const { image_name, board_id: old_board_id } = oldImageDTO;
-
- // Figure out the `listImages` caches that we need to update
- const removeFromQueryArgs: ListImagesArgs[] = [];
-
- // remove from "No Board"
- removeFromQueryArgs.push({ board_id: 'none' });
-
- // remove from old board
- if (old_board_id) {
- removeFromQueryArgs.push({ board_id: old_board_id });
- }
-
- // Store all patch results in case we need to roll back
const patches: PatchCollection[] = [];
+ const categories = getCategories(imageDTO);
- // Updated imageDTO with new board_id
- const newImageDTO = { ...oldImageDTO, board_id };
-
- // Update getImageDTO cache
+ // *update* getImageDTO
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'getImageDTO',
- image_name,
+ imageDTO.image_name,
(draft) => {
- Object.assign(draft, newImageDTO);
+ Object.assign(draft, { board_id });
}
)
)
);
- // Do the "Remove from" cache updates
- removeFromQueryArgs.forEach((queryArgs) => {
+ // *remove* from [no_board|board_id]/[images|assets]
+ patches.push(
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ {
+ board_id: imageDTO.board_id ?? 'none',
+ categories,
+ },
+ (draft) => {
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.removeOne(
+ draft,
+ imageDTO.image_name
+ );
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
+ }
+ )
+ )
+ );
+
+ // $cache = board_id/[images|assets]
+ const queryArgs = { board_id: board_id ?? 'none', categories };
+ const currentCache = imagesApi.endpoints.listImages.select(queryArgs)(
+ getState()
+ );
+
+ // IF it eligible for insertion into existing $cache
+ // "eligible" means either:
+ // - The cache is fully populated, with all images in the db cached
+ // OR
+ // - The image's `created_at` is within the range of the cached images
+
+ const isCacheFullyPopulated =
+ currentCache.data &&
+ currentCache.data.ids.length >= currentCache.data.total;
+
+ const isInDateRange = getIsImageInDateRange(
+ currentCache.data,
+ imageDTO
+ );
+
+ if (isCacheFullyPopulated || isInDateRange) {
+ // THEN *add* to $cache
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'listImages',
queryArgs,
(draft) => {
- // sanity check
- if (draft.ids.includes(image_name)) {
- imagesAdapter.removeOne(draft, image_name);
- draft.total = Math.max(draft.total - 1, 0);
- }
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.addOne(draft, imageDTO);
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
}
)
)
);
- });
-
- // We only need to add to the cache if the board is not a system board
- if (!SYSTEM_BOARDS.includes(board_id)) {
- const queryArgs = { board_id };
- const { data } = imagesApi.endpoints.listImages.select(queryArgs)(
- getState()
- );
-
- const cacheAction = getCacheAction(data, oldImageDTO);
-
- if (['add', 'update'].includes(cacheAction)) {
- // Do the "Add to" cache updates
- patches.push(
- dispatch(
- imagesApi.util.updateQueryData(
- 'listImages',
- queryArgs,
- (draft) => {
- if (cacheAction === 'add') {
- imagesAdapter.addOne(draft, newImageDTO);
- draft.total += 1;
- } else {
- imagesAdapter.updateOne(draft, {
- id: image_name,
- changes: { board_id },
- });
- }
- }
- )
- )
- );
- }
}
try {
@@ -531,87 +619,97 @@ export const imagesApi = api.injectEndpoints({
body: { board_id, image_name },
};
},
- invalidatesTags: (result, error, arg) => [
- { type: 'BoardImage' },
- { type: 'Board', id: arg.imageDTO.board_id },
+ invalidatesTags: (result, error, { imageDTO }) => [
+ { type: 'Board', id: imageDTO.board_id },
+ { type: 'BoardImagesTotal', id: imageDTO.board_id },
+ { type: 'BoardImagesTotal', id: 'none' },
+ { type: 'BoardAssetsTotal', id: imageDTO.board_id },
+ { type: 'BoardAssetsTotal', id: 'none' },
],
async onQueryStarted(
{ imageDTO },
{ dispatch, queryFulfilled, getState }
) {
/**
- * Cache changes for `removeImageFromBoard`:
- * - *update* `getImageDTO`
- * - IF the image's `created_at` is within the range of the board's cached images
- * - THEN *add* to "No Board"
- * - *remove* from `old_board_id`
+ * Cache changes for removeImageFromBoard:
+ * - *update* getImageDTO
+ * - *remove* from board_id/[images|assets]
+ * - $cache = no_board/[images|assets]
+ * - IF it eligible for insertion into existing $cache:
+ * - THEN *upsert* to $cache
*/
- const { image_name, board_id: old_board_id } = imageDTO;
-
+ const categories = getCategories(imageDTO);
const patches: PatchCollection[] = [];
- // Updated imageDTO with new board_id
- const newImageDTO = { ...imageDTO, board_id: undefined };
-
- // Update getImageDTO cache
+ // *update* getImageDTO
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'getImageDTO',
- image_name,
+ imageDTO.image_name,
(draft) => {
- Object.assign(draft, newImageDTO);
+ Object.assign(draft, { board_id: undefined });
}
)
)
);
- // Remove from old board
- if (old_board_id) {
- const oldBoardQueryArgs = { board_id: old_board_id };
- patches.push(
- dispatch(
- imagesApi.util.updateQueryData(
- 'listImages',
- oldBoardQueryArgs,
- (draft) => {
- // sanity check
- if (draft.ids.includes(image_name)) {
- imagesAdapter.removeOne(draft, image_name);
- draft.total = Math.max(draft.total - 1, 0);
- }
- }
- )
+ // *remove* from board_id/[images|assets]
+ patches.push(
+ dispatch(
+ imagesApi.util.updateQueryData(
+ 'listImages',
+ {
+ board_id: imageDTO.board_id ?? 'none',
+ categories,
+ },
+ (draft) => {
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.removeOne(
+ draft,
+ imageDTO.image_name
+ );
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
+ }
)
- );
- }
+ )
+ );
- // Add to "No Board"
- const noBoardQueryArgs = { board_id: 'none' };
- const { data } = imagesApi.endpoints.listImages.select(
- noBoardQueryArgs
- )(getState());
+ // $cache = no_board/[images|assets]
+ const queryArgs = { board_id: 'none', categories };
+ const currentCache = imagesApi.endpoints.listImages.select(queryArgs)(
+ getState()
+ );
- // Check if we need to make any cache changes
- const cacheAction = getCacheAction(data, imageDTO);
+ // IF it eligible for insertion into existing $cache
+ // "eligible" means either:
+ // - The cache is fully populated, with all images in the db cached
+ // OR
+ // - The image's `created_at` is within the range of the cached images
- if (['add', 'update'].includes(cacheAction)) {
+ const isCacheFullyPopulated =
+ currentCache.data &&
+ currentCache.data.ids.length >= currentCache.data.total;
+
+ const isInDateRange = getIsImageInDateRange(
+ currentCache.data,
+ imageDTO
+ );
+
+ if (isCacheFullyPopulated || isInDateRange) {
+ // THEN *upsert* to $cache
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'listImages',
- noBoardQueryArgs,
+ queryArgs,
(draft) => {
- if (cacheAction === 'add') {
- imagesAdapter.addOne(draft, imageDTO);
- draft.total += 1;
- } else {
- imagesAdapter.updateOne(draft, {
- id: image_name,
- changes: { board_id: undefined },
- });
- }
+ const oldTotal = draft.total;
+ const newState = imagesAdapter.upsertOne(draft, imageDTO);
+ const delta = newState.total - oldTotal;
+ draft.total = draft.total + delta;
}
)
)
@@ -635,7 +733,9 @@ export const {
useGetImageDTOQuery,
useGetImageMetadataQuery,
useDeleteImageMutation,
- useUpdateImageMutation,
+ // useUpdateImageMutation,
+ useGetBoardImagesTotalQuery,
+ useGetBoardAssetsTotalQuery,
useUploadImageMutation,
useAddImageToBoardMutation,
useRemoveImageFromBoardMutation,
diff --git a/invokeai/frontend/web/src/services/api/endpoints/util.ts b/invokeai/frontend/web/src/services/api/endpoints/util.ts
index d613711dc2..0172c1af44 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/util.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/util.ts
@@ -25,27 +25,27 @@ export const getIsImageInDateRange = (
return false;
};
-/**
- * Determines the action we should take when an image may need to be added or updated in a cache.
- */
-export const getCacheAction = (
- data: ImageCache | undefined,
- imageDTO: ImageDTO
-): 'add' | 'update' | 'none' => {
- const isInDateRange = getIsImageInDateRange(data, imageDTO);
- const isCacheFullyPopulated = data && data.total === data.ids.length;
- const shouldUpdateCache =
- Boolean(isInDateRange) || Boolean(isCacheFullyPopulated);
+// /**
+// * Determines the action we should take when an image may need to be added or updated in a cache.
+// */
+// export const getCacheAction = (
+// data: ImageCache | undefined,
+// imageDTO: ImageDTO
+// ): 'add' | 'update' | 'none' => {
+// const isInDateRange = getIsImageInDateRange(data, imageDTO);
+// const isCacheFullyPopulated = data && data.total === data.ids.length;
+// const shouldUpdateCache =
+// Boolean(isInDateRange) || Boolean(isCacheFullyPopulated);
- const isImageInCache = data && data.ids.includes(imageDTO.image_name);
+// const isImageInCache = data && data.ids.includes(imageDTO.image_name);
- if (shouldUpdateCache && isImageInCache) {
- return 'update';
- }
+// if (shouldUpdateCache && isImageInCache) {
+// return 'update';
+// }
- if (shouldUpdateCache && !isImageInCache) {
- return 'add';
- }
+// if (shouldUpdateCache && !isImageInCache) {
+// return 'add';
+// }
- return 'none';
-};
+// return 'none';
+// };
diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts
index cbe0ec1808..d6b010e3ab 100644
--- a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts
+++ b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts
@@ -4,19 +4,8 @@ import { useListAllBoardsQuery } from '../endpoints/boards';
export const useBoardName = (board_id: BoardId | null | undefined) => {
const { boardName } = useListAllBoardsQuery(undefined, {
selectFromResult: ({ data }) => {
- let boardName = '';
- if (board_id === 'images') {
- boardName = 'Images';
- } else if (board_id === 'assets') {
- boardName = 'Assets';
- } else if (board_id === 'no_board') {
- boardName = 'No Board';
- } else if (board_id === 'batch') {
- boardName = 'Batch';
- } else {
- const selectedBoard = data?.find((b) => b.board_id === board_id);
- boardName = selectedBoard?.board_name || 'Unknown Board';
- }
+ const selectedBoard = data?.find((b) => b.board_id === board_id);
+ const boardName = selectedBoard?.board_name || 'Uncategorized';
return { boardName };
},
diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts
index 8deccd8947..1a9e69ff2d 100644
--- a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts
+++ b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts
@@ -1,53 +1,21 @@
-import { skipToken } from '@reduxjs/toolkit/dist/query';
-import {
- ASSETS_CATEGORIES,
- BoardId,
- IMAGE_CATEGORIES,
- INITIAL_IMAGE_LIMIT,
-} from 'features/gallery/store/gallerySlice';
+import { useAppSelector } from 'app/store/storeHooks';
+import { BoardId } from 'features/gallery/store/gallerySlice';
import { useMemo } from 'react';
-import { ListImagesArgs, useListImagesQuery } from '../endpoints/images';
+import {
+ useGetBoardAssetsTotalQuery,
+ useGetBoardImagesTotalQuery,
+} from '../endpoints/images';
-const baseQueryArg: ListImagesArgs = {
- offset: 0,
- limit: INITIAL_IMAGE_LIMIT,
- is_intermediate: false,
-};
-
-const imagesQueryArg: ListImagesArgs = {
- categories: IMAGE_CATEGORIES,
- ...baseQueryArg,
-};
-
-const assetsQueryArg: ListImagesArgs = {
- categories: ASSETS_CATEGORIES,
- ...baseQueryArg,
-};
-
-const noBoardQueryArg: ListImagesArgs = {
- board_id: 'none',
- ...baseQueryArg,
-};
-
-export const useBoardTotal = (board_id: BoardId | null | undefined) => {
- const queryArg = useMemo(() => {
- if (!board_id) {
- return;
- }
- if (board_id === 'images') {
- return imagesQueryArg;
- } else if (board_id === 'assets') {
- return assetsQueryArg;
- } else if (board_id === 'no_board') {
- return noBoardQueryArg;
- } else {
- return { board_id, ...baseQueryArg };
- }
- }, [board_id]);
-
- const { total } = useListImagesQuery(queryArg ?? skipToken, {
- selectFromResult: ({ currentData }) => ({ total: currentData?.total }),
- });
-
- return total;
+export const useBoardTotal = (board_id: BoardId) => {
+ const galleryView = useAppSelector((state) => state.gallery.galleryView);
+
+ const { data: totalImages } = useGetBoardImagesTotalQuery(board_id);
+ const { data: totalAssets } = useGetBoardAssetsTotalQuery(board_id);
+
+ const currentViewTotal = useMemo(
+ () => (galleryView === 'images' ? totalImages : totalAssets),
+ [galleryView, totalAssets, totalImages]
+ );
+
+ return { totalImages, totalAssets, currentViewTotal };
};
diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts
index e10c96543e..0a0391898c 100644
--- a/invokeai/frontend/web/src/services/api/index.ts
+++ b/invokeai/frontend/web/src/services/api/index.ts
@@ -10,6 +10,8 @@ import { $authToken, $baseUrl } from 'services/api/client';
export const tagTypes = [
'Board',
+ 'BoardImagesTotal',
+ 'BoardAssetsTotal',
'Image',
'ImageNameList',
'ImageList',