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 Toaster from './Toaster';
|
||||||
import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
|
import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
|
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -143,6 +144,7 @@ const App = ({
|
|||||||
</Portal>
|
</Portal>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteImageModal />
|
<DeleteImageModal />
|
||||||
|
<UpdateImageBoardModal />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
</>
|
</>
|
||||||
|
@ -21,6 +21,8 @@ import {
|
|||||||
DeleteImageContext,
|
DeleteImageContext,
|
||||||
DeleteImageContextProvider,
|
DeleteImageContextProvider,
|
||||||
} from 'app/contexts/DeleteImageContext';
|
} from 'app/contexts/DeleteImageContext';
|
||||||
|
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||||
|
import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -76,11 +78,13 @@ const InvokeAIUI = ({
|
|||||||
<ThemeLocaleProvider>
|
<ThemeLocaleProvider>
|
||||||
<ImageDndContext>
|
<ImageDndContext>
|
||||||
<DeleteImageContextProvider>
|
<DeleteImageContextProvider>
|
||||||
|
<AddImageToBoardContextProvider>
|
||||||
<App
|
<App
|
||||||
config={config}
|
config={config}
|
||||||
headerComponent={headerComponent}
|
headerComponent={headerComponent}
|
||||||
setIsReady={setIsReady}
|
setIsReady={setIsReady}
|
||||||
/>
|
/>
|
||||||
|
</AddImageToBoardContextProvider>
|
||||||
</DeleteImageContextProvider>
|
</DeleteImageContextProvider>
|
||||||
</ImageDndContext>
|
</ImageDndContext>
|
||||||
</ThemeLocaleProvider>
|
</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 { ImageDTO } from 'services/api';
|
||||||
import { useDraggable } from '@dnd-kit/core';
|
import { useDraggable } from '@dnd-kit/core';
|
||||||
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
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(
|
export const selector = createSelector(
|
||||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||||
@ -100,6 +103,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
|
|
||||||
const { onDelete } = useContext(DeleteImageContext);
|
const { onDelete } = useContext(DeleteImageContext);
|
||||||
|
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
onDelete(image);
|
onDelete(image);
|
||||||
}, [image, onDelete]);
|
}, [image, onDelete]);
|
||||||
@ -175,9 +179,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
// dispatch(setIsLightboxOpen(true));
|
// dispatch(setIsLightboxOpen(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToFolder = useCallback(() => {
|
const handleAddToBoard = useCallback(() => {
|
||||||
// dispatch(addImageToFolder(image));
|
onClickAddToBoard(image);
|
||||||
}, []);
|
}, [image, onClickAddToBoard]);
|
||||||
|
|
||||||
const handleOpenInNewTab = () => {
|
const handleOpenInNewTab = () => {
|
||||||
window.open(image.image_url, '_blank');
|
window.open(image.image_url, '_blank');
|
||||||
@ -255,8 +259,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
{t('parameters.sendToUnifiedCanvas')}
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToFolder}>
|
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||||
Add to Folder
|
Add to Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: 'error.300' }}
|
sx={{ color: 'error.300' }}
|
||||||
|
@ -27,6 +27,7 @@ type AdditionalBoardsState = {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
selectedBoardId: EntityId | null;
|
selectedBoardId: EntityId | null;
|
||||||
searchText?: string;
|
searchText?: string;
|
||||||
|
updateBoardModalOpen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialBoardsState =
|
export const initialBoardsState =
|
||||||
@ -36,6 +37,7 @@ export const initialBoardsState =
|
|||||||
total: 0,
|
total: 0,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
selectedBoardId: null,
|
selectedBoardId: null,
|
||||||
|
updateBoardModalOpen: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BoardsState = typeof initialBoardsState;
|
export type BoardsState = typeof initialBoardsState;
|
||||||
@ -59,6 +61,9 @@ const boardsSlice = createSlice({
|
|||||||
setBoardSearchText: (state, action: PayloadAction<string>) => {
|
setBoardSearchText: (state, action: PayloadAction<string>) => {
|
||||||
state.searchText = action.payload;
|
state.searchText = action.payload;
|
||||||
},
|
},
|
||||||
|
setUpdateBoardModalOpen: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.updateBoardModalOpen = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(receivedBoards.pending, (state) => {
|
builder.addCase(receivedBoards.pending, (state) => {
|
||||||
@ -105,6 +110,7 @@ export const {
|
|||||||
boardRemoved,
|
boardRemoved,
|
||||||
boardIdSelected,
|
boardIdSelected,
|
||||||
setBoardSearchText,
|
setBoardSearchText,
|
||||||
|
setUpdateBoardModalOpen,
|
||||||
} = boardsSlice.actions;
|
} = boardsSlice.actions;
|
||||||
|
|
||||||
export const boardsSelector = (state: RootState) => state.boards;
|
export const boardsSelector = (state: RootState) => state.boards;
|
||||||
|
@ -39,3 +39,15 @@ export const boardUpdated = createAppAsyncThunk(
|
|||||||
return response;
|
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