mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
[half-baked] adding image to board modal
This commit is contained in:
parent
bd29e5e655
commit
2e41af2109
@ -23,6 +23,7 @@ import GlobalHotkeys from './GlobalHotkeys';
|
||||
import Toaster from './Toaster';
|
||||
import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||
|
||||
const DEFAULT_CONFIG = {};
|
||||
|
||||
@ -143,6 +144,7 @@ const App = ({
|
||||
</Portal>
|
||||
</Grid>
|
||||
<DeleteImageModal />
|
||||
<UpdateImageBoardModal />
|
||||
<Toaster />
|
||||
<GlobalHotkeys />
|
||||
</>
|
||||
|
@ -21,6 +21,8 @@ import {
|
||||
DeleteImageContext,
|
||||
DeleteImageContextProvider,
|
||||
} from 'app/contexts/DeleteImageContext';
|
||||
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||
import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext';
|
||||
|
||||
const App = lazy(() => import('./App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||
@ -76,11 +78,13 @@ const InvokeAIUI = ({
|
||||
<ThemeLocaleProvider>
|
||||
<ImageDndContext>
|
||||
<DeleteImageContextProvider>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
<AddImageToBoardContextProvider>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
</AddImageToBoardContextProvider>
|
||||
</DeleteImageContextProvider>
|
||||
</ImageDndContext>
|
||||
</ThemeLocaleProvider>
|
||||
|
@ -0,0 +1,151 @@
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { requestedImageDeletion } from 'features/gallery/store/actions';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
createContext,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { controlNetSelector } from 'features/controlNet/store/controlNetSlice';
|
||||
import { nodesSelecter } from 'features/nodes/store/nodesSlice';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { some } from 'lodash-es';
|
||||
import { imageAddedToBoard } from '../../services/thunks/board';
|
||||
|
||||
export type ImageUsage = {
|
||||
isInitialImage: boolean;
|
||||
isCanvasImage: boolean;
|
||||
isNodesImage: boolean;
|
||||
isControlNetImage: boolean;
|
||||
};
|
||||
|
||||
export const selectImageUsage = createSelector(
|
||||
[
|
||||
generationSelector,
|
||||
canvasSelector,
|
||||
nodesSelecter,
|
||||
controlNetSelector,
|
||||
(state: RootState, image_name?: string) => image_name,
|
||||
],
|
||||
(generation, canvas, nodes, controlNet, image_name) => {
|
||||
const isInitialImage = generation.initialImage?.image_name === image_name;
|
||||
|
||||
const isCanvasImage = canvas.layerState.objects.some(
|
||||
(obj) => obj.kind === 'image' && obj.image.image_name === image_name
|
||||
);
|
||||
|
||||
const isNodesImage = nodes.nodes.some((node) => {
|
||||
return some(
|
||||
node.data.inputs,
|
||||
(input) =>
|
||||
input.type === 'image' && input.value?.image_name === image_name
|
||||
);
|
||||
});
|
||||
|
||||
const isControlNetImage = some(
|
||||
controlNet.controlNets,
|
||||
(c) =>
|
||||
c.controlImage?.image_name === image_name ||
|
||||
c.processedControlImage?.image_name === image_name
|
||||
);
|
||||
|
||||
const imageUsage: ImageUsage = {
|
||||
isInitialImage,
|
||||
isCanvasImage,
|
||||
isNodesImage,
|
||||
isControlNetImage,
|
||||
};
|
||||
|
||||
return imageUsage;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
type AddImageToBoardContextValue = {
|
||||
/**
|
||||
* Whether the move image dialog is open.
|
||||
*/
|
||||
isOpen: boolean;
|
||||
/**
|
||||
* Closes the move image dialog.
|
||||
*/
|
||||
onClose: () => void;
|
||||
/**
|
||||
* The image pending movement
|
||||
*/
|
||||
image?: ImageDTO;
|
||||
onClickAddToBoard: (image: ImageDTO) => void;
|
||||
handleAddToBoard: (boardId: string) => void;
|
||||
};
|
||||
|
||||
export const AddImageToBoardContext =
|
||||
createContext<AddImageToBoardContextValue>({
|
||||
isOpen: false,
|
||||
onClose: () => undefined,
|
||||
onClickAddToBoard: () => undefined,
|
||||
handleAddToBoard: () => undefined,
|
||||
});
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
|
||||
export const AddImageToBoardContextProvider = (props: Props) => {
|
||||
const [imageToMove, setImageToMove] = useState<ImageDTO>();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
// Clean up after deleting or dismissing the modal
|
||||
const closeAndClearImageToDelete = useCallback(() => {
|
||||
setImageToMove(undefined);
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
const onClickAddToBoard = useCallback(
|
||||
(image?: ImageDTO) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
setImageToMove(image);
|
||||
onOpen();
|
||||
},
|
||||
[setImageToMove, onOpen]
|
||||
);
|
||||
|
||||
const handleAddToBoard = useCallback(
|
||||
(boardId: string) => {
|
||||
if (imageToMove) {
|
||||
dispatch(
|
||||
imageAddedToBoard({
|
||||
requestBody: {
|
||||
board_id: boardId,
|
||||
image_name: imageToMove.image_name,
|
||||
},
|
||||
})
|
||||
);
|
||||
closeAndClearImageToDelete();
|
||||
}
|
||||
},
|
||||
[closeAndClearImageToDelete, dispatch, imageToMove]
|
||||
);
|
||||
|
||||
return (
|
||||
<AddImageToBoardContext.Provider
|
||||
value={{
|
||||
isOpen,
|
||||
image: imageToMove,
|
||||
onClose: closeAndClearImageToDelete,
|
||||
onClickAddToBoard,
|
||||
handleAddToBoard,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</AddImageToBoardContext.Provider>
|
||||
);
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
Box,
|
||||
Divider,
|
||||
Flex,
|
||||
Select,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
|
||||
import { memo, useContext, useRef, useState } from 'react';
|
||||
import { AddImageToBoardContext } from '../../../../app/contexts/AddImageToBoardContext';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { selectBoardsAll } from '../../store/boardSlice';
|
||||
import IAISelect from '../../../../common/components/IAISelect';
|
||||
|
||||
const UpdateImageBoardModal = () => {
|
||||
const boards = useSelector(selectBoardsAll);
|
||||
const [selectedBoard, setSelectedBoard] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const { isOpen, onClose, handleAddToBoard, image } = useContext(
|
||||
AddImageToBoardContext
|
||||
);
|
||||
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const currentBoard = boards.filter(
|
||||
(board) => board.board_id === image?.board_id
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
isOpen={isOpen}
|
||||
leastDestructiveRef={cancelRef}
|
||||
onClose={onClose}
|
||||
isCentered
|
||||
>
|
||||
<AlertDialogOverlay>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
Move Image to Board
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>
|
||||
<Box>
|
||||
<Flex direction="column" gap={3}>
|
||||
<Text>
|
||||
Moving this image to a board will remove it from its existing
|
||||
board.
|
||||
</Text>
|
||||
<IAISelect
|
||||
placeholder="Select Board"
|
||||
onChange={(e) => setSelectedBoard(e.target.value)}
|
||||
validValues={boards.map((board) => board.board_name)}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</AlertDialogBody>
|
||||
<AlertDialogFooter>
|
||||
<IAIButton onClick={onClose}>Cancel</IAIButton>
|
||||
<IAIButton
|
||||
isDisabled={!selectedBoard}
|
||||
colorScheme="accent"
|
||||
onClick={() => {
|
||||
if (selectedBoard) handleAddToBoard(selectedBoard);
|
||||
}}
|
||||
ml={3}
|
||||
>
|
||||
Add to Board
|
||||
</IAIButton>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UpdateImageBoardModal);
|
@ -34,6 +34,9 @@ import { useAppToaster } from 'app/components/Toaster';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||
import { imageAddedToBoard } from '../../../services/thunks/board';
|
||||
import { setUpdateBoardModalOpen } from '../store/boardSlice';
|
||||
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
||||
|
||||
export const selector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
@ -100,6 +103,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||
|
||||
const { onDelete } = useContext(DeleteImageContext);
|
||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||
const handleDelete = useCallback(() => {
|
||||
onDelete(image);
|
||||
}, [image, onDelete]);
|
||||
@ -175,9 +179,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
// dispatch(setIsLightboxOpen(true));
|
||||
};
|
||||
|
||||
const handleAddToFolder = useCallback(() => {
|
||||
// dispatch(addImageToFolder(image));
|
||||
}, []);
|
||||
const handleAddToBoard = useCallback(() => {
|
||||
onClickAddToBoard(image);
|
||||
}, [image, onClickAddToBoard]);
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
window.open(image.image_url, '_blank');
|
||||
@ -255,8 +259,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
{t('parameters.sendToUnifiedCanvas')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToFolder}>
|
||||
Add to Folder
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||
Add to Board
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
sx={{ color: 'error.300' }}
|
||||
|
@ -27,6 +27,7 @@ type AdditionalBoardsState = {
|
||||
isLoading: boolean;
|
||||
selectedBoardId: EntityId | null;
|
||||
searchText?: string;
|
||||
updateBoardModalOpen: boolean;
|
||||
};
|
||||
|
||||
export const initialBoardsState =
|
||||
@ -36,6 +37,7 @@ export const initialBoardsState =
|
||||
total: 0,
|
||||
isLoading: false,
|
||||
selectedBoardId: null,
|
||||
updateBoardModalOpen: false,
|
||||
});
|
||||
|
||||
export type BoardsState = typeof initialBoardsState;
|
||||
@ -59,6 +61,9 @@ const boardsSlice = createSlice({
|
||||
setBoardSearchText: (state, action: PayloadAction<string>) => {
|
||||
state.searchText = action.payload;
|
||||
},
|
||||
setUpdateBoardModalOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.updateBoardModalOpen = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(receivedBoards.pending, (state) => {
|
||||
@ -105,6 +110,7 @@ export const {
|
||||
boardRemoved,
|
||||
boardIdSelected,
|
||||
setBoardSearchText,
|
||||
setUpdateBoardModalOpen,
|
||||
} = boardsSlice.actions;
|
||||
|
||||
export const boardsSelector = (state: RootState) => state.boards;
|
||||
|
@ -39,3 +39,15 @@ export const boardUpdated = createAppAsyncThunk(
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
||||
type ImageAddedToBoardArg = Parameters<
|
||||
(typeof BoardsService)['createBoardImage']
|
||||
>[0];
|
||||
|
||||
export const imageAddedToBoard = createAppAsyncThunk(
|
||||
'api/imageAddedToBoard',
|
||||
async (arg: ImageAddedToBoardArg) => {
|
||||
const response = await BoardsService.createBoardImage(arg);
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user