diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index dd1c87effb..104fad3364 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -60,6 +60,7 @@ "@chakra-ui/styled-system": "^2.9.0", "@chakra-ui/theme-tools": "^2.0.16", "@dagrejs/graphlib": "^2.1.12", + "@dnd-kit/core": "^6.0.8", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@floating-ui/react-dom": "^2.0.0", diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx new file mode 100644 index 0000000000..9e8495aa63 --- /dev/null +++ b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx @@ -0,0 +1,45 @@ +import { + DndContext, + DragEndEvent, + DragOverlay, + DragStartEvent, +} from '@dnd-kit/core'; +import { PropsWithChildren, memo, useCallback, useState } from 'react'; +import OverlayDragImage from './OverlayDragImage'; +import { ImageDTO } from 'services/api'; +import { isImageDTO } from 'services/types/guards'; + +type ImageDndContextProps = PropsWithChildren; + +const ImageDndContext = (props: ImageDndContextProps) => { + const [draggedImage, setDraggedImage] = useState(null); + + const handleDragStart = useCallback((event: DragStartEvent) => { + const dragData = event.active.data.current; + if (dragData && 'image' in dragData && isImageDTO(dragData.image)) { + setDraggedImage(dragData.image); + } + }, []); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const handleDrop = event.over?.data.current?.handleDrop; + if (handleDrop && typeof handleDrop === 'function' && draggedImage) { + handleDrop(draggedImage); + } + setDraggedImage(null); + }, + [draggedImage] + ); + + return ( + + {props.children} + + {draggedImage && } + + + ); +}; + +export default memo(ImageDndContext); diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx new file mode 100644 index 0000000000..59fe6a7971 --- /dev/null +++ b/invokeai/frontend/web/src/app/components/ImageDnd/OverlayDragImage.tsx @@ -0,0 +1,23 @@ +import { Image } from '@chakra-ui/react'; +import { memo } from 'react'; +import { ImageDTO } from 'services/api'; + +type OverlayDragImageProps = { + image: ImageDTO; +}; + +const OverlayDragImage = (props: OverlayDragImageProps) => { + return ( + + ); +}; + +export default memo(OverlayDragImage); diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 69b2756f96..c94f7624b2 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -16,6 +16,7 @@ import { PartialAppConfig } from 'app/types/invokeai'; import '../../i18n'; import { socketMiddleware } from 'services/events/middleware'; import { Middleware } from '@reduxjs/toolkit'; +import ImageDndContext from './ImageDnd/ImageDndContext'; const App = lazy(() => import('./App')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); @@ -69,11 +70,13 @@ const InvokeAIUI = ({ }> - + + + diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts index 940cc84c1e..9069e477ac 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts @@ -2,12 +2,10 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { t } from 'i18next'; import { addToast } from 'features/system/store/systemSlice'; import { startAppListening } from '..'; -import { - initialImageSelected, - isImageDTO, -} from 'features/parameters/store/actions'; +import { initialImageSelected } from 'features/parameters/store/actions'; import { makeToast } from 'app/components/Toaster'; import { selectImagesById } from 'features/gallery/store/imagesSlice'; +import { isImageDTO } from 'services/types/guards'; export const addInitialImageSelectedListener = () => { startAppListening({ diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 521610adcc..f577b73895 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -13,6 +13,7 @@ import galleryReducer from 'features/gallery/store/gallerySlice'; import imagesReducer from 'features/gallery/store/imagesSlice'; import lightboxReducer from 'features/lightbox/store/lightboxSlice'; import generationReducer from 'features/parameters/store/generationSlice'; +import controlNetReducer from 'features/controlNet/store/controlNetSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import systemReducer from 'features/system/store/systemSlice'; // import sessionReducer from 'features/system/store/sessionSlice'; @@ -45,6 +46,7 @@ const allReducers = { ui: uiReducer, hotkeys: hotkeysReducer, images: imagesReducer, + controlNet: controlNetReducer, // session: sessionReducer, }; @@ -62,6 +64,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [ 'postprocessing', 'system', 'ui', + 'controlNet', // 'hotkeys', // 'config', ]; diff --git a/invokeai/frontend/web/src/common/components/IAICheckbox.tsx b/invokeai/frontend/web/src/common/components/IAICheckbox.tsx deleted file mode 100644 index eb423b2b27..0000000000 --- a/invokeai/frontend/web/src/common/components/IAICheckbox.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Checkbox, CheckboxProps } from '@chakra-ui/react'; -import { memo, ReactNode } from 'react'; - -type IAICheckboxProps = CheckboxProps & { - label: string | ReactNode; -}; - -const IAICheckbox = (props: IAICheckboxProps) => { - const { label, ...rest } = props; - return ( - - {label} - - ); -}; - -export default memo(IAICheckbox); diff --git a/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx b/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx new file mode 100644 index 0000000000..97ff24689c --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx @@ -0,0 +1,25 @@ +import { + Checkbox, + CheckboxProps, + FormControl, + FormControlProps, + FormLabel, +} from '@chakra-ui/react'; +import { memo, ReactNode } from 'react'; + +type IAIFullCheckboxProps = CheckboxProps & { + label: string | ReactNode; + formControlProps?: FormControlProps; +}; + +const IAIFullCheckbox = (props: IAIFullCheckboxProps) => { + const { label, formControlProps, ...rest } = props; + return ( + + {label} + + + ); +}; + +export default memo(IAIFullCheckbox); diff --git a/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx b/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx new file mode 100644 index 0000000000..4d21d3d3d0 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx @@ -0,0 +1,19 @@ +import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react'; +import { memo, ReactNode } from 'react'; + +type IAISimpleCheckboxProps = CheckboxProps & { + label: string | ReactNode; +}; + +const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => { + const { label, ...rest } = props; + return ( + + + {label} + + + ); +}; + +export default memo(IAISimpleCheckbox); diff --git a/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx index 95c888d658..e3bee9797b 100644 --- a/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx @@ -23,9 +23,9 @@ const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => { flexDirection: 'column', position: 'absolute', top: 0, - right: 0, + insetInlineStart: 0, p: 2, - alignItems: 'flex-end', + alignItems: 'flex-start', gap: 2, }} > diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx index b345f2cda0..2f74e5542a 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx @@ -2,7 +2,7 @@ import { ButtonGroup, Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; @@ -117,12 +117,12 @@ const IAICanvasMaskOptions = () => { } > - - diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 94a990bb4c..638332809c 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -1,7 +1,7 @@ import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; @@ -102,50 +102,50 @@ const IAICanvasSettingsButtonPopover = () => { } > - dispatch(setShouldShowIntermediates(e.target.checked)) } /> - dispatch(setShouldShowGrid(e.target.checked))} /> - - dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)) } /> - dispatch(setShouldAutoSave(e.target.checked))} /> - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) } /> - dispatch(setShouldRestrictStrokesToBox(e.target.checked)) } /> - @@ -153,7 +153,7 @@ const IAICanvasSettingsButtonPopover = () => { } /> - dispatch(setShouldAntialias(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 51fa33353b..ad4c6e714b 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -1,7 +1,32 @@ -import { memo } from 'react'; +import { memo, useCallback } from 'react'; import { ControlNetProcessorNode } from '../store/types'; import { ImageDTO } from 'services/api'; import CannyProcessor from './processors/CannyProcessor'; +import { + ControlNet, + ControlNetModel, + controlNetBeginStepPctChanged, + controlNetEndStepPctChanged, + controlNetImageChanged, + controlNetModelChanged, + controlNetProcessedImageChanged, + controlNetRemoved, + controlNetToggled, + controlNetWeightChanged, + isControlNetImageProcessedToggled, +} from '../store/controlNetSlice'; +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; +import IAISlider from 'common/components/IAISlider'; +import ParamControlNetIsEnabled from './parameters/ParamControlNetIsEnabled'; +import ParamControlNetModel from './parameters/ParamControlNetModel'; +import ParamControlNetWeight from './parameters/ParamControlNetWeight'; +import ParamControlNetBeginStepPct from './parameters/ParamControlNetBeginStepPct'; +import ParamControlNetEndStepPct from './parameters/ParamControlNetEndStepPct'; +import { Flex, HStack, VStack } from '@chakra-ui/react'; +import IAISelectableImage from './parameters/IAISelectableImage'; +import IAIButton from 'common/components/IAIButton'; +import IAIIconButton from 'common/components/IAIIconButton'; export type ControlNetProcessorProps = { controlNetId: string; @@ -16,11 +41,83 @@ const renderProcessorComponent = (props: ControlNetProcessorProps) => { } }; -const ControlNet = () => { +type ControlNetProps = { + controlNet: ControlNet; +}; + +const ControlNet = (props: ControlNetProps) => { + const { + controlNetId, + isEnabled, + model, + weight, + beginStepPct, + endStepPct, + controlImage, + isControlImageProcessed, + processedControlImage, + } = props.controlNet; + const dispatch = useAppDispatch(); + + const handleControlImageChanged = useCallback( + (controlImage: ImageDTO) => { + dispatch(controlNetImageChanged({ controlNetId, controlImage })); + }, + [controlNetId, dispatch] + ); + + const handleControlImageReset = useCallback(() => { + dispatch(controlNetImageChanged({ controlNetId, controlImage: null })); + }, [controlNetId, dispatch]); + + const handleControlNetRemoved = useCallback(() => { + dispatch(controlNetRemoved(controlNetId)); + }, [controlNetId, dispatch]); + + const handleIsControlImageProcessedToggled = useCallback(() => { + dispatch( + isControlNetImageProcessedToggled({ + controlNetId, + }) + ); + }, [controlNetId, dispatch]); + + const handleProcessedControlImageChanged = useCallback( + (processedControlImage: ImageDTO | null) => { + dispatch( + controlNetProcessedImageChanged({ + controlNetId, + processedControlImage, + }) + ); + }, + [controlNetId, dispatch] + ); + return ( -
-

