mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip boards via rtk-query
This commit is contained in:
parent
661a94b3de
commit
cfda128e06
@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
|||||||
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { imageAddedToBoard } from '../../services/thunks/board';
|
import { imageAddedToBoard } from '../../services/thunks/board';
|
||||||
|
import { useAddImageToBoardMutation } from 'services/apiSlice';
|
||||||
|
|
||||||
export type ImageUsage = {
|
export type ImageUsage = {
|
||||||
isInitialImage: boolean;
|
isInitialImage: boolean;
|
||||||
@ -43,6 +44,8 @@ export const AddImageToBoardContextProvider = (props: Props) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const [addImageToBoard, result] = useAddImageToBoardMutation();
|
||||||
|
|
||||||
// Clean up after deleting or dismissing the modal
|
// Clean up after deleting or dismissing the modal
|
||||||
const closeAndClearImageToDelete = useCallback(() => {
|
const closeAndClearImageToDelete = useCallback(() => {
|
||||||
setImageToMove(undefined);
|
setImageToMove(undefined);
|
||||||
@ -63,18 +66,14 @@ export const AddImageToBoardContextProvider = (props: Props) => {
|
|||||||
const handleAddToBoard = useCallback(
|
const handleAddToBoard = useCallback(
|
||||||
(boardId: string) => {
|
(boardId: string) => {
|
||||||
if (imageToMove) {
|
if (imageToMove) {
|
||||||
dispatch(
|
addImageToBoard({
|
||||||
imageAddedToBoard({
|
board_id: boardId,
|
||||||
requestBody: {
|
image_name: imageToMove.image_name,
|
||||||
board_id: boardId,
|
});
|
||||||
image_name: imageToMove.image_name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
closeAndClearImageToDelete();
|
closeAndClearImageToDelete();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[closeAndClearImageToDelete, dispatch, imageToMove]
|
[addImageToBoard, closeAndClearImageToDelete, imageToMove]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -73,6 +73,10 @@ import { addImageCategoriesChangedListener } from './listeners/imageCategoriesCh
|
|||||||
import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
|
import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
|
||||||
import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
|
import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
|
||||||
import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect';
|
import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect';
|
||||||
|
import {
|
||||||
|
addImageAddedToBoardFulfilledListener,
|
||||||
|
addImageAddedToBoardRejectedListener,
|
||||||
|
} from './listeners/imageAddedToBoard';
|
||||||
|
|
||||||
export const listenerMiddleware = createListenerMiddleware();
|
export const listenerMiddleware = createListenerMiddleware();
|
||||||
|
|
||||||
@ -183,3 +187,7 @@ addControlNetAutoProcessListener();
|
|||||||
|
|
||||||
// Update image URLs on connect
|
// Update image URLs on connect
|
||||||
addUpdateImageUrlsOnConnectListener();
|
addUpdateImageUrlsOnConnectListener();
|
||||||
|
|
||||||
|
// Boards
|
||||||
|
addImageAddedToBoardFulfilledListener();
|
||||||
|
addImageAddedToBoardRejectedListener();
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
import { imageMetadataReceived } from 'services/thunks/image';
|
||||||
|
import { api } from 'services/apiSlice';
|
||||||
|
|
||||||
|
const moduleLog = log.child({ namespace: 'boards' });
|
||||||
|
|
||||||
|
export const addImageAddedToBoardFulfilledListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
matcher: api.endpoints.addImageToBoard.matchFulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_name } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_name } },
|
||||||
|
'Image added to board'
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
imageMetadataReceived({
|
||||||
|
imageName: image_name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addImageAddedToBoardRejectedListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
matcher: api.endpoints.addImageToBoard.matchRejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_name } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_name } },
|
||||||
|
'Problem adding image to board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -13,6 +13,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
|||||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { api } from 'services/apiSlice';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
|
const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
|
|||||||
export const addRequestedImageDeletionListener = () => {
|
export const addRequestedImageDeletionListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: requestedImageDeletion,
|
actionCreator: requestedImageDeletion,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState, condition }) => {
|
||||||
const { image, imageUsage } = action.payload;
|
const { image, imageUsage } = action.payload;
|
||||||
|
|
||||||
const { image_name } = image;
|
const { image_name } = image;
|
||||||
@ -30,7 +31,7 @@ export const addRequestedImageDeletionListener = () => {
|
|||||||
const state = getState();
|
const state = getState();
|
||||||
const selectedImage = state.gallery.selectedImage;
|
const selectedImage = state.gallery.selectedImage;
|
||||||
|
|
||||||
if (selectedImage && selectedImage.image_name === image_name) {
|
if (selectedImage && selectedImage === image_name) {
|
||||||
const ids = selectImagesIds(state);
|
const ids = selectImagesIds(state);
|
||||||
const entities = selectImagesEntities(state);
|
const entities = selectImagesEntities(state);
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ export const addRequestedImageDeletionListener = () => {
|
|||||||
const newSelectedImage = entities[newSelectedImageId];
|
const newSelectedImage = entities[newSelectedImageId];
|
||||||
|
|
||||||
if (newSelectedImageId) {
|
if (newSelectedImageId) {
|
||||||
dispatch(imageSelected(newSelectedImage));
|
dispatch(imageSelected(newSelectedImageId));
|
||||||
} else {
|
} else {
|
||||||
dispatch(imageSelected());
|
dispatch(imageSelected());
|
||||||
}
|
}
|
||||||
@ -79,7 +80,19 @@ export const addRequestedImageDeletionListener = () => {
|
|||||||
dispatch(imageRemoved(image_name));
|
dispatch(imageRemoved(image_name));
|
||||||
|
|
||||||
// Delete from server
|
// Delete from server
|
||||||
dispatch(imageDeleted({ imageName: image_name }));
|
const { requestId } = dispatch(imageDeleted({ imageName: image_name }));
|
||||||
|
|
||||||
|
// Wait for successful deletion, then trigger boards to re-fetch
|
||||||
|
const wasImageDeleted = await condition(
|
||||||
|
(action) => action.meta.requestId === requestId,
|
||||||
|
30000
|
||||||
|
);
|
||||||
|
|
||||||
|
if (wasImageDeleted) {
|
||||||
|
dispatch(
|
||||||
|
api.util.invalidateTags([{ type: 'Board', id: image.board_id }])
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist';
|
|||||||
import { serialize } from './enhancers/reduxRemember/serialize';
|
import { serialize } from './enhancers/reduxRemember/serialize';
|
||||||
import { unserialize } from './enhancers/reduxRemember/unserialize';
|
import { unserialize } from './enhancers/reduxRemember/unserialize';
|
||||||
import { LOCALSTORAGE_PREFIX } from './constants';
|
import { LOCALSTORAGE_PREFIX } from './constants';
|
||||||
|
import { api } from 'services/apiSlice';
|
||||||
|
|
||||||
const allReducers = {
|
const allReducers = {
|
||||||
canvas: canvasReducer,
|
canvas: canvasReducer,
|
||||||
@ -49,6 +50,7 @@ const allReducers = {
|
|||||||
images: imagesReducer,
|
images: imagesReducer,
|
||||||
controlNet: controlNetReducer,
|
controlNet: controlNetReducer,
|
||||||
boards: boardsReducer,
|
boards: boardsReducer,
|
||||||
|
[api.reducerPath]: api.reducer,
|
||||||
// session: sessionReducer,
|
// session: sessionReducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,6 +89,7 @@ export const store = configureStore({
|
|||||||
immutableCheck: false,
|
immutableCheck: false,
|
||||||
serializableCheck: false,
|
serializableCheck: false,
|
||||||
})
|
})
|
||||||
|
.concat(api.middleware)
|
||||||
.concat(dynamicMiddlewares)
|
.concat(dynamicMiddlewares)
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
devTools: {
|
devTools: {
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { Flex, Icon, Text } from '@chakra-ui/react';
|
import { Flex, Icon, Spinner, Text } from '@chakra-ui/react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { useAppDispatch } from '../../../../app/store/storeHooks';
|
import { useCreateBoardMutation } from 'services/apiSlice';
|
||||||
import { boardCreated } from '../../../../services/thunks/board';
|
|
||||||
|
const DEFAULT_BOARD_NAME = 'My Board';
|
||||||
|
|
||||||
const AddBoardButton = () => {
|
const AddBoardButton = () => {
|
||||||
const dispatch = useAppDispatch();
|
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
||||||
|
|
||||||
const handleCreateBoard = useCallback(() => {
|
const handleCreateBoard = useCallback(() => {
|
||||||
dispatch(boardCreated({ requestBody: 'My Board' }));
|
createBoard(DEFAULT_BOARD_NAME);
|
||||||
}, [dispatch]);
|
}, [createBoard]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
onClick={handleCreateBoard}
|
onClick={isLoading ? undefined : handleCreateBoard}
|
||||||
sx={{
|
sx={{
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@ -36,7 +37,11 @@ const AddBoardButton = () => {
|
|||||||
aspectRatio: '1/1',
|
aspectRatio: '1/1',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon boxSize={8} color="base.700" as={FaPlus} />
|
{isLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<Icon boxSize={8} color="base.700" as={FaPlus} />
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>New Board</Text>
|
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>New Board</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -25,6 +25,7 @@ import { searchBoardsSelector } from '../../store/boardSelectors';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import IAICollapse from '../../../../common/components/IAICollapse';
|
import IAICollapse from '../../../../common/components/IAICollapse';
|
||||||
import { CloseIcon } from '@chakra-ui/icons';
|
import { CloseIcon } from '@chakra-ui/icons';
|
||||||
|
import { useListBoardsQuery } from 'services/apiSlice';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[selectBoardsAll, boardsSelector],
|
[selectBoardsAll, boardsSelector],
|
||||||
@ -40,9 +41,17 @@ const selector = createSelector(
|
|||||||
const BoardsList = () => {
|
const BoardsList = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { selectedBoard, searchText } = useAppSelector(selector);
|
const { selectedBoard, searchText } = useAppSelector(selector);
|
||||||
const filteredBoards = useSelector(searchBoardsSelector);
|
// const filteredBoards = useSelector(searchBoardsSelector);
|
||||||
const { isOpen, onToggle } = useDisclosure();
|
const { isOpen, onToggle } = useDisclosure();
|
||||||
|
|
||||||
|
const { data } = useListBoardsQuery({ offset: 0, limit: 8 });
|
||||||
|
|
||||||
|
const filteredBoards = searchText
|
||||||
|
? data?.items.filter((board) =>
|
||||||
|
board.board_name.toLowerCase().includes(searchText.toLowerCase())
|
||||||
|
)
|
||||||
|
: data.items;
|
||||||
|
|
||||||
const [searchMode, setSearchMode] = useState(false);
|
const [searchMode, setSearchMode] = useState(false);
|
||||||
|
|
||||||
const handleBoardSearch = (searchTerm: string) => {
|
const handleBoardSearch = (searchTerm: string) => {
|
||||||
@ -100,13 +109,14 @@ const BoardsList = () => {
|
|||||||
<AllImagesBoard isSelected={!selectedBoard} />
|
<AllImagesBoard isSelected={!selectedBoard} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{filteredBoards.map((board) => (
|
{filteredBoards &&
|
||||||
<HoverableBoard
|
filteredBoards.map((board) => (
|
||||||
key={board.board_id}
|
<HoverableBoard
|
||||||
board={board}
|
key={board.board_id}
|
||||||
isSelected={selectedBoard?.board_id === board.board_id}
|
board={board}
|
||||||
/>
|
isSelected={selectedBoard?.board_id === board.board_id}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</>
|
</>
|
||||||
|
@ -26,6 +26,10 @@ import IAIDndImage from '../../../../common/components/IAIDndImage';
|
|||||||
import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from '../../../../app/store/store';
|
import { RootState } from '../../../../app/store/store';
|
||||||
|
import {
|
||||||
|
useDeleteBoardMutation,
|
||||||
|
useUpdateBoardMutation,
|
||||||
|
} from 'services/apiSlice';
|
||||||
|
|
||||||
const coverImageSelector = (imageName: string | undefined) =>
|
const coverImageSelector = (imageName: string | undefined) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
@ -59,19 +63,20 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
dispatch(boardIdSelected(board_id));
|
dispatch(boardIdSelected(board_id));
|
||||||
}, [board_id, dispatch]);
|
}, [board_id, dispatch]);
|
||||||
|
|
||||||
const handleDeleteBoard = useCallback(() => {
|
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
|
||||||
dispatch(boardDeleted(board_id));
|
useUpdateBoardMutation();
|
||||||
}, [board_id, dispatch]);
|
|
||||||
|
const [deleteBoard, { isLoading: isDeleteBoardLoading }] =
|
||||||
|
useDeleteBoardMutation();
|
||||||
|
|
||||||
const handleUpdateBoardName = (newBoardName: string) => {
|
const handleUpdateBoardName = (newBoardName: string) => {
|
||||||
dispatch(
|
updateBoard({ board_id, changes: { board_name: newBoardName } });
|
||||||
boardUpdated({
|
|
||||||
boardId: board_id,
|
|
||||||
requestBody: { board_name: newBoardName },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteBoard = useCallback(() => {
|
||||||
|
deleteBoard(board_id);
|
||||||
|
}, [board_id, deleteBoard]);
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(droppedImage: ImageDTO) => {
|
(droppedImage: ImageDTO) => {
|
||||||
if (droppedImage.board_id === board_id) {
|
if (droppedImage.board_id === board_id) {
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Select,
|
Select,
|
||||||
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
@ -19,9 +20,11 @@ import { useSelector } from 'react-redux';
|
|||||||
import { selectBoardsAll } from '../../store/boardSlice';
|
import { selectBoardsAll } from '../../store/boardSlice';
|
||||||
import IAISelect from '../../../../common/components/IAISelect';
|
import IAISelect from '../../../../common/components/IAISelect';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
|
import { useListAllBoardsQuery } from 'services/apiSlice';
|
||||||
|
|
||||||
const UpdateImageBoardModal = () => {
|
const UpdateImageBoardModal = () => {
|
||||||
const boards = useSelector(selectBoardsAll);
|
// const boards = useSelector(selectBoardsAll);
|
||||||
|
const { data: boards, isFetching } = useListAllBoardsQuery();
|
||||||
const { isOpen, onClose, handleAddToBoard, image } = useContext(
|
const { isOpen, onClose, handleAddToBoard, image } = useContext(
|
||||||
AddImageToBoardContext
|
AddImageToBoardContext
|
||||||
);
|
);
|
||||||
@ -29,9 +32,9 @@ const UpdateImageBoardModal = () => {
|
|||||||
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const currentBoard = boards.filter(
|
const currentBoard = boards?.find(
|
||||||
(board) => board.board_id === image?.board_id
|
(board) => board.board_id === image?.board_id
|
||||||
)[0];
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
@ -55,15 +58,19 @@ const UpdateImageBoardModal = () => {
|
|||||||
<strong>{currentBoard.board_name}</strong> to
|
<strong>{currentBoard.board_name}</strong> to
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<IAIMantineSelect
|
{isFetching ? (
|
||||||
placeholder="Select Board"
|
<Spinner />
|
||||||
onChange={(v) => setSelectedBoard(v)}
|
) : (
|
||||||
value={selectedBoard}
|
<IAIMantineSelect
|
||||||
data={boards.map((board) => ({
|
placeholder="Select Board"
|
||||||
label: board.board_name,
|
onChange={(v) => setSelectedBoard(v)}
|
||||||
value: board.board_id,
|
value={selectedBoard}
|
||||||
}))}
|
data={(boards ?? []).map((board) => ({
|
||||||
/>
|
label: board.board_name,
|
||||||
|
value: board.board_id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
</AlertDialogBody>
|
</AlertDialogBody>
|
||||||
@ -73,7 +80,9 @@ const UpdateImageBoardModal = () => {
|
|||||||
isDisabled={!selectedBoard}
|
isDisabled={!selectedBoard}
|
||||||
colorScheme="accent"
|
colorScheme="accent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedBoard) handleAddToBoard(selectedBoard);
|
if (selectedBoard) {
|
||||||
|
handleAddToBoard(selectedBoard);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
ml={3}
|
ml={3}
|
||||||
>
|
>
|
||||||
|
@ -51,9 +51,12 @@ import { useAppToaster } from 'app/components/Toaster';
|
|||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
import { DeleteImageButton } from './DeleteImageModal';
|
import { DeleteImageButton } from './DeleteImageModal';
|
||||||
|
import { selectImagesById } from '../store/imagesSlice';
|
||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
|
(state: RootState) => state,
|
||||||
systemSelector,
|
systemSelector,
|
||||||
gallerySelector,
|
gallerySelector,
|
||||||
postprocessingSelector,
|
postprocessingSelector,
|
||||||
@ -61,7 +64,7 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
lightboxSelector,
|
lightboxSelector,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
],
|
],
|
||||||
(system, gallery, postprocessing, ui, lightbox, activeTabName) => {
|
(state, system, gallery, postprocessing, ui, lightbox, activeTabName) => {
|
||||||
const {
|
const {
|
||||||
isProcessing,
|
isProcessing,
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -81,6 +84,8 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
} = ui;
|
} = ui;
|
||||||
|
|
||||||
|
const imageDTO = selectImagesById(state, gallery.selectedImage ?? '');
|
||||||
|
|
||||||
const { selectedImage } = gallery;
|
const { selectedImage } = gallery;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -97,10 +102,10 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
activeTabName,
|
activeTabName,
|
||||||
isLightboxOpen,
|
isLightboxOpen,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
image: selectedImage,
|
image: imageDTO,
|
||||||
seed: selectedImage?.metadata?.seed,
|
seed: imageDTO?.metadata?.seed,
|
||||||
prompt: selectedImage?.metadata?.positive_conditioning,
|
prompt: imageDTO?.metadata?.positive_conditioning,
|
||||||
negativePrompt: selectedImage?.metadata?.negative_conditioning,
|
negativePrompt: imageDTO?.metadata?.negative_conditioning,
|
||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,8 @@ import { imageSelected } from '../store/gallerySlice';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
import { selectImagesById } from '../store/imagesSlice';
|
||||||
|
|
||||||
export const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
[uiSelector, gallerySelector, systemSelector],
|
[uiSelector, gallerySelector, systemSelector],
|
||||||
@ -29,7 +31,7 @@ export const imagesSelector = createSelector(
|
|||||||
return {
|
return {
|
||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
image: selectedImage,
|
selectedImage,
|
||||||
progressImage,
|
progressImage,
|
||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
shouldAntialiasProgressImage,
|
shouldAntialiasProgressImage,
|
||||||
@ -45,11 +47,16 @@ export const imagesSelector = createSelector(
|
|||||||
const CurrentImagePreview = () => {
|
const CurrentImagePreview = () => {
|
||||||
const {
|
const {
|
||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
image,
|
selectedImage,
|
||||||
progressImage,
|
progressImage,
|
||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
shouldAntialiasProgressImage,
|
shouldAntialiasProgressImage,
|
||||||
} = useAppSelector(imagesSelector);
|
} = useAppSelector(imagesSelector);
|
||||||
|
|
||||||
|
const image = useAppSelector((state: RootState) =>
|
||||||
|
selectImagesById(state, selectedImage ?? '')
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
@ -57,7 +64,7 @@ const CurrentImagePreview = () => {
|
|||||||
if (droppedImage.image_name === image?.image_name) {
|
if (droppedImage.image_name === image?.image_name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(imageSelected(droppedImage));
|
dispatch(imageSelected(droppedImage.image_name));
|
||||||
},
|
},
|
||||||
[dispatch, image?.image_name]
|
[dispatch, image?.image_name]
|
||||||
);
|
);
|
||||||
|
@ -72,17 +72,10 @@ interface HoverableImageProps {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const memoEqualityCheck = (
|
|
||||||
prev: HoverableImageProps,
|
|
||||||
next: HoverableImageProps
|
|
||||||
) =>
|
|
||||||
prev.image.image_name === next.image.image_name &&
|
|
||||||
prev.isSelected === next.isSelected;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gallery image component with delete/use all/use seed buttons on hover.
|
* Gallery image component with delete/use all/use seed buttons on hover.
|
||||||
*/
|
*/
|
||||||
const HoverableImage = memo((props: HoverableImageProps) => {
|
const HoverableImage = (props: HoverableImageProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
activeTabName,
|
activeTabName,
|
||||||
@ -121,7 +114,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
const handleMouseOut = () => setIsHovered(false);
|
const handleMouseOut = () => setIsHovered(false);
|
||||||
|
|
||||||
const handleSelectImage = useCallback(() => {
|
const handleSelectImage = useCallback(() => {
|
||||||
dispatch(imageSelected(image));
|
dispatch(imageSelected(image.image_name));
|
||||||
}, [image, dispatch]);
|
}, [image, dispatch]);
|
||||||
|
|
||||||
// Recall parameters handlers
|
// Recall parameters handlers
|
||||||
@ -260,7 +253,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||||
Add to Board
|
{image.board_id ? 'Change Board' : 'Add to Board'}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: 'error.300' }}
|
sx={{ color: 'error.300' }}
|
||||||
@ -357,8 +350,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}, memoEqualityCheck);
|
};
|
||||||
|
|
||||||
HoverableImage.displayName = 'HoverableImage';
|
export default memo(HoverableImage);
|
||||||
|
|
||||||
export default HoverableImage;
|
|
||||||
|
@ -201,12 +201,6 @@ const ImageGalleryContent = () => {
|
|||||||
dispatch(setGalleryView('boards'));
|
dispatch(setGalleryView('boards'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const [newBoardName, setNewBoardName] = useState('');
|
|
||||||
|
|
||||||
const handleCreateNewBoard = () => {
|
|
||||||
dispatch(boardCreated({ requestBody: newBoardName }));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -323,9 +317,7 @@ const ImageGalleryContent = () => {
|
|||||||
<HoverableImage
|
<HoverableImage
|
||||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||||
image={item}
|
image={item}
|
||||||
isSelected={
|
isSelected={selectedImage === item?.image_name}
|
||||||
selectedImage?.image_name === item?.image_name
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
@ -344,9 +336,7 @@ const ImageGalleryContent = () => {
|
|||||||
<HoverableImage
|
<HoverableImage
|
||||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||||
image={item}
|
image={item}
|
||||||
isSelected={
|
isSelected={selectedImage === item?.image_name}
|
||||||
selectedImage?.image_name === item?.image_name
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -93,19 +93,11 @@ type ImageMetadataViewerProps = {
|
|||||||
image: ImageDTO;
|
image: ImageDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: I don't know if this is needed.
|
|
||||||
const memoEqualityCheck = (
|
|
||||||
prev: ImageMetadataViewerProps,
|
|
||||||
next: ImageMetadataViewerProps
|
|
||||||
) => prev.image.image_name === next.image.image_name;
|
|
||||||
|
|
||||||
// TODO: Show more interesting information in this component.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image metadata viewer overlays currently selected image and provides
|
* Image metadata viewer overlays currently selected image and provides
|
||||||
* access to any of its metadata for use in processing.
|
* access to any of its metadata for use in processing.
|
||||||
*/
|
*/
|
||||||
const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
recallBothPrompts,
|
recallBothPrompts,
|
||||||
@ -333,8 +325,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}, memoEqualityCheck);
|
};
|
||||||
|
|
||||||
ImageMetadataViewer.displayName = 'ImageMetadataViewer';
|
export default memo(ImageMetadataViewer);
|
||||||
|
|
||||||
export default ImageMetadataViewer;
|
|
||||||
|
@ -42,7 +42,7 @@ export const nextPrevImageButtonsSelector = createSelector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentImageIndex = filteredImageIds.findIndex(
|
const currentImageIndex = filteredImageIds.findIndex(
|
||||||
(i) => i === selectedImage.image_name
|
(i) => i === selectedImage
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextImageIndex = clamp(
|
const nextImageIndex = clamp(
|
||||||
@ -71,6 +71,8 @@ export const nextPrevImageButtonsSelector = createSelector(
|
|||||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||||
nextImage,
|
nextImage,
|
||||||
prevImage,
|
prevImage,
|
||||||
|
nextImageId,
|
||||||
|
prevImageId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -84,7 +86,7 @@ const NextPrevImageButtons = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { isOnFirstImage, isOnLastImage, nextImage, prevImage } =
|
const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } =
|
||||||
useAppSelector(nextPrevImageButtonsSelector);
|
useAppSelector(nextPrevImageButtonsSelector);
|
||||||
|
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||||
@ -99,19 +101,19 @@ const NextPrevImageButtons = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePrevImage = useCallback(() => {
|
const handlePrevImage = useCallback(() => {
|
||||||
dispatch(imageSelected(prevImage));
|
dispatch(imageSelected(prevImageId));
|
||||||
}, [dispatch, prevImage]);
|
}, [dispatch, prevImageId]);
|
||||||
|
|
||||||
const handleNextImage = useCallback(() => {
|
const handleNextImage = useCallback(() => {
|
||||||
dispatch(imageSelected(nextImage));
|
dispatch(imageSelected(nextImageId));
|
||||||
}, [dispatch, nextImage]);
|
}, [dispatch, nextImageId]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'left',
|
'left',
|
||||||
() => {
|
() => {
|
||||||
handlePrevImage();
|
handlePrevImage();
|
||||||
},
|
},
|
||||||
[prevImage]
|
[prevImageId]
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@ -119,7 +121,7 @@ const NextPrevImageButtons = () => {
|
|||||||
() => {
|
() => {
|
||||||
handleNextImage();
|
handleNextImage();
|
||||||
},
|
},
|
||||||
[nextImage]
|
[nextImageId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,7 +7,7 @@ import { imageUrlsReceived } from 'services/thunks/image';
|
|||||||
type GalleryImageObjectFitType = 'contain' | 'cover';
|
type GalleryImageObjectFitType = 'contain' | 'cover';
|
||||||
|
|
||||||
export interface GalleryState {
|
export interface GalleryState {
|
||||||
selectedImage?: ImageDTO;
|
selectedImage?: string;
|
||||||
galleryImageMinimumWidth: number;
|
galleryImageMinimumWidth: number;
|
||||||
galleryImageObjectFit: GalleryImageObjectFitType;
|
galleryImageObjectFit: GalleryImageObjectFitType;
|
||||||
shouldAutoSwitchToNewImages: boolean;
|
shouldAutoSwitchToNewImages: boolean;
|
||||||
@ -27,7 +27,7 @@ export const gallerySlice = createSlice({
|
|||||||
name: 'gallery',
|
name: 'gallery',
|
||||||
initialState: initialGalleryState,
|
initialState: initialGalleryState,
|
||||||
reducers: {
|
reducers: {
|
||||||
imageSelected: (state, action: PayloadAction<ImageDTO | undefined>) => {
|
imageSelected: (state, action: PayloadAction<string | undefined>) => {
|
||||||
state.selectedImage = action.payload;
|
state.selectedImage = action.payload;
|
||||||
// TODO: if the user selects an image, disable the auto switch?
|
// TODO: if the user selects an image, disable the auto switch?
|
||||||
// state.shouldAutoSwitchToNewImages = false;
|
// state.shouldAutoSwitchToNewImages = false;
|
||||||
@ -63,17 +63,17 @@ export const gallerySlice = createSlice({
|
|||||||
state.shouldAutoSwitchToNewImages &&
|
state.shouldAutoSwitchToNewImages &&
|
||||||
action.payload.image_category === 'general'
|
action.payload.image_category === 'general'
|
||||||
) {
|
) {
|
||||||
state.selectedImage = action.payload;
|
state.selectedImage = action.payload.image_name;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
// builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
||||||
const { image_name, image_url, thumbnail_url } = action.payload;
|
// const { image_name, image_url, thumbnail_url } = action.payload;
|
||||||
|
|
||||||
if (state.selectedImage?.image_name === image_name) {
|
// if (state.selectedImage?.image_name === image_name) {
|
||||||
state.selectedImage.image_url = image_url;
|
// state.selectedImage.image_url = image_url;
|
||||||
state.selectedImage.thumbnail_url = thumbnail_url;
|
// state.selectedImage.thumbnail_url = thumbnail_url;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,13 +23,9 @@ export type BoardDTO = {
|
|||||||
*/
|
*/
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
/**
|
/**
|
||||||
* The name of the cover image of the board.
|
* The name of the board's cover image.
|
||||||
*/
|
*/
|
||||||
cover_image_name?: string;
|
cover_image_name?: string;
|
||||||
/**
|
|
||||||
* The URL of the thumbnail of the board's cover image.
|
|
||||||
*/
|
|
||||||
cover_image_url?: string;
|
|
||||||
/**
|
/**
|
||||||
* The number of images in the board.
|
* The number of images in the board.
|
||||||
*/
|
*/
|
||||||
|
@ -17,13 +17,18 @@ export class BoardsService {
|
|||||||
/**
|
/**
|
||||||
* List Boards
|
* List Boards
|
||||||
* Gets a list of boards
|
* Gets a list of boards
|
||||||
* @returns OffsetPaginatedResults_BoardDTO_ Successful Response
|
* @returns any Successful Response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static listBoards({
|
public static listBoards({
|
||||||
|
all,
|
||||||
offset,
|
offset,
|
||||||
limit = 10,
|
limit,
|
||||||
}: {
|
}: {
|
||||||
|
/**
|
||||||
|
* Whether to list all boards
|
||||||
|
*/
|
||||||
|
all?: boolean,
|
||||||
/**
|
/**
|
||||||
* The page offset
|
* The page offset
|
||||||
*/
|
*/
|
||||||
@ -32,11 +37,12 @@ export class BoardsService {
|
|||||||
* The number of boards per page
|
* The number of boards per page
|
||||||
*/
|
*/
|
||||||
limit?: number,
|
limit?: number,
|
||||||
}): CancelablePromise<OffsetPaginatedResults_BoardDTO_> {
|
}): CancelablePromise<(OffsetPaginatedResults_BoardDTO_ | Array<BoardDTO>)> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/v1/boards/',
|
url: '/api/v1/boards/',
|
||||||
query: {
|
query: {
|
||||||
|
'all': all,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
'limit': limit,
|
'limit': limit,
|
||||||
},
|
},
|
||||||
@ -53,15 +59,19 @@ export class BoardsService {
|
|||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static createBoard({
|
public static createBoard({
|
||||||
requestBody,
|
boardName,
|
||||||
}: {
|
}: {
|
||||||
requestBody: string,
|
/**
|
||||||
|
* The name of the board to create
|
||||||
|
*/
|
||||||
|
boardName: string,
|
||||||
}): CancelablePromise<BoardDTO> {
|
}): CancelablePromise<BoardDTO> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/v1/boards/',
|
url: '/api/v1/boards/',
|
||||||
body: requestBody,
|
query: {
|
||||||
mediaType: 'application/json',
|
'board_name': boardName,
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Validation Error`,
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
|
144
invokeai/frontend/web/src/services/apiSlice.ts
Normal file
144
invokeai/frontend/web/src/services/apiSlice.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||||
|
import { BoardDTO } from './api/models/BoardDTO';
|
||||||
|
import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_';
|
||||||
|
import { BoardChanges } from './api/models/BoardChanges';
|
||||||
|
import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_';
|
||||||
|
|
||||||
|
type ListBoardsArg = { offset: number; limit: number };
|
||||||
|
type UpdateBoardArg = { board_id: string; changes: BoardChanges };
|
||||||
|
type AddImageToBoardArg = { board_id: string; image_name: string };
|
||||||
|
type RemoveImageFromBoardArg = { board_id: string; image_name: string };
|
||||||
|
type ListBoardImagesArg = { board_id: string; offset: number; limit: number };
|
||||||
|
|
||||||
|
export const api = createApi({
|
||||||
|
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }),
|
||||||
|
reducerPath: 'api',
|
||||||
|
tagTypes: ['Board'],
|
||||||
|
endpoints: (build) => ({
|
||||||
|
/**
|
||||||
|
* Boards Queries
|
||||||
|
*/
|
||||||
|
listBoards: build.query<OffsetPaginatedResults_BoardDTO_, ListBoardsArg>({
|
||||||
|
query: (arg) => ({ url: 'boards/', params: arg }),
|
||||||
|
providesTags: (result, error, arg) => {
|
||||||
|
if (!result) {
|
||||||
|
// Provide the broad 'Board' tag until there is a response
|
||||||
|
return ['Board'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the broad 'Board' tab, and individual tags for each board
|
||||||
|
return [
|
||||||
|
...result.items.map(({ board_id }) => ({
|
||||||
|
type: 'Board' as const,
|
||||||
|
id: board_id,
|
||||||
|
})),
|
||||||
|
'Board',
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
listAllBoards: build.query<Array<BoardDTO>, void>({
|
||||||
|
query: () => ({
|
||||||
|
url: 'boards/',
|
||||||
|
params: { all: true },
|
||||||
|
}),
|
||||||
|
providesTags: (result, error, arg) => {
|
||||||
|
if (!result) {
|
||||||
|
// Provide the broad 'Board' tag until there is a response
|
||||||
|
return ['Board'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the broad 'Board' tab, and individual tags for each board
|
||||||
|
return [
|
||||||
|
...result.map(({ board_id }) => ({
|
||||||
|
type: 'Board' as const,
|
||||||
|
id: board_id,
|
||||||
|
})),
|
||||||
|
'Board',
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boards Mutations
|
||||||
|
*/
|
||||||
|
|
||||||
|
createBoard: build.mutation<BoardDTO, string>({
|
||||||
|
query: (board_name) => ({
|
||||||
|
url: `boards/`,
|
||||||
|
method: 'POST',
|
||||||
|
params: { board_name },
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Board'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateBoard: build.mutation<BoardDTO, UpdateBoardArg>({
|
||||||
|
query: ({ board_id, changes }) => ({
|
||||||
|
url: `boards/${board_id}`,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: changes,
|
||||||
|
}),
|
||||||
|
invalidatesTags: (result, error, arg) => [
|
||||||
|
{ type: 'Board', id: arg.board_id },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteBoard: build.mutation<void, string>({
|
||||||
|
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
|
||||||
|
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }],
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Board Images Queries
|
||||||
|
*/
|
||||||
|
|
||||||
|
listBoardImages: build.query<
|
||||||
|
OffsetPaginatedResults_ImageDTO_,
|
||||||
|
ListBoardImagesArg
|
||||||
|
>({
|
||||||
|
query: ({ board_id, offset, limit }) => ({
|
||||||
|
url: `board_images/${board_id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
body: { offset, limit },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Board Images Mutations
|
||||||
|
*/
|
||||||
|
|
||||||
|
addImageToBoard: build.mutation<void, AddImageToBoardArg>({
|
||||||
|
query: ({ board_id, image_name }) => ({
|
||||||
|
url: `board_images/`,
|
||||||
|
method: 'POST',
|
||||||
|
body: { board_id, image_name },
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Board'],
|
||||||
|
// invalidatesTags: (result, error, arg) => [
|
||||||
|
// { type: 'Board', id: arg.board_id },
|
||||||
|
// ],
|
||||||
|
}),
|
||||||
|
|
||||||
|
removeImageFromBoard: build.mutation<void, RemoveImageFromBoardArg>({
|
||||||
|
query: ({ board_id, image_name }) => ({
|
||||||
|
url: `board_images/`,
|
||||||
|
method: 'DELETE',
|
||||||
|
body: { board_id, image_name },
|
||||||
|
}),
|
||||||
|
invalidatesTags: (result, error, arg) => [
|
||||||
|
{ type: 'Board', id: arg.board_id },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useListBoardsQuery,
|
||||||
|
useListAllBoardsQuery,
|
||||||
|
useCreateBoardMutation,
|
||||||
|
useUpdateBoardMutation,
|
||||||
|
useDeleteBoardMutation,
|
||||||
|
useAddImageToBoardMutation,
|
||||||
|
useRemoveImageFromBoardMutation,
|
||||||
|
useListBoardImagesQuery,
|
||||||
|
} = api;
|
@ -42,7 +42,7 @@ export const boardUpdated = createAppAsyncThunk(
|
|||||||
|
|
||||||
type ImageAddedToBoardArg = Parameters<
|
type ImageAddedToBoardArg = Parameters<
|
||||||
(typeof BoardsService)['createBoardImage']
|
(typeof BoardsService)['createBoardImage']
|
||||||
>[0];
|
>[0]['requestBody'];
|
||||||
|
|
||||||
export const imageAddedToBoard = createAppAsyncThunk(
|
export const imageAddedToBoard = createAppAsyncThunk(
|
||||||
'api/imageAddedToBoard',
|
'api/imageAddedToBoard',
|
||||||
|
Loading…
Reference in New Issue
Block a user