ControlNet

-
+ + Remove ControlNet + + + + + + + ); }; diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/IAISelectableImage.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/IAISelectableImage.tsx new file mode 100644 index 0000000000..62cd4603e5 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/IAISelectableImage.tsx @@ -0,0 +1,200 @@ +import { + Box, + Flex, + Icon, + IconButtonProps, + Image, + Text, +} from '@chakra-ui/react'; +import { useDroppable } from '@dnd-kit/core'; +import IAIIconButton from 'common/components/IAIIconButton'; +import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; +import { useGetUrl } from 'common/util/getUrl'; +import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner'; +import { AnimatePresence, motion } from 'framer-motion'; +import { SyntheticEvent } from 'react'; +import { memo, useRef } from 'react'; +import { FaImage, FaUndo } from 'react-icons/fa'; +import { ImageDTO } from 'services/api'; +import { v4 as uuidv4 } from 'uuid'; + +type IAISelectableImageProps = { + image: ImageDTO | null | undefined; + onChange: (image: ImageDTO) => void; + onReset?: () => void; + onError?: (event: SyntheticEvent) => void; + resetIconSize?: IconButtonProps['size']; +}; + +const IAISelectableImage = (props: IAISelectableImageProps) => { + const { image, onChange, onReset, onError, resetIconSize = 'md' } = props; + const droppableId = useRef(uuidv4()); + const { getUrl } = useGetUrl(); + const { isOver, setNodeRef, active } = useDroppable({ + id: droppableId.current, + data: { + handleDrop: onChange, + }, + }); + + return ( + + {image && ( + + } + onError={onError} + sx={{ + borderRadius: 'base', + }} + /> + + + {active && } + + + )} + {!image && ( + <> + + + + + {active && } + + + )} + {image && onReset && ( + + } + onClick={onReset} + /> + + )} + + ); +}; + +export default memo(IAISelectableImage); + +type DropOverlayProps = { + isOver: boolean; +}; + +const DropOverlay = (props: DropOverlayProps) => { + const { isOver } = props; + return ( + + + + + + Drop Image + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginStepPct.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginStepPct.tsx new file mode 100644 index 0000000000..914bfa2818 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginStepPct.tsx @@ -0,0 +1,44 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { controlNetBeginStepPctChanged } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetBeginStepPctProps = { + controlNetId: string; + beginStepPct: number; +}; + +const ParamControlNetBeginStepPct = ( + props: ParamControlNetBeginStepPctProps +) => { + const { controlNetId, beginStepPct } = props; + const dispatch = useAppDispatch(); + + const handleBeginStepPctChanged = useCallback( + (beginStepPct: number) => { + dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct })); + }, + [controlNetId, dispatch] + ); + + const handleBeginStepPctReset = () => { + dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct: 0 })); + }; + + return ( + + ); +}; + +export default memo(ParamControlNetBeginStepPct); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetEndStepPct.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetEndStepPct.tsx new file mode 100644 index 0000000000..d3d831cf31 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetEndStepPct.tsx @@ -0,0 +1,42 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { controlNetEndStepPctChanged } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetEndStepPctProps = { + controlNetId: string; + endStepPct: number; +}; + +const ParamControlNetEndStepPct = (props: ParamControlNetEndStepPctProps) => { + const { controlNetId, endStepPct } = props; + const dispatch = useAppDispatch(); + + const handleEndStepPctChanged = useCallback( + (endStepPct: number) => { + dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct })); + }, + [controlNetId, dispatch] + ); + + const handleEndStepPctReset = () => { + dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: 0 })); + }; + + return ( + + ); +}; + +export default memo(ParamControlNetEndStepPct); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx new file mode 100644 index 0000000000..f29b9396b4 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetIsEnabled.tsx @@ -0,0 +1,28 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIFullCheckbox from 'common/components/IAIFullCheckbox'; +import { controlNetToggled } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetIsEnabledProps = { + controlNetId: string; + isEnabled: boolean; +}; + +const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => { + const { controlNetId, isEnabled } = props; + const dispatch = useAppDispatch(); + + const handleIsEnabledChanged = useCallback(() => { + dispatch(controlNetToggled(controlNetId)); + }, [dispatch, controlNetId]); + + return ( + + ); +}; + +export default memo(ParamControlNetIsEnabled); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx new file mode 100644 index 0000000000..d38fdc902c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx @@ -0,0 +1,38 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAICustomSelect from 'common/components/IAICustomSelect'; +import { + CONTROLNET_MODELS, + ControlNetModel, + controlNetModelChanged, +} from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamIsControlNetModelProps = { + controlNetId: string; + model: ControlNetModel; +}; + +const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => { + const { controlNetId, model } = props; + const dispatch = useAppDispatch(); + + const handleModelChanged = useCallback( + (val: string | null | undefined) => { + // TODO: do not cast + const model = val as ControlNetModel; + dispatch(controlNetModelChanged({ controlNetId, model })); + }, + [controlNetId, dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamIsControlNetModel); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx new file mode 100644 index 0000000000..11272582d0 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx @@ -0,0 +1,42 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISlider from 'common/components/IAISlider'; +import { controlNetWeightChanged } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type ParamControlNetWeightProps = { + controlNetId: string; + weight: number; +}; + +const ParamControlNetWeight = (props: ParamControlNetWeightProps) => { + const { controlNetId, weight } = props; + const dispatch = useAppDispatch(); + + const handleWeightChanged = useCallback( + (weight: number) => { + dispatch(controlNetWeightChanged({ controlNetId, weight })); + }, + [controlNetId, dispatch] + ); + + const handleWeightReset = () => { + dispatch(controlNetWeightChanged({ controlNetId, weight: 1 })); + }; + + return ( + + ); +}; + +export default memo(ParamControlNetWeight); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx index 012fb8532b..dc735a1ee5 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx @@ -4,8 +4,6 @@ import { memo, useCallback, useState } from 'react'; import ControlNetProcessButton from './common/ControlNetProcessButton'; import { useAppDispatch } from 'app/store/storeHooks'; import { controlNetImageProcessed } from 'features/controlNet/store/actions'; -import { ImageDTO } from 'services/api'; -import ControlNetProcessorImage from './common/ControlNetProcessorImage'; import { ControlNetProcessorProps } from '../ControlNet'; export const CANNY_PROCESSOR = 'canny_processor'; diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorImage.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorImage.tsx deleted file mode 100644 index 6c253291f7..0000000000 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorImage.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Flex, Image } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectImagesById } from 'features/gallery/store/imagesSlice'; -import { DragEvent, memo, useCallback } from 'react'; -import { ImageDTO } from 'services/api'; - -type ControlNetProcessorImageProps = { - image: ImageDTO | undefined; - setImage: (image: ImageDTO) => void; -}; - -const ControlNetProcessorImage = (props: ControlNetProcessorImageProps) => { - const { image, setImage } = props; - const state = useAppSelector((state) => state); - const handleDrop = useCallback( - (e: DragEvent) => { - const name = e.dataTransfer.getData('invokeai/imageName'); - const droppedImage = selectImagesById(state, name); - if (droppedImage) { - setImage(droppedImage); - } - }, - [setImage, state] - ); - - if (!image) { - return Upload Image; - } - - return ; -}; - -export default memo(ControlNetProcessorImage); diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index 88188a0a7f..5909c85cac 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -1,5 +1,10 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; +import { + $CombinedState, + PayloadAction, + createSelector, +} from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; import { ImageDTO } from 'services/api'; export const CONTROLNET_MODELS = [ @@ -11,22 +16,22 @@ export const CONTROLNET_MODELS = [ 'lllyasviel/sd-controlnet-scribble', 'lllyasviel/sd-controlnet-normal', 'lllyasviel/sd-controlnet-mlsd', -] as const; +]; -export const CONTROLNET_PROCESSORS = [ - 'canny', - 'contentShuffle', - 'hed', - 'lineart', - 'lineartAnime', - 'mediapipeFace', - 'midasDepth', - 'mlsd', - 'normalBae', - 'openpose', - 'pidi', - 'zoeDepth', -] as const; +// export const CONTROLNET_PROCESSORS = [ +// 'canny', +// 'contentShuffle', +// 'hed', +// 'lineart', +// 'lineartAnime', +// 'mediapipeFace', +// 'midasDepth', +// 'mlsd', +// 'normalBae', +// 'openpose', +// 'pidi', +// 'zoeDepth', +// ] as const; export type ControlNetModel = (typeof CONTROLNET_MODELS)[number]; @@ -37,6 +42,7 @@ export const initialControlNet: Omit = { beginStepPct: 0, endStepPct: 1, controlImage: null, + isControlImageProcessed: false, processedControlImage: null, }; @@ -48,6 +54,7 @@ export type ControlNet = { beginStepPct: number; endStepPct: number; controlImage: ImageDTO | null; + isControlImageProcessed: boolean; processedControlImage: ImageDTO | null; }; @@ -63,15 +70,14 @@ export const controlNetSlice = createSlice({ name: 'controlNet', initialState: initialControlNetState, reducers: { - controlNetAddedFromModel: ( + controlNetAdded: ( state, - action: PayloadAction<{ controlNetId: string; model: ControlNetModel }> + action: PayloadAction<{ controlNetId: string }> ) => { - const { controlNetId, model } = action.payload; + const { controlNetId } = action.payload; state.controlNets[controlNetId] = { ...initialControlNet, controlNetId, - model, }; }, controlNetAddedFromImage: ( @@ -96,11 +102,24 @@ export const controlNetSlice = createSlice({ }, controlNetImageChanged: ( state, - action: PayloadAction<{ controlNetId: string; controlImage: ImageDTO }> + action: PayloadAction<{ + controlNetId: string; + controlImage: ImageDTO | null; + }> ) => { const { controlNetId, controlImage } = action.payload; state.controlNets[controlNetId].controlImage = controlImage; }, + isControlNetImageProcessedToggled: ( + state, + action: PayloadAction<{ + controlNetId: string; + }> + ) => { + const { controlNetId } = action.payload; + state.controlNets[controlNetId].isControlImageProcessed = + !state.controlNets[controlNetId].isControlImageProcessed; + }, controlNetProcessedImageChanged: ( state, action: PayloadAction<{ @@ -144,10 +163,11 @@ export const controlNetSlice = createSlice({ }); export const { - controlNetAddedFromModel, + controlNetAdded, controlNetAddedFromImage, controlNetRemoved, controlNetImageChanged, + isControlNetImageProcessedToggled, controlNetProcessedImageChanged, controlNetToggled, controlNetModelChanged, @@ -157,3 +177,5 @@ export const { } = controlNetSlice.actions; export default controlNetSlice.reducer; + +export const controlNetSelector = (state: RootState) => state.controlNet; diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx index f652cebda2..c1fe2569e3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx @@ -39,6 +39,8 @@ import { } from '../store/actions'; import { useAppToaster } from 'app/components/Toaster'; import { ImageDTO } from 'services/api'; +import { useDraggable } from '@dnd-kit/core'; +import { CSS } from '@dnd-kit/utilities'; export const selector = createSelector( [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector], @@ -117,6 +119,13 @@ const HoverableImage = memo((props: HoverableImageProps) => { const { recallBothPrompts, recallSeed, recallAllParameters } = useRecallParameters(); + const { attributes, listeners, setNodeRef } = useDraggable({ + id: image_name, + data: { + image, + }, + }); + const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); @@ -212,7 +221,12 @@ const HoverableImage = memo((props: HoverableImageProps) => { }; return ( - <> + menuProps={{ size: 'sm', isLazy: true }} renderMenu={() => ( @@ -291,8 +305,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} userSelect="none" - draggable={true} - onDragStart={handleDragStart} + // draggable={true} + // onDragStart={handleDragStart} onClick={handleSelectImage} ref={ref} sx={{ @@ -373,7 +387,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { onClose={onDeleteDialogClose} handleDelete={handleDelete} /> - + ); }, memoEqualityCheck); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 77f42a11a6..fe8690e379 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -10,7 +10,7 @@ import { } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import IAISlider from 'common/components/IAISlider'; @@ -233,7 +233,7 @@ const ImageGalleryContent = () => { withReset handleReset={() => dispatch(setGalleryImageMinimumWidth(64))} /> - @@ -244,14 +244,14 @@ const ImageGalleryContent = () => { ) } /> - ) => dispatch(setShouldAutoSwitchToNewImages(e.target.checked)) } /> - ) => diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx index 57cefb0a9c..1232ff28e1 100644 --- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx @@ -1,39 +1,26 @@ -import { Box, Image } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; -import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder'; -import { useGetUrl } from 'common/util/getUrl'; -import useGetImageByName from 'features/gallery/hooks/useGetImageByName'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { ImageInputFieldTemplate, ImageInputFieldValue, } from 'features/nodes/types/types'; -import { DragEvent, memo, useCallback, useState } from 'react'; +import { memo, useCallback } from 'react'; import { FieldComponentProps } from './types'; +import IAISelectableImage from 'features/controlNet/components/parameters/IAISelectableImage'; +import { ImageDTO } from 'services/api'; +import { Flex } from '@chakra-ui/react'; const ImageInputFieldComponent = ( props: FieldComponentProps ) => { const { nodeId, field } = props; - const getImageByName = useGetImageByName(); const dispatch = useAppDispatch(); - const [url, setUrl] = useState(field.value?.image_url); - const { getUrl } = useGetUrl(); - - const handleDrop = useCallback( - (e: DragEvent) => { - const name = e.dataTransfer.getData('invokeai/imageName'); - const image = getImageByName(name); - - if (!image) { - return; - } - - setUrl(image.image_url); + const handleChange = useCallback( + (image: ImageDTO) => { dispatch( fieldValueChanged({ nodeId, @@ -42,13 +29,20 @@ const ImageInputFieldComponent = ( }) ); }, - [getImageByName, dispatch, field.name, nodeId] + [dispatch, field.name, nodeId] ); return ( - - } /> - + + + ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx index e62e343d66..2c4088d376 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -1,61 +1,59 @@ -import { Flex, Text, useDisclosure } from '@chakra-ui/react'; +import { Flex, useDisclosure } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import IAICollapse from 'common/components/IAICollapse'; -import { memo, useCallback, useState } from 'react'; -import IAICustomSelect from 'common/components/IAICustomSelect'; +import { memo, useCallback } from 'react'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaPlus } from 'react-icons/fa'; -import CannyProcessor from 'features/controlNet/components/processors/CannyProcessor'; import ControlNet from 'features/controlNet/components/ControlNet'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { + controlNetAdded, + controlNetSelector, +} from 'features/controlNet/store/controlNetSlice'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { map } from 'lodash-es'; +import { v4 as uuidv4 } from 'uuid'; -const CONTROLNET_MODELS = [ - 'lllyasviel/sd-controlnet-canny', - 'lllyasviel/sd-controlnet-depth', - 'lllyasviel/sd-controlnet-hed', - 'lllyasviel/sd-controlnet-seg', - 'lllyasviel/sd-controlnet-openpose', - 'lllyasviel/sd-controlnet-scribble', - 'lllyasviel/sd-controlnet-normal', - 'lllyasviel/sd-controlnet-mlsd', -]; +const selector = createSelector( + controlNetSelector, + (controlNet) => { + const { controlNets } = controlNet; + + return { controlNets }; + }, + defaultSelectorOptions +); const ParamControlNetCollapse = () => { const { t } = useTranslation(); const { isOpen, onToggle } = useDisclosure(); - const [model, setModel] = useState(CONTROLNET_MODELS[0]); + const { controlNets } = useAppSelector(selector); + const dispatch = useAppDispatch(); - const handleSetControlNet = useCallback( - (model: string | null | undefined) => { - if (model) { - setModel(model); - } - }, - [] - ); + const handleClickedAddControlNet = useCallback(() => { + dispatch(controlNetAdded({ controlNetId: uuidv4() })); + }, [dispatch]); return ( - - // - // - // - // } - // /> - // - // - // + + + } + /> + + {map(controlNets, (c) => ( + + ))} + ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx index f17ebcbdc0..1746c9b592 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx @@ -28,7 +28,6 @@ const InitialImageDisplay = () => { gap: 4, }} > -
diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx index cfe1513420..8cd7b99dc5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx @@ -2,7 +2,10 @@ import { Flex, Icon, Image } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useGetUrl } from 'common/util/getUrl'; -import { clearInitialImage } from 'features/parameters/store/generationSlice'; +import { + clearInitialImage, + initialImageChanged, +} from 'features/parameters/store/generationSlice'; import { DragEvent, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; @@ -13,6 +16,8 @@ import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinn import { FaImage } from 'react-icons/fa'; import { configSelector } from '../../../../system/store/configSelectors'; import { useAppToaster } from 'app/components/Toaster'; +import IAISelectableImage from 'features/controlNet/components/parameters/IAISelectableImage'; +import { ImageDTO } from 'services/api'; const selector = createSelector( [generationSelector], @@ -51,14 +56,17 @@ const InitialImagePreview = () => { } }, [dispatch, t, toaster, shouldFetchImages]); - const handleDrop = useCallback( - (e: DragEvent) => { - const name = e.dataTransfer.getData('invokeai/imageName'); - dispatch(initialImageSelected(name)); + const handleChange = useCallback( + (image: ImageDTO) => { + dispatch(initialImageChanged(image)); }, [dispatch] ); + const handleReset = useCallback(() => { + dispatch(clearInitialImage()); + }, [dispatch]); + return ( { alignItems: 'center', justifyContent: 'center', }} - onDrop={handleDrop} + // onDrop={handleDrop} > - {initialImage?.image_url && ( + + {/* {initialImage?.image_url && ( <> { color: 'base.500', }} /> - )} + )} */} ); }; diff --git a/invokeai/frontend/web/src/features/parameters/store/actions.ts b/invokeai/frontend/web/src/features/parameters/store/actions.ts index e9b90134e1..eba01248d1 100644 --- a/invokeai/frontend/web/src/features/parameters/store/actions.ts +++ b/invokeai/frontend/web/src/features/parameters/store/actions.ts @@ -7,25 +7,6 @@ export type ImageNameAndOrigin = { image_origin: ResourceOrigin; }; -export const isImageDTO = (image: any): image is ImageDTO => { - return ( - image && - isObject(image) && - 'image_name' in image && - image?.image_name !== undefined && - 'image_origin' in image && - image?.image_origin !== undefined && - 'image_url' in image && - image?.image_url !== undefined && - 'thumbnail_url' in image && - image?.thumbnail_url !== undefined && - 'image_category' in image && - image?.image_category !== undefined && - 'created_at' in image && - image?.created_at !== undefined - ); -}; - export const initialImageSelected = createAction( 'generation/initialImageSelected' ); diff --git a/invokeai/frontend/web/src/features/system/components/ModelManager/AddCheckpointModel.tsx b/invokeai/frontend/web/src/features/system/components/ModelManager/AddCheckpointModel.tsx index bb5db0302d..e6bd0b6ffb 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelManager/AddCheckpointModel.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelManager/AddCheckpointModel.tsx @@ -10,7 +10,7 @@ import { } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIInput from 'common/components/IAIInput'; import IAINumberInput from 'common/components/IAINumberInput'; import React from 'react'; @@ -74,12 +74,12 @@ export default function AddCheckpointModel() { return ( - setAddmanually(!addManually)} /> - setAddmanually(!addManually)} diff --git a/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx b/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx index 6ba148cac4..219d49d4ee 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelManager/MergeModels.tsx @@ -24,7 +24,7 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import * as InvokeAI from 'app/types/invokeai'; import IAISlider from 'common/components/IAISlider'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; export default function MergeModels() { const dispatch = useAppDispatch(); @@ -286,7 +286,7 @@ export default function MergeModels() { )} - setModelMergeForce(e.target.checked)} diff --git a/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx b/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx index 3a99997ac8..3381cb85d3 100644 --- a/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx +++ b/invokeai/frontend/web/src/features/system/components/ModelManager/SearchModels.tsx @@ -1,5 +1,5 @@ import IAIButton from 'common/components/IAIButton'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import React from 'react'; @@ -81,13 +81,13 @@ function SearchModelEntry({ borderRadius={4} > - {model.name}} isChecked={modelsToAdd.includes(model.name)} isDisabled={existingModels.includes(model.location)} onChange={foundModelsChangeHandler} - > + > {existingModels.includes(model.location) && ( {t('modelManager.modelExists')} )} @@ -324,7 +324,7 @@ export default function SearchModels() { > {t('modelManager.deselectAll')} - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx index 3b3daeaa4c..dd2fd00d22 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabParameters.tsx @@ -8,6 +8,7 @@ import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; import ImageToImageTabCoreParameters from './ImageToImageTabCoreParameters'; +import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; const ImageToImageTabParameters = () => { return ( @@ -17,6 +18,7 @@ const ImageToImageTabParameters = () => { + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx index 042749e792..53e36f62b6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,7 @@ export default function UnifiedCanvasDarkenOutsideSelection() { const { t } = useTranslation(); return ( - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx index 24f3f45a25..ceb58cb5ca 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -16,7 +16,7 @@ export default function UnifiedCanvasEnableMask() { dispatch(setIsMaskEnabled(!isMaskEnabled)); return ( - diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx index 9b4b20e936..fd3396533c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ export default function UnifiedCanvasPreserveMask() { ); return ( - dispatch(setShouldPreserveMaskedArea(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx index bfaa7cdae8..a173211258 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx @@ -1,7 +1,7 @@ import { Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; @@ -73,33 +73,33 @@ const UnifiedCanvasSettings = () => { } > - dispatch(setShouldShowIntermediates(e.target.checked)) } /> - dispatch(setShouldAutoSave(e.target.checked))} /> - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) } /> - dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) } /> - dispatch(setShouldAntialias(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx index e3d8a518ef..e17f74ce41 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldShowGrid } from 'features/canvas/store/canvasSlice'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ export default function UnifiedCanvasShowGrid() { const { t } = useTranslation(); return ( - dispatch(setShouldShowGrid(e.target.checked))} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx index c334bd213b..69e9a4e78b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx @@ -1,6 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAICheckbox from 'common/components/IAICheckbox'; +import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice'; import { ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; @@ -17,7 +17,7 @@ export default function UnifiedCanvasSnapToGrid() { dispatch(setShouldSnapToGrid(e.target.checked)); return ( - { + return ( + isObject(obj) && + 'image_name' in obj && + isString(obj?.image_name) && + 'thumbnail_url' in obj && + isString(obj?.thumbnail_url) && + 'image_url' in obj && + isString(obj?.image_url) && + 'image_origin' in obj && + isString(obj?.image_origin) && + 'created_at' in obj && + isString(obj?.created_at) + ); +}; + export const isImageOutput = ( output: GraphExecutionState['results'][string] ): output is ImageOutput => output.type === 'image_output'; diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 356f7466fe..e3b2978457 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -937,6 +937,29 @@ gonzales-pe "^4.3.0" node-source-walk "^5.0.1" +"@dnd-kit/accessibility@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c" + integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.0.8": + version "6.0.8" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.8.tgz#040ae13fea9787ee078e5f0361f3b49b07f3f005" + integrity sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA== + dependencies: + "@dnd-kit/accessibility" "^3.0.0" + "@dnd-kit/utilities" "^3.2.1" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.1.tgz#53f9e2016fd2506ec49e404c289392cfff30332a" + integrity sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA== + dependencies: + tslib "^2.0.0" + "@emotion/babel-plugin@^11.10.8": version "11.10.8" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.8.tgz#bae325c902937665d00684038fd5294223ef9e1d"