From 19c66e5c7635a1ba7e02cc21b7f386296d5a0897 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 15 Jun 2024 20:22:07 +1000 Subject: [PATCH] refactor(ui): canvas v2 (wip) merge all canvas state reducers into one big slice (but with the logic split across files so it's not hell) --- .../listeners/boardAndImagesDeleted.ts | 2 +- .../listeners/controlAdapterPreprocessor.ts | 2 +- .../listeners/imageDeletionListeners.ts | 2 +- .../listeners/imageDropped.ts | 2 +- .../listeners/imageUploaded.ts | 68 +-- .../listeners/modelsLoaded.ts | 2 +- .../listeners/promptChanged.ts | 2 +- .../listeners/setDefaultSettings.ts | 2 +- invokeai/frontend/web/src/app/store/store.ts | 20 +- .../src/common/hooks/useIsReadyToEnqueue.ts | 2 +- .../components/ControlAdapterImagePreview.tsx | 2 +- .../components/AddLayerButton.tsx | 3 +- .../components/AddPromptButtons.tsx | 8 +- .../controlLayers/components/BrushWidth.tsx | 2 +- .../components/ControlAdapter/CA.tsx | 2 +- .../ControlAdapter/CAActionsMenu.tsx | 31 +- .../ControlAdapter/CAEntityHeader.tsx | 5 +- .../ControlAdapter/CAImagePreview.tsx | 2 +- .../ControlAdapter/CAOpacityAndFilter.tsx | 2 +- .../components/ControlAdapter/CASettings.tsx | 6 +- .../components/ControlLayersPanelContent.tsx | 37 +- .../components/DeleteAllLayersButton.tsx | 20 +- .../controlLayers/components/EraserWidth.tsx | 2 +- .../components/FillColorPicker.tsx | 2 +- .../components/HeadsUpDisplay.tsx | 2 +- .../components/IPAdapter/IPA.tsx | 2 +- .../components/IPAdapter/IPAHeader.tsx | 5 +- .../components/IPAdapter/IPAImagePreview.tsx | 2 +- .../components/IPAdapter/IPASettings.tsx | 6 +- .../controlLayers/components/Layer/Layer.tsx | 2 +- .../components/Layer/LayerActionsMenu.tsx | 31 +- .../components/Layer/LayerHeader.tsx | 5 +- .../components/Layer/LayerOpacity.tsx | 5 +- .../components/RGGlobalOpacity.tsx | 4 +- .../components/RegionalGuidance/RG.tsx | 6 +- .../RegionalGuidance/RGActionsMenu.tsx | 16 +- .../components/RegionalGuidance/RGHeader.tsx | 7 +- .../RegionalGuidance/RGIPAdapterSettings.tsx | 6 +- .../RegionalGuidance/RGIPAdapters.tsx | 4 +- .../RGMaskFillColorPicker.tsx | 5 +- .../RegionalGuidance/RGNegativePrompt.tsx | 5 +- .../RegionalGuidance/RGPositivePrompt.tsx | 5 +- .../RegionalGuidance/RGSettings.tsx | 8 +- .../RegionalGuidance/RGSettingsPopover.tsx | 5 +- .../components/StageComponent.tsx | 86 ++-- .../controlLayers/components/ToolChooser.tsx | 15 +- .../components/UndoRedoButtonGroup.tsx | 2 +- .../controlLayers/hooks/addLayerHooks.ts | 4 +- .../controlLayers/hooks/layerStateHooks.ts | 2 +- ...controlLayersSlice.ts => canvasV2Slice.ts} | 103 ++++ .../store/controlAdaptersReducers.ts | 238 +++++++++ .../store/controlAdaptersSlice.ts | 291 ----------- .../controlLayers/store/ipAdaptersReducers.ts | 102 ++++ .../controlLayers/store/ipAdaptersSlice.ts | 149 ------ .../controlLayers/store/layersReducers.ts | 227 +++++++++ .../controlLayers/store/layersSlice.ts | 275 ----------- .../store/regionalGuidanceSlice.ts | 452 ------------------ .../controlLayers/store/regionsReducers.ts | 381 +++++++++++++++ .../src/features/controlLayers/store/test.ts | 57 +++ .../src/features/controlLayers/store/types.ts | 5 + .../components/DeleteImageModal.tsx | 2 +- .../deleteImageModal/store/selectors.ts | 2 +- .../components/Boards/DeleteBoardModal.tsx | 2 +- .../SingleSelectionMenuItems.tsx | 2 +- .../ImageViewer/CurrentImageButtons.tsx | 2 +- .../src/features/metadata/util/handlers.ts | 2 +- .../src/features/metadata/util/recallers.ts | 2 +- .../util/graph/generation/addControlLayers.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 2 +- .../components/Core/ParamPositivePrompt.tsx | 2 +- .../ImageSize/AspectRatioCanvasPreview.tsx | 2 +- .../parameters/hooks/usePreselectedImage.ts | 2 +- .../queue/components/QueueButtonTooltip.tsx | 2 +- .../ParamSDXLNegativeStylePrompt.tsx | 2 +- .../ParamSDXLPositiveStylePrompt.tsx | 2 +- .../SDXLPrompts/SDXLConcatButton.tsx | 2 +- .../ImageSettingsAccordion.tsx | 2 +- .../ImageSizeLinear.tsx | 2 +- .../ParametersPanelTextToImage.tsx | 2 +- 79 files changed, 1313 insertions(+), 1473 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/store/{controlLayersSlice.ts => canvasV2Slice.ts} (72%) create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts delete mode 100644 invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts delete mode 100644 invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts delete mode 100644 invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts delete mode 100644 invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/test.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts index fb4a23912a..eb7a793d3a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts @@ -1,7 +1,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice'; +import { allLayersDeleted } from 'features/controlLayers/store/canvasV2Slice'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { imagesApi } from 'services/api/endpoints/images'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts index bdd72764f2..4e5fb3cb00 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts @@ -10,7 +10,7 @@ import { controlAdapterProcessorConfigChanged, controlAdapterProcessorPendingBatchIdChanged, controlAdapterRecalled, -} from 'features/controlLayers/store/controlLayersSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; import { isControlAdapterLayer } from 'features/controlLayers/store/types'; import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters'; import { toast } from 'features/toast/toast'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts index 218b0be8ee..cb4e9ec8c8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts @@ -8,7 +8,7 @@ import { selectControlAdapterAll, } from 'features/controlAdapters/store/controlAdaptersSlice'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; -import { layerDeleted } from 'features/controlLayers/store/controlLayersSlice'; +import { layerDeleted } from 'features/controlLayers/store/canvasV2Slice'; import { isControlAdapterLayer, isInitialImageLayer, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index 663a8f7d19..f5d04ccd02 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -13,7 +13,7 @@ import { layerImageAdded, ipAdapterImageChanged, regionalGuidanceIPAdapterImageChanged, -} from 'features/controlLayers/store/controlLayersSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { isValidDrop } from 'features/dnd/util/isValidDrop'; import { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 3c9059d9c9..e63d0aa8c3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -1,19 +1,8 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; -import { - controlAdapterImageChanged, - controlAdapterIsEnabledChanged, -} from 'features/controlAdapters/store/controlAdaptersSlice'; -import { - controlAdapterImageChanged, - iiLayerImageChanged, - ipAdapterImageChanged, - regionalGuidanceIPAdapterImageChanged, -} from 'features/controlLayers/store/controlLayersSlice'; +import { caImageChanged, ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; -import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { toast } from 'features/toast/toast'; import { t } from 'i18next'; @@ -81,15 +70,6 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis return; } - if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') { - dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state))); - toast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setAsCanvasInitialImage'), - }); - return; - } - if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') { dispatch(upscaleInitialImageChanged(imageDTO)); toast({ @@ -99,57 +79,27 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis return; } - if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') { + if (postUploadAction?.type === 'SET_CA_IMAGE') { const { id } = postUploadAction; - dispatch( - controlAdapterIsEnabledChanged({ - id, - isEnabled: true, - }) - ); - dispatch( - controlAdapterImageChanged({ - id, - controlImage: imageDTO.image_name, - }) - ); - toast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }); - return; - } - - if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') { - const { layerId } = postUploadAction; - dispatch(controlAdapterImageChanged({ layerId, imageDTO })); + dispatch(caImageChanged({ id, imageDTO })); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'), }); } - if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') { - const { layerId } = postUploadAction; - dispatch(ipAdapterImageChanged({ layerId, imageDTO })); + if (postUploadAction?.type === 'SET_IPA_IMAGE') { + const { id } = postUploadAction; + dispatch(ipaImageChanged({ id, imageDTO })); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'), }); } - if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') { - const { layerId, ipAdapterId } = postUploadAction; - dispatch(regionalGuidanceIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO })); - toast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }); - } - - if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') { - const { layerId } = postUploadAction; - dispatch(iiLayerImageChanged({ layerId, imageDTO })); + if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') { + const { id, ipAdapterId } = postUploadAction; + dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO })); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'), diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 30f558086e..5645567613 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -6,7 +6,7 @@ import { controlAdapterModelCleared, selectControlAdapterAll, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { loraRemoved } from 'features/lora/store/loraSlice'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index 2b4da169eb..aba3e8ecc3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -1,6 +1,6 @@ import { isAnyOf } from '@reduxjs/toolkit'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { positivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { combinatorialToggled, isErrorChanged, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts index 415c359d70..c706b55c7d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts @@ -1,5 +1,5 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { setDefaultSettings } from 'features/parameters/store/actions'; import { setCfgRescaleMultiplier, diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 17a4205725..3cedd0db26 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -5,17 +5,7 @@ import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver'; import { errorHandler } from 'app/store/enhancers/reduxRemember/errors'; import type { JSONObject } from 'common/types'; import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice'; -import { - controlAdaptersV2PersistConfig, - controlAdaptersV2Slice, -} from 'features/controlLayers/store/controlAdaptersSlice'; -import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; -import { ipAdaptersPersistConfig, ipAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice'; -import { layersPersistConfig, layersSlice } from 'features/controlLayers/store/layersSlice'; -import { - regionalGuidancePersistConfig, - regionalGuidanceSlice, -} from 'features/controlLayers/store/regionalGuidanceSlice'; +import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice'; @@ -70,10 +60,6 @@ const allReducers = { [workflowSettingsSlice.name]: workflowSettingsSlice.reducer, [upscaleSlice.name]: upscaleSlice.reducer, [stylePresetSlice.name]: stylePresetSlice.reducer, - [layersSlice.name]: layersSlice.reducer, - [controlAdaptersV2Slice.name]: controlAdaptersV2Slice.reducer, - [ipAdaptersSlice.name]: ipAdaptersSlice.reducer, - [regionalGuidanceSlice.name]: regionalGuidanceSlice.reducer, }; const rootReducer = combineReducers(allReducers); @@ -118,10 +104,6 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig, [upscalePersistConfig.name]: upscalePersistConfig, [stylePresetPersistConfig.name]: stylePresetPersistConfig, - [layersPersistConfig.name]: layersPersistConfig, - [controlAdaptersV2PersistConfig.name]: controlAdaptersV2PersistConfig, - [ipAdaptersPersistConfig.name]: ipAdaptersPersistConfig, - [regionalGuidancePersistConfig.name]: regionalGuidancePersistConfig, }; const unserialize: UnserializeFunction = (data, key) => { diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index c47a285c54..2f2847f058 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice'; import { selectLayersSlice } from 'features/controlLayers/store/layersSlice'; import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice'; diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx index 6caf46d0b4..c8670470b2 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx @@ -12,7 +12,7 @@ import { controlAdapterImageChanged, selectControlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx index 591f4a41a1..aee648be93 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx @@ -1,8 +1,7 @@ import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks'; -import { layerAdded } from 'features/controlLayers/store/layersSlice'; -import { rgAdded } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { layerAdded, rgAdded } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx index 8f312aba1d..3f4222b202 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx @@ -5,8 +5,8 @@ import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerH import { rgNegativePromptChanged, rgPositivePromptChanged, - selectRegionalGuidanceSlice, -} from 'features/controlLayers/store/regionalGuidanceSlice'; + selectCanvasV2Slice, +} from 'features/controlLayers/store/canvasV2Slice'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -21,8 +21,8 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => { const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id); const selectValidActions = useMemo( () => - createMemoizedSelector(selectRegionalGuidanceSlice, (regionalGuidanceState) => { - const rg = regionalGuidanceState.regions.find((rg) => rg.id === id); + createMemoizedSelector(selectCanvasV2Slice, (caState) => { + const rg = caState.regions.find((rg) => rg.id === id); return { canAddPositivePrompt: rg?.positivePrompt === null, canAddNegativePrompt: rg?.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx index b1b813f652..fdb7b33d84 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { brushWidthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { brushWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CA.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CA.tsx index cd636b71ca..f58e37b275 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CA.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CA.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer'; import { CAHeader } from 'features/controlLayers/components/ControlAdapter/CAEntityHeader'; import { CASettings } from 'features/controlLayers/components/ControlAdapter/CASettings'; -import { entitySelected } from 'features/controlLayers/store/controlLayersSlice'; +import { entitySelected } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; type Props = { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAActionsMenu.tsx index 75b9471942..1219a1d226 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAActionsMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAActionsMenu.tsx @@ -8,9 +8,9 @@ import { caMovedForwardOne, caMovedToBack, caMovedToFront, - selectCAOrThrow, - selectControlAdaptersV2Slice, -} from 'features/controlLayers/store/controlAdaptersSlice'; + selectCanvasV2Slice, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -25,20 +25,17 @@ type Props = { id: string; }; -const selectValidActions = createAppSelector( - [selectControlAdaptersV2Slice, (caState, id: string) => id], - (caState, id) => { - const ca = selectCAOrThrow(caState, id); - const caIndex = caState.controlAdapters.indexOf(ca); - const caCount = caState.controlAdapters.length; - return { - canMoveForward: caIndex < caCount - 1, - canMoveBackward: caIndex > 0, - canMoveToFront: caIndex < caCount - 1, - canMoveToBack: caIndex > 0, - }; - } -); +const selectValidActions = createAppSelector([selectCanvasV2Slice, (canvasV2, id: string) => id], (canvasV2, id) => { + const ca = selectCAOrThrow(canvasV2, id); + const caIndex = canvasV2.controlAdapters.indexOf(ca); + const caCount = canvasV2.controlAdapters.length; + return { + canMoveForward: caIndex < caCount - 1, + canMoveBackward: caIndex > 0, + canMoveToFront: caIndex < caCount - 1, + canMoveToBack: caIndex > 0, + }; +}); export const CAActionsMenu = memo(({ id }: Props) => { const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAEntityHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAEntityHeader.tsx index 1aeaacca8f..fe49d9953b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAEntityHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAEntityHeader.tsx @@ -6,7 +6,8 @@ import { CanvasEntityHeader } from 'features/controlLayers/components/common/Can import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; import { CAActionsMenu } from 'features/controlLayers/components/ControlAdapter/CAActionsMenu'; import { CAOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/CAOpacityAndFilter'; -import { caDeleted, caIsEnabledToggled, selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersSlice'; +import { caDeleted, caIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,7 +19,7 @@ type Props = { export const CAHeader = memo(({ id, onToggleVisibility }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isEnabled = useAppSelector((s) => selectCAOrThrow(s.controlAdaptersV2, id).isEnabled); + const isEnabled = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id).isEnabled); const onToggleIsEnabled = useCallback(() => { dispatch(caIsEnabledToggled({ id })); }, [dispatch, id]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAImagePreview.tsx index 34baca75fe..fc574fbb04 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAImagePreview.tsx @@ -3,7 +3,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; -import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { ControlAdapterData } from 'features/controlLayers/store/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAOpacityAndFilter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAOpacityAndFilter.tsx index 7703754044..ce4e918f52 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAOpacityAndFilter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CAOpacityAndFilter.tsx @@ -15,7 +15,7 @@ import { import { useAppDispatch } from 'app/store/storeHooks'; import { stopPropagation } from 'common/util/stopPropagation'; import { useCALayerOpacity } from 'features/controlLayers/hooks/layerStateHooks'; -import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/controlAdaptersSlice'; +import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CASettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CASettings.tsx index 01f7edcc79..292e456584 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CASettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/CASettings.tsx @@ -16,8 +16,8 @@ import { caProcessedImageChanged, caProcessorConfigChanged, caWeightChanged, - selectCAOrThrow, -} from 'features/controlLayers/store/controlAdaptersSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers'; import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/store/types'; import type { CAImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; @@ -40,7 +40,7 @@ export const CASettings = memo(({ id }: Props) => { const { t } = useTranslation(); const [isExpanded, toggleIsExpanded] = useToggle(false); - const controlAdapter = useAppSelector((s) => selectCAOrThrow(s.controlAdaptersV2, id)); + const controlAdapter = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id)); const onChangeBeginEndStepPct = useCallback( (beginEndStepPct: [number, number]) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx index c1308d98ec..c462048335 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx @@ -11,39 +11,22 @@ import { IPA } from 'features/controlLayers/components/IPAdapter/IPA'; import { Layer } from 'features/controlLayers/components/Layer/Layer'; import { RG } from 'features/controlLayers/components/RegionalGuidance/RG'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice'; -import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice'; -import { selectLayersSlice } from 'features/controlLayers/store/layersSlice'; -import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice'; -import { memo, useMemo } from 'react'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; -const selectRGIds = createMemoizedSelector(selectRegionalGuidanceSlice, (rgState) => { - return rgState.regions.map(mapId).reverse(); -}); - -const selectCAIds = createMemoizedSelector(selectControlAdaptersV2Slice, (caState) => { - return caState.controlAdapters.map(mapId).reverse(); -}); - -const selectIPAIds = createMemoizedSelector(selectIPAdaptersSlice, (ipaState) => { - return ipaState.ipAdapters.map(mapId).reverse(); -}); - -const selectLayerIds = createMemoizedSelector(selectLayersSlice, (layersState) => { - return layersState.layers.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2State) => { + const rgIds = canvasV2State.regions.map(mapId).reverse(); + const caIds = canvasV2State.controlAdapters.map(mapId).reverse(); + const ipaIds = canvasV2State.ipAdapters.map(mapId).reverse(); + const layerIds = canvasV2State.layers.map(mapId).reverse(); + const entityCount = rgIds.length + caIds.length + ipaIds.length + layerIds.length; + return { rgIds, caIds, ipaIds, layerIds, entityCount }; }); export const ControlLayersPanelContent = memo(() => { const { t } = useTranslation(); - const rgIds = useAppSelector(selectRGIds); - const caIds = useAppSelector(selectCAIds); - const ipaIds = useAppSelector(selectIPAIds); - const layerIds = useAppSelector(selectLayerIds); - const entityCount = useMemo( - () => rgIds.length + caIds.length + ipaIds.length + layerIds.length, - [rgIds.length, caIds.length, ipaIds.length, layerIds.length] - ); + const { rgIds, caIds, ipaIds, layerIds, entityCount } = useAppSelector(selectEntityIds); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx index f43ffc7725..647a8fba2c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx @@ -1,10 +1,6 @@ import { Button } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { caAllDeleted } from 'features/controlLayers/store/controlAdaptersSlice'; -import { ipaAllDeleted } from 'features/controlLayers/store/ipAdaptersSlice'; -import { layerAllDeleted } from 'features/controlLayers/store/layersSlice'; -import { rgAllDeleted } from 'features/controlLayers/store/regionalGuidanceSlice'; -import { selectEntityCount } from 'features/controlLayers/store/selectors'; +import { allEntitiesDeleted } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; @@ -12,12 +8,16 @@ import { PiTrashSimpleBold } from 'react-icons/pi'; export const DeleteAllLayersButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const entityCount = useAppSelector(selectEntityCount); + const entityCount = useAppSelector((s) => { + return ( + s.canvasV2.regions.length + + s.canvasV2.controlAdapters.length + + s.canvasV2.ipAdapters.length + + s.canvasV2.layers.length + ); + }); const onClick = useCallback(() => { - dispatch(caAllDeleted()); - dispatch(rgAllDeleted()); - dispatch(ipaAllDeleted()); - dispatch(layerAllDeleted()); + dispatch(allEntitiesDeleted()); }, [dispatch]); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx b/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx index d976fa8470..0903763f2d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { eraserWidthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { eraserWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/FillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/FillColorPicker.tsx index 3550f67f13..3d71523c62 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/FillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/FillColorPicker.tsx @@ -2,7 +2,7 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@inv import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIColorPicker from 'common/components/IAIColorPicker'; import { rgbaColorToString } from 'common/util/colorCodeTransformers'; -import { fillChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { fillChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { RgbaColor } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx index 602b29914d..f5ab9d1ba5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Text } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; -import { $stageAttrs } from 'features/controlLayers/store/controlLayersSlice'; +import { $stageAttrs } from 'features/controlLayers/store/canvasV2Slice'; import { round } from 'lodash-es'; import { memo } from 'react'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPA.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPA.tsx index f94afe4da3..69e88c9d8c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPA.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPA.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer'; import { IPAHeader } from 'features/controlLayers/components/IPAdapter/IPAHeader'; import { IPASettings } from 'features/controlLayers/components/IPAdapter/IPASettings'; -import { entitySelected } from 'features/controlLayers/store/controlLayersSlice'; +import { entitySelected } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; type Props = { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAHeader.tsx index 9604a3283f..ec56c81b91 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAHeader.tsx @@ -4,7 +4,8 @@ import { CanvasEntityDeleteButton } from 'features/controlLayers/components/comm import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; -import { ipaDeleted, ipaIsEnabledToggled, selectIPAOrThrow } from 'features/controlLayers/store/ipAdaptersSlice'; +import { ipaDeleted, ipaIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { selectIPAOrThrow } from 'features/controlLayers/store/ipAdaptersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -16,7 +17,7 @@ type Props = { export const IPAHeader = memo(({ id, onToggleVisibility }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isEnabled = useAppSelector((s) => selectIPAOrThrow(s.ipAdapters, id).isEnabled); + const isEnabled = useAppSelector((s) => selectIPAOrThrow(s.canvasV2, id).isEnabled); const onToggleIsEnabled = useCallback(() => { dispatch(ipaIsEnabledToggled({ id })); }, [dispatch, id]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAImagePreview.tsx index 47fd0f5c92..71018b5cc6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAImagePreview.tsx @@ -3,7 +3,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; -import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { ImageWithDims } from 'features/controlLayers/store/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPASettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPASettings.tsx index dfe26b41ed..f8911818ee 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPASettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPASettings.tsx @@ -11,8 +11,8 @@ import { ipaMethodChanged, ipaModelChanged, ipaWeightChanged, - selectIPAOrThrow, -} from 'features/controlLayers/store/ipAdaptersSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectIPAOrThrow } from 'features/controlLayers/store/ipAdaptersReducers'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { IPAImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; @@ -27,7 +27,7 @@ type Props = { export const IPASettings = memo(({ id }: Props) => { const dispatch = useAppDispatch(); - const ipAdapter = useAppSelector((s) => selectIPAOrThrow(s.ipAdapters, id)); + const ipAdapter = useAppSelector((s) => selectIPAOrThrow(s.canvasV2, id)); const onChangeBeginEndStepPct = useCallback( (beginEndStepPct: [number, number]) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx index 06235f59de..e49d76ebbe 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer'; import { LayerHeader } from 'features/controlLayers/components/Layer/LayerHeader'; import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings'; -import { entitySelected } from 'features/controlLayers/store/controlLayersSlice'; +import { entitySelected } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; type Props = { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerActionsMenu.tsx index 03b4e8953d..a9ebc599be 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerActionsMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerActionsMenu.tsx @@ -8,9 +8,9 @@ import { layerMovedForwardOne, layerMovedToBack, layerMovedToFront, - selectLayerOrThrow, - selectLayersSlice, -} from 'features/controlLayers/store/layersSlice'; + selectCanvasV2Slice, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -25,20 +25,17 @@ type Props = { id: string; }; -const selectValidActions = createAppSelector( - [selectLayersSlice, (layersState, id: string) => id], - (layersState, id) => { - const layer = selectLayerOrThrow(layersState, id); - const layerIndex = layersState.layers.indexOf(layer); - const layerCount = layersState.layers.length; - return { - canMoveForward: layerIndex < layerCount - 1, - canMoveBackward: layerIndex > 0, - canMoveToFront: layerIndex < layerCount - 1, - canMoveToBack: layerIndex > 0, - }; - } -); +const selectValidActions = createAppSelector([selectCanvasV2Slice, (canvasV2, id: string) => id], (canvasV2, id) => { + const layer = selectLayerOrThrow(canvasV2, id); + const layerIndex = canvasV2.layers.indexOf(layer); + const layerCount = canvasV2.layers.length; + return { + canMoveForward: layerIndex < layerCount - 1, + canMoveBackward: layerIndex > 0, + canMoveToFront: layerIndex < layerCount - 1, + canMoveToBack: layerIndex > 0, + }; +}); export const LayerActionsMenu = memo(({ id }: Props) => { const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerHeader.tsx index cd8bc2d5f9..c1f5606eba 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerHeader.tsx @@ -5,7 +5,8 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerActionsMenu'; -import { layerDeleted, layerIsEnabledToggled, selectLayerOrThrow } from 'features/controlLayers/store/layersSlice'; +import { layerDeleted, layerIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -19,7 +20,7 @@ type Props = { export const LayerHeader = memo(({ id, onToggleVisibility }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isEnabled = useAppSelector((s) => selectLayerOrThrow(s.layers, id).isEnabled); + const isEnabled = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, id).isEnabled); const onToggleIsEnabled = useCallback(() => { dispatch(layerIsEnabledToggled({ id })); }, [dispatch, id]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx index 00de65b713..da1582310e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx @@ -13,7 +13,8 @@ import { } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { stopPropagation } from 'common/util/stopPropagation'; -import { layerOpacityChanged, selectLayerOrThrow } from 'features/controlLayers/store/layersSlice'; +import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiDropHalfFill } from 'react-icons/pi'; @@ -28,7 +29,7 @@ const formatPct = (v: number | string) => `${v} %`; export const LayerOpacity = memo(({ id }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.layers, id).opacity * 100)); + const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, id).opacity * 100)); const onChangeOpacity = useCallback( (v: number) => { dispatch(layerOpacityChanged({ id, opacity: v / 100 })); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGGlobalOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGGlobalOpacity.tsx index db56cdd558..e20026f3dd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGGlobalOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGGlobalOpacity.tsx @@ -1,6 +1,6 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { rgGlobalOpacityChanged } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgGlobalOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -10,7 +10,7 @@ const formatPct = (v: number | string) => `${v} %`; export const RGGlobalOpacity = memo(() => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const opacity = useAppSelector((s) => Math.round(s.regionalGuidance.opacity * 100)); + const opacity = useAppSelector((s) => Math.round(s.canvasV2.maskFillOpacity * 100)); const onChange = useCallback( (v: number) => { dispatch(rgGlobalOpacityChanged({ opacity: v / 100 })); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RG.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RG.tsx index 6c503f98e1..26b5cf624c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RG.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RG.tsx @@ -4,8 +4,8 @@ import { rgbColorToString } from 'features/canvas/util/colorToString'; import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer'; import { RGHeader } from 'features/controlLayers/components/RegionalGuidance/RGHeader'; import { RGSettings } from 'features/controlLayers/components/RegionalGuidance/RGSettings'; -import { entitySelected } from 'features/controlLayers/store/controlLayersSlice'; -import { selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { entitySelected } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo, useCallback } from 'react'; type Props = { @@ -14,7 +14,7 @@ type Props = { export const RG = memo(({ id }: Props) => { const dispatch = useAppDispatch(); - const selectedBorderColor = useAppSelector((s) => rgbColorToString(selectRGOrThrow(s.regionalGuidance, id).fill)); + const selectedBorderColor = useAppSelector((s) => rgbColorToString(selectRGOrThrow(s.canvasV2, id).fill)); const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id); const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); const onSelect = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGActionsMenu.tsx index db1cb078bc..94107c9c0f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGActionsMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGActionsMenu.tsx @@ -12,9 +12,9 @@ import { rgNegativePromptChanged, rgPositivePromptChanged, rgReset, - selectRegionalGuidanceSlice, - selectRGOrThrow, -} from 'features/controlLayers/store/regionalGuidanceSlice'; + selectCanvasV2Slice, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -32,11 +32,11 @@ type Props = { }; const selectActionsValidity = createMemoizedAppSelector( - [selectRegionalGuidanceSlice, (rgState, id: string) => id], - (rgState, id) => { - const rg = selectRGOrThrow(rgState, id); - const rgIndex = rgState.regions.indexOf(rg); - const rgCount = rgState.regions.length; + [selectCanvasV2Slice, (canvasV2, id: string) => id], + (canvasV2, id) => { + const rg = selectRGOrThrow(canvasV2, id); + const rgIndex = canvasV2.regions.indexOf(rg); + const rgCount = canvasV2.regions.length; return { isMoveForwardOneDisabled: rgIndex < rgCount - 1, isMoveBackardOneDisabled: rgIndex > 0, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGHeader.tsx index 50f854e856..ec4cc36b66 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGHeader.tsx @@ -5,7 +5,8 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; import { RGActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RGActionsMenu'; -import { rgDeleted, rgIsEnabledToggled, selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgDeleted, rgIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,8 +21,8 @@ type Props = { export const RGHeader = memo(({ id, onToggleVisibility }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isEnabled = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).isEnabled); - const autoNegative = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).autoNegative); + const isEnabled = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).isEnabled); + const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative); const onToggleIsEnabled = useCallback(() => { dispatch(rgIsEnabledToggled({ id })); }, [dispatch, id]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapterSettings.tsx index 678b70ee90..6e35a910b7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapterSettings.tsx @@ -13,8 +13,8 @@ import { rgIPAdapterMethodChanged, rgIPAdapterModelChanged, rgIPAdapterWeightChanged, - selectRGOrThrow, -} from 'features/controlLayers/store/regionalGuidanceSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { RGIPAdapterImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; @@ -34,7 +34,7 @@ export const RGIPAdapterSettings = memo(({ id, ipAdapterId, ipAdapterNumber }: P dispatch(rgIPAdapterDeleted({ id, ipAdapterId })); }, [dispatch, ipAdapterId, id]); const ipAdapter = useAppSelector((s) => { - const ipa = selectRGOrThrow(s.regionalGuidance, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId); + const ipa = selectRGOrThrow(s.canvasV2, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId); assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); return ipa; }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapters.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapters.tsx index 5d787ffdda..a6df88f1bc 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapters.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGIPAdapters.tsx @@ -1,7 +1,7 @@ import { Divider, Flex } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { RGIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RGIPAdapterSettings'; -import { selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo } from 'react'; type Props = { @@ -9,7 +9,7 @@ type Props = { }; export const RGIPAdapters = memo(({ id }: Props) => { - const ipAdapterIds = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).ipAdapters.map(({ id }) => id)); + const ipAdapterIds = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.map(({ id }) => id)); if (ipAdapterIds.length === 0) { return null; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGMaskFillColorPicker.tsx index e325b6d965..24df9dc558 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGMaskFillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGMaskFillColorPicker.tsx @@ -3,7 +3,8 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { stopPropagation } from 'common/util/stopPropagation'; import { rgbColorToString } from 'features/canvas/util/colorToString'; -import { rgFillChanged, selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo, useCallback } from 'react'; import type { RgbColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; @@ -15,7 +16,7 @@ type Props = { export const RGMaskFillColorPicker = memo(({ id }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const fill = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).fill); + const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).fill); const onChange = useCallback( (fill: RgbColor) => { dispatch(rgFillChanged({ id, fill })); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGNegativePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGNegativePrompt.tsx index e42f2728aa..db879be4f0 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGNegativePrompt.tsx @@ -1,7 +1,8 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { RGDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RGDeletePromptButton'; -import { rgNegativePromptChanged, selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; @@ -14,7 +15,7 @@ type Props = { }; export const RGNegativePrompt = memo(({ id }: Props) => { - const prompt = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).negativePrompt ?? ''); + const prompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt ?? ''); const dispatch = useAppDispatch(); const textareaRef = useRef(null); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGPositivePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGPositivePrompt.tsx index 0bc82526b2..49bca080da 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGPositivePrompt.tsx @@ -1,7 +1,8 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { RGDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RGDeletePromptButton'; -import { rgPositivePromptChanged, selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; @@ -14,7 +15,7 @@ type Props = { }; export const RGPositivePrompt = memo(({ id }: Props) => { - const prompt = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).positivePrompt ?? ''); + const prompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt ?? ''); const dispatch = useAppDispatch(); const textareaRef = useRef(null); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettings.tsx index c626facd01..7ba5ec84a3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettings.tsx @@ -1,7 +1,7 @@ import { useAppSelector } from 'app/store/storeHooks'; import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons'; import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings'; -import { selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import { memo } from 'react'; import { RGIPAdapters } from './RGIPAdapters'; @@ -13,9 +13,9 @@ type Props = { }; export const RGSettings = memo(({ id }: Props) => { - const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).positivePrompt !== null); - const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).negativePrompt !== null); - const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).ipAdapters.length > 0); + const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt !== null); + const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt !== null); + const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.length > 0); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettingsPopover.tsx index a74f0039fa..cf7c157460 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RGSettingsPopover.tsx @@ -12,7 +12,8 @@ import { } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { stopPropagation } from 'common/util/stopPropagation'; -import { rgAutoNegativeChanged, selectRGOrThrow } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -25,7 +26,7 @@ type Props = { export const RGSettingsPopover = memo(({ id }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const autoNegative = useAppSelector((s) => selectRGOrThrow(s.regionalGuidance, id).autoNegative); + const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative); const onChange = useCallback( (e: ChangeEvent) => { dispatch(rgAutoNegativeChanged({ id, autoNegative: e.target.checked ? 'invert' : 'off' })); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index a0b65d3a4e..3dd7b1df35 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -1,14 +1,11 @@ import { $alt, $ctrl, $meta, $shift, Box, Flex, Heading } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; -import { createSelector } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay'; import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants'; import { setStageEventHandlers } from 'features/controlLayers/konva/events'; import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/konva/renderers/layers'; -import { caBboxChanged, caTranslated } from 'features/controlLayers/store/controlAdaptersSlice'; import { $bbox, $currentFill, @@ -23,29 +20,26 @@ import { $toolState, bboxChanged, brushWidthChanged, + caBboxChanged, + caTranslated, eraserWidthChanged, - selectCanvasV2Slice, - toolBufferChanged, - toolChanged, -} from 'features/controlLayers/store/controlLayersSlice'; -import { layerBboxChanged, layerBrushLineAdded, layerEraserLineAdded, layerLinePointAdded, layerRectAdded, layerTranslated, - selectLayersSlice, -} from 'features/controlLayers/store/layersSlice'; -import { rgBboxChanged, rgBrushLineAdded, rgEraserLineAdded, rgLinePointAdded, rgRectAdded, rgTranslated, - selectRegionalGuidanceSlice, -} from 'features/controlLayers/store/regionalGuidanceSlice'; + selectCanvasV2Slice, + toolBufferChanged, + toolChanged, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectEntityCount } from 'features/controlLayers/store/selectors'; import type { BboxChangedArg, BrushLineAddedArg, @@ -69,62 +63,42 @@ Konva.showWarnings = false; const log = logger('controlLayers'); -const selectBrushFill = createSelector( - selectCanvasV2Slice, - selectLayersSlice, - selectRegionalGuidanceSlice, - (canvas, layers, regionalGuidance) => { - const rg = regionalGuidance.regions.find((i) => i.id === canvas.selectedEntityIdentifier?.id); - - if (rg) { - return rgbaColorToString({ ...rg.fill, a: regionalGuidance.opacity }); - } - - return rgbaColorToString(canvas.tool.fill); - } -); - const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, asPreview: boolean) => { const dispatch = useAppDispatch(); const canvasV2State = useAppSelector(selectCanvasV2Slice); - const layersState = useAppSelector((s) => s.layers); - const controlAdaptersState = useAppSelector((s) => s.controlAdaptersV2); - const ipAdaptersState = useAppSelector((s) => s.ipAdapters); - const regionalGuidanceState = useAppSelector((s) => s.regionalGuidance); const lastCursorPos = useStore($lastCursorPos); const lastMouseDownPos = useStore($lastMouseDownPos); const isMouseDown = useStore($isMouseDown); const isDrawing = useStore($isDrawing); - const brushColor = useAppSelector(selectBrushFill); const selectedEntity = useMemo(() => { const identifier = canvasV2State.selectedEntityIdentifier; if (!identifier) { return null; } else if (identifier.type === 'layer') { - return layersState.layers.find((i) => i.id === identifier.id) ?? null; + return canvasV2State.layers.find((i) => i.id === identifier.id) ?? null; } else if (identifier.type === 'control_adapter') { - return controlAdaptersState.controlAdapters.find((i) => i.id === identifier.id) ?? null; + return canvasV2State.controlAdapters.find((i) => i.id === identifier.id) ?? null; } else if (identifier.type === 'ip_adapter') { - return ipAdaptersState.ipAdapters.find((i) => i.id === identifier.id) ?? null; + return canvasV2State.ipAdapters.find((i) => i.id === identifier.id) ?? null; } else if (identifier.type === 'regional_guidance') { - return regionalGuidanceState.regions.find((i) => i.id === identifier.id) ?? null; + return canvasV2State.regions.find((i) => i.id === identifier.id) ?? null; } else { return null; } }, [ + canvasV2State.controlAdapters, + canvasV2State.ipAdapters, + canvasV2State.layers, + canvasV2State.regions, canvasV2State.selectedEntityIdentifier, - controlAdaptersState.controlAdapters, - ipAdaptersState.ipAdapters, - layersState.layers, - regionalGuidanceState.regions, ]); const currentFill = useMemo(() => { if (selectedEntity && selectedEntity.type === 'regional_guidance') { - return { ...selectedEntity.fill, a: regionalGuidanceState.opacity }; + return { ...selectedEntity.fill, a: canvasV2State.maskFillOpacity }; } return canvasV2State.tool.fill; - }, [canvasV2State.tool.fill, regionalGuidanceState.opacity, selectedEntity]); + }, [canvasV2State.maskFillOpacity, canvasV2State.tool.fill, selectedEntity]); const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]); const dpr = useDevicePixelRatio({ round: false }); @@ -341,7 +315,6 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, ); }, [ asPreview, - brushColor, canvasV2State.tool, currentFill, isDrawing, @@ -376,10 +349,10 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, log.trace('Rendering layers'); renderers.renderLayers( stage, - layersState.layers, - controlAdaptersState.controlAdapters, - regionalGuidanceState.regions, - regionalGuidanceState.opacity, + canvasV2State.layers, + canvasV2State.controlAdapters, + canvasV2State.regions, + canvasV2State.maskFillOpacity, canvasV2State.tool.selected, selectedEntity, getImageDTO, @@ -388,13 +361,13 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, }, [ stage, renderers, - layersState.layers, - controlAdaptersState.controlAdapters, - regionalGuidanceState.regions, - regionalGuidanceState.opacity, onPosChanged, canvasV2State.tool.selected, selectedEntity, + canvasV2State.layers, + canvasV2State.controlAdapters, + canvasV2State.regions, + canvasV2State.maskFillOpacity, ]); // useLayoutEffect(() => { @@ -450,7 +423,7 @@ export const StageComponent = memo(({ asPreview = false }: Props) => { backgroundRepeat="repeat" opacity={0.2} /> - {!asPreview && } + {!asPreview && } { StageComponent.displayName = 'StageComponent'; -const NoLayersFallback = () => { +const NoEntitiesFallback = () => { const { t } = useTranslation(); - const layerCount = useAppSelector((s) => s.layers.layers.length); - if (layerCount) { + const entityCount = useAppSelector(selectEntityCount); + + if (entityCount) { return null; } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx index 6434ef402d..863a56c1bd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx @@ -1,11 +1,16 @@ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { caDeleted } from 'features/controlLayers/store/controlAdaptersSlice'; -import { selectCanvasV2Slice, toolChanged } from 'features/controlLayers/store/controlLayersSlice'; -import { ipaDeleted } from 'features/controlLayers/store/ipAdaptersSlice'; -import { layerDeleted, layerReset } from 'features/controlLayers/store/layersSlice'; -import { rgDeleted, rgReset } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { + caDeleted, + ipaDeleted, + layerDeleted, + layerReset, + rgDeleted, + rgReset, + selectCanvasV2Slice, + toolChanged, +} from 'features/controlLayers/store/canvasV2Slice'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx index 8babae7fcc..eae2bc65cf 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx @@ -1,7 +1,7 @@ /* eslint-disable i18next/no-literal-string */ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { redo, undo } from 'features/controlLayers/store/controlLayersSlice'; +import { redo, undo } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts index c52944b88c..8d3def70b1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts @@ -1,7 +1,5 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { caAdded } from 'features/controlLayers/store/controlAdaptersSlice'; -import { ipaAdded } from 'features/controlLayers/store/ipAdaptersSlice'; -import { rgIPAdapterAdded } from 'features/controlLayers/store/regionalGuidanceSlice'; +import { caAdded, ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice'; import { buildControlNet, buildIPAdapter, diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts index 03f47bcdda..28f6af7ea4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { isControlAdapterLayer, isRegionalGuidanceLayer } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts similarity index 72% rename from invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts rename to invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 0dd87536c7..206ac47da6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -3,6 +3,10 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; import { deepClone } from 'common/util/deepClone'; import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; +import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers'; +import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers'; +import { layersReducers } from 'features/controlLayers/store/layersReducers'; +import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; @@ -47,12 +51,21 @@ const initialState: CanvasV2State = { width: 512, height: 512, }, + controlAdapters: [], + ipAdapters: [], + regions: [], + layers: [], + maskFillOpacity: 0.3, }; export const canvasV2Slice = createSlice({ name: 'canvasV2', initialState, reducers: { + ...layersReducers, + ...ipAdaptersReducers, + ...controlAdaptersReducers, + ...regionsReducers, positivePromptChanged: (state, action: PayloadAction) => { state.prompts.positivePrompt = action.payload; }, @@ -110,9 +123,18 @@ export const canvasV2Slice = createSlice({ toolBufferChanged: (state, action: PayloadAction) => { state.tool.selectedBuffer = action.payload; }, + maskFillOpacityChanged: (state, action: PayloadAction) => { + state.maskFillOpacity = action.payload; + }, entitySelected: (state, action: PayloadAction) => { state.selectedEntityIdentifier = action.payload; }, + allEntitiesDeleted: (state) => { + state.regions = []; + state.layers = []; + state.ipAdapters = []; + state.controlAdapters = []; + }, }, extraReducers(builder) { builder.addCase(modelChanged, (state, action) => { @@ -148,7 +170,88 @@ export const { invertScrollChanged, toolChanged, toolBufferChanged, + maskFillOpacityChanged, entitySelected, + allEntitiesDeleted, + // layers + layerAdded, + layerDeleted, + layerReset, + layerMovedForwardOne, + layerMovedToFront, + layerMovedBackwardOne, + layerMovedToBack, + layerIsEnabledToggled, + layerOpacityChanged, + layerTranslated, + layerBboxChanged, + layerBrushLineAdded, + layerEraserLineAdded, + layerLinePointAdded, + layerRectAdded, + layerImageAdded, + // IP Adapters + ipaAdded, + ipaRecalled, + ipaIsEnabledToggled, + ipaDeleted, + ipaImageChanged, + ipaMethodChanged, + ipaModelChanged, + ipaCLIPVisionModelChanged, + ipaWeightChanged, + ipaBeginEndStepPctChanged, + // Control Adapters + caAdded, + caBboxChanged, + caDeleted, + caIsEnabledToggled, + caMovedBackwardOne, + caMovedForwardOne, + caMovedToBack, + caMovedToFront, + caOpacityChanged, + caTranslated, + caRecalled, + caImageChanged, + caProcessedImageChanged, + caModelChanged, + caControlModeChanged, + caProcessorConfigChanged, + caFilterChanged, + caProcessorPendingBatchIdChanged, + caWeightChanged, + caBeginEndStepPctChanged, + // Regions + rgAdded, + rgRecalled, + rgReset, + rgIsEnabledToggled, + rgTranslated, + rgBboxChanged, + rgDeleted, + rgGlobalOpacityChanged, + rgMovedForwardOne, + rgMovedToFront, + rgMovedBackwardOne, + rgMovedToBack, + rgPositivePromptChanged, + rgNegativePromptChanged, + rgFillChanged, + rgMaskImageUploaded, + rgAutoNegativeChanged, + rgIPAdapterAdded, + rgIPAdapterDeleted, + rgIPAdapterImageChanged, + rgIPAdapterWeightChanged, + rgIPAdapterBeginEndStepPctChanged, + rgIPAdapterMethodChanged, + rgIPAdapterModelChanged, + rgIPAdapterCLIPVisionModelChanged, + rgBrushLineAdded, + rgEraserLineAdded, + rgLinePointAdded, + rgRectAdded, } = canvasV2Slice.actions; export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts new file mode 100644 index 0000000000..6cda64871f --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts @@ -0,0 +1,238 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; +import { zModelIdentifierField } from 'features/nodes/types/common'; +import type { IRect } from 'konva/lib/types'; +import { isEqual } from 'lodash-es'; +import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types'; +import { assert } from 'tsafe'; +import { v4 as uuidv4 } from 'uuid'; + +import type { + CanvasV2State, + ControlAdapterConfig, + ControlAdapterData, + ControlModeV2, + Filter, + ProcessorConfig, +} from './types'; +import { buildControlAdapterProcessorV2, imageDTOToImageWithDims } from './types'; + +export const selectCA = (state: CanvasV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id); +export const selectCAOrThrow = (state: CanvasV2State, id: string) => { + const ca = selectCA(state, id); + assert(ca, `Control Adapter with id ${id} not found`); + return ca; +}; + +export const controlAdaptersReducers = { + caAdded: { + reducer: (state, action: PayloadAction<{ id: string; config: ControlAdapterConfig }>) => { + const { id, config } = action.payload; + state.controlAdapters.push({ + id, + type: 'control_adapter', + x: 0, + y: 0, + bbox: null, + bboxNeedsUpdate: false, + isEnabled: true, + opacity: 1, + filter: 'LightnessToAlphaFilter', + processorPendingBatchId: null, + ...config, + }); + }, + prepare: (config: ControlAdapterConfig) => ({ + payload: { id: uuidv4(), config }, + }), + }, + caRecalled: (state, action: PayloadAction<{ data: ControlAdapterData }>) => { + state.controlAdapters.push(action.payload.data); + }, + caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.isEnabled = !ca.isEnabled; + }, + caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { + const { id, x, y } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.x = x; + ca.y = y; + }, + caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { + const { id, bbox } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.bbox = bbox; + ca.bboxNeedsUpdate = false; + }, + caDeleted: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + state.controlAdapters = state.controlAdapters.filter((ca) => ca.id !== id); + }, + caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { + const { id, opacity } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.opacity = opacity; + }, + caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + moveOneToEnd(state.controlAdapters, ca); + }, + caMovedToFront: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + moveToEnd(state.controlAdapters, ca); + }, + caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + moveOneToStart(state.controlAdapters, ca); + }, + caMovedToBack: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + moveToStart(state.controlAdapters, ca); + }, + caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { + const { id, imageDTO } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.bbox = null; + ca.bboxNeedsUpdate = true; + ca.isEnabled = true; + if (imageDTO) { + const newImage = imageDTOToImageWithDims(imageDTO); + if (isEqual(newImage, ca.image)) { + return; + } + ca.image = newImage; + ca.processedImage = null; + } else { + ca.image = null; + ca.processedImage = null; + } + }, + caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { + const { id, imageDTO } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.bbox = null; + ca.bboxNeedsUpdate = true; + ca.isEnabled = true; + ca.processedImage = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; + }, + caModelChanged: ( + state, + action: PayloadAction<{ + id: string; + modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null; + }> + ) => { + const { id, modelConfig } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + if (!modelConfig) { + ca.model = null; + return; + } + ca.model = zModelIdentifierField.parse(modelConfig); + + // We may need to convert the CA to match the model + if (!ca.controlMode && ca.model.type === 'controlnet') { + ca.controlMode = 'balanced'; + } else if (ca.controlMode && ca.model.type === 't2i_adapter') { + ca.controlMode = null; + } + + const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig); + if (candidateProcessorConfig?.type !== ca.processorConfig?.type) { + // The processor has changed. For example, the previous model was a Canny model and the new model is a Depth + // model. We need to use the new processor. + ca.processedImage = null; + ca.processorConfig = candidateProcessorConfig; + } + }, + caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => { + const { id, controlMode } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.controlMode = controlMode; + }, + caProcessorConfigChanged: (state, action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }>) => { + const { id, processorConfig } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.processorConfig = processorConfig; + if (!processorConfig) { + ca.processedImage = null; + } + }, + caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => { + const { id, filter } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.filter = filter; + }, + caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => { + const { id, batchId } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.processorPendingBatchId = batchId; + }, + caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { + const { id, weight } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.weight = weight; + }, + caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { + const { id, beginEndStepPct } = action.payload; + const ca = selectCA(state, id); + if (!ca) { + return; + } + ca.beginEndStepPct = beginEndStepPct; + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts deleted file mode 100644 index 07438575e2..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts +++ /dev/null @@ -1,291 +0,0 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import type { PersistConfig, RootState } from 'app/store/store'; -import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; -import { zModelIdentifierField } from 'features/nodes/types/common'; -import type { IRect } from 'konva/lib/types'; -import { isEqual } from 'lodash-es'; -import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; -import { v4 as uuidv4 } from 'uuid'; - -import type { ControlAdapterConfig, ControlAdapterData, ControlModeV2, Filter, ProcessorConfig } from './types'; -import { buildControlAdapterProcessorV2, imageDTOToImageWithDims } from './types'; - -type ControlAdaptersV2State = { - _version: 1; - controlAdapters: ControlAdapterData[]; -}; - -const initialState: ControlAdaptersV2State = { - _version: 1, - controlAdapters: [], -}; - -export const selectCA = (state: ControlAdaptersV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id); -export const selectCAOrThrow = (state: ControlAdaptersV2State, id: string) => { - const ca = selectCA(state, id); - assert(ca, `Control Adapter with id ${id} not found`); - return ca; -}; - -export const controlAdaptersV2Slice = createSlice({ - name: 'controlAdaptersV2', - initialState, - reducers: { - caAdded: { - reducer: (state, action: PayloadAction<{ id: string; config: ControlAdapterConfig }>) => { - const { id, config } = action.payload; - state.controlAdapters.push({ - id, - type: 'control_adapter', - x: 0, - y: 0, - bbox: null, - bboxNeedsUpdate: false, - isEnabled: true, - opacity: 1, - filter: 'LightnessToAlphaFilter', - processorPendingBatchId: null, - ...config, - }); - }, - prepare: (config: ControlAdapterConfig) => ({ - payload: { id: uuidv4(), config }, - }), - }, - caRecalled: (state, action: PayloadAction<{ data: ControlAdapterData }>) => { - state.controlAdapters.push(action.payload.data); - }, - caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.isEnabled = !ca.isEnabled; - }, - caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { - const { id, x, y } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.x = x; - ca.y = y; - }, - caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { - const { id, bbox } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.bbox = bbox; - ca.bboxNeedsUpdate = false; - }, - caDeleted: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - state.controlAdapters = state.controlAdapters.filter((ca) => ca.id !== id); - }, - caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { - const { id, opacity } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.opacity = opacity; - }, - caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - moveOneToEnd(state.controlAdapters, ca); - }, - caMovedToFront: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - moveToEnd(state.controlAdapters, ca); - }, - caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - moveOneToStart(state.controlAdapters, ca); - }, - caMovedToBack: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - moveToStart(state.controlAdapters, ca); - }, - caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { - const { id, imageDTO } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.bbox = null; - ca.bboxNeedsUpdate = true; - ca.isEnabled = true; - if (imageDTO) { - const newImage = imageDTOToImageWithDims(imageDTO); - if (isEqual(newImage, ca.image)) { - return; - } - ca.image = newImage; - ca.processedImage = null; - } else { - ca.image = null; - ca.processedImage = null; - } - }, - caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { - const { id, imageDTO } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.bbox = null; - ca.bboxNeedsUpdate = true; - ca.isEnabled = true; - ca.processedImage = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; - }, - caModelChanged: ( - state, - action: PayloadAction<{ - id: string; - modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null; - }> - ) => { - const { id, modelConfig } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - if (!modelConfig) { - ca.model = null; - return; - } - ca.model = zModelIdentifierField.parse(modelConfig); - - // We may need to convert the CA to match the model - if (!ca.controlMode && ca.model.type === 'controlnet') { - ca.controlMode = 'balanced'; - } else if (ca.controlMode && ca.model.type === 't2i_adapter') { - ca.controlMode = null; - } - - const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig); - if (candidateProcessorConfig?.type !== ca.processorConfig?.type) { - // The processor has changed. For example, the previous model was a Canny model and the new model is a Depth - // model. We need to use the new processor. - ca.processedImage = null; - ca.processorConfig = candidateProcessorConfig; - } - }, - caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => { - const { id, controlMode } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.controlMode = controlMode; - }, - caProcessorConfigChanged: ( - state, - action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }> - ) => { - const { id, processorConfig } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.processorConfig = processorConfig; - if (!processorConfig) { - ca.processedImage = null; - } - }, - caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => { - const { id, filter } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.filter = filter; - }, - caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => { - const { id, batchId } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.processorPendingBatchId = batchId; - }, - caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { - const { id, weight } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.weight = weight; - }, - caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { - const { id, beginEndStepPct } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.beginEndStepPct = beginEndStepPct; - }, - caAllDeleted: (state) => { - state.controlAdapters = []; - }, - }, -}); - -export const { - caAdded, - caBboxChanged, - caDeleted, - caIsEnabledToggled, - caMovedBackwardOne, - caMovedForwardOne, - caMovedToBack, - caMovedToFront, - caOpacityChanged, - caTranslated, - caRecalled, - caImageChanged, - caProcessedImageChanged, - caModelChanged, - caControlModeChanged, - caProcessorConfigChanged, - caFilterChanged, - caProcessorPendingBatchIdChanged, - caWeightChanged, - caBeginEndStepPctChanged, - caAllDeleted, -} = controlAdaptersV2Slice.actions; - -export const selectControlAdaptersV2Slice = (state: RootState) => state.controlAdaptersV2; - -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const migrate = (state: any): any => { - return state; -}; - -export const controlAdaptersV2PersistConfig: PersistConfig = { - name: controlAdaptersV2Slice.name, - initialState, - migrate, - persistDenylist: [], -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts new file mode 100644 index 0000000000..16cf222b60 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts @@ -0,0 +1,102 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { zModelIdentifierField } from 'features/nodes/types/common'; +import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; +import { assert } from 'tsafe'; +import { v4 as uuidv4 } from 'uuid'; + +import type { CanvasV2State, CLIPVisionModelV2, IPAdapterConfig, IPAdapterData, IPMethodV2 } from './types'; +import { imageDTOToImageWithDims } from './types'; + +export const selectIPA = (state: CanvasV2State, id: string) => state.ipAdapters.find((ipa) => ipa.id === id); +export const selectIPAOrThrow = (state: CanvasV2State, id: string) => { + const ipa = selectIPA(state, id); + assert(ipa, `IP Adapter with id ${id} not found`); + return ipa; +}; + +export const ipAdaptersReducers = { + ipaAdded: { + reducer: (state, action: PayloadAction<{ id: string; config: IPAdapterConfig }>) => { + const { id, config } = action.payload; + const layer: IPAdapterData = { + id, + type: 'ip_adapter', + isEnabled: true, + ...config, + }; + state.ipAdapters.push(layer); + }, + prepare: (config: IPAdapterConfig) => ({ payload: { id: uuidv4(), config } }), + }, + ipaRecalled: (state, action: PayloadAction<{ data: IPAdapterData }>) => { + state.ipAdapters.push(action.payload.data); + }, + ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const ipa = selectIPA(state, id); + if (ipa) { + ipa.isEnabled = !ipa.isEnabled; + } + }, + ipaDeleted: (state, action: PayloadAction<{ id: string }>) => { + state.ipAdapters = state.ipAdapters.filter((ipa) => ipa.id !== action.payload.id); + }, + ipaImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { + const { id, imageDTO } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; + }, + ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => { + const { id, method } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + ipa.method = method; + }, + ipaModelChanged: ( + state, + action: PayloadAction<{ + id: string; + modelConfig: IPAdapterModelConfig | null; + }> + ) => { + const { id, modelConfig } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + if (modelConfig) { + ipa.model = zModelIdentifierField.parse(modelConfig); + } else { + ipa.model = null; + } + }, + ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => { + const { id, clipVisionModel } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + ipa.clipVisionModel = clipVisionModel; + }, + ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { + const { id, weight } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + ipa.weight = weight; + }, + ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { + const { id, beginEndStepPct } = action.payload; + const ipa = selectIPA(state, id); + if (!ipa) { + return; + } + ipa.beginEndStepPct = beginEndStepPct; + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts deleted file mode 100644 index cdde6078c8..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import type { PersistConfig, RootState } from 'app/store/store'; -import { zModelIdentifierField } from 'features/nodes/types/common'; -import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; -import { v4 as uuidv4 } from 'uuid'; - -import type { CLIPVisionModelV2, IPAdapterConfig, IPAdapterData, IPMethodV2 } from './types'; -import { imageDTOToImageWithDims } from './types'; - -type IPAdaptersState = { - _version: 1; - ipAdapters: IPAdapterData[]; -}; - -const initialState: IPAdaptersState = { - _version: 1, - ipAdapters: [], -}; - -export const selectIPA = (state: IPAdaptersState, id: string) => state.ipAdapters.find((ipa) => ipa.id === id); -export const selectIPAOrThrow = (state: IPAdaptersState, id: string) => { - const ipa = selectIPA(state, id); - assert(ipa, `IP Adapter with id ${id} not found`); - return ipa; -}; - -export const ipAdaptersSlice = createSlice({ - name: 'ipAdapters', - initialState, - reducers: { - ipaAdded: { - reducer: (state, action: PayloadAction<{ id: string; config: IPAdapterConfig }>) => { - const { id, config } = action.payload; - const layer: IPAdapterData = { - id, - type: 'ip_adapter', - isEnabled: true, - ...config, - }; - state.ipAdapters.push(layer); - }, - prepare: (config: IPAdapterConfig) => ({ payload: { id: uuidv4(), config } }), - }, - ipaRecalled: (state, action: PayloadAction<{ data: IPAdapterData }>) => { - state.ipAdapters.push(action.payload.data); - }, - ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const ipa = selectIPA(state, id); - if (ipa) { - ipa.isEnabled = !ipa.isEnabled; - } - }, - ipaDeleted: (state, action: PayloadAction<{ id: string }>) => { - state.ipAdapters = state.ipAdapters.filter((ipa) => ipa.id !== action.payload.id); - }, - ipaImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { - const { id, imageDTO } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; - }, - ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => { - const { id, method } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - ipa.method = method; - }, - ipaModelChanged: ( - state, - action: PayloadAction<{ - id: string; - modelConfig: IPAdapterModelConfig | null; - }> - ) => { - const { id, modelConfig } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - if (modelConfig) { - ipa.model = zModelIdentifierField.parse(modelConfig); - } else { - ipa.model = null; - } - }, - ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => { - const { id, clipVisionModel } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - ipa.clipVisionModel = clipVisionModel; - }, - ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { - const { id, weight } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - ipa.weight = weight; - }, - ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { - const { id, beginEndStepPct } = action.payload; - const ipa = selectIPA(state, id); - if (!ipa) { - return; - } - ipa.beginEndStepPct = beginEndStepPct; - }, - ipaAllDeleted: (state) => { - state.ipAdapters = []; - }, - }, -}); - -export const { - ipaAdded, - ipaRecalled, - ipaIsEnabledToggled, - ipaDeleted, - ipaImageChanged, - ipaMethodChanged, - ipaModelChanged, - ipaCLIPVisionModelChanged, - ipaWeightChanged, - ipaBeginEndStepPctChanged, - ipaAllDeleted, -} = ipAdaptersSlice.actions; - -export const selectIPAdaptersSlice = (state: RootState) => state.ipAdapters; - -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const migrate = (state: any): any => { - return state; -}; - -export const ipAdaptersPersistConfig: PersistConfig = { - name: ipAdaptersSlice.name, - initialState, - migrate, - persistDenylist: [], -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts new file mode 100644 index 0000000000..d44585fd4f --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts @@ -0,0 +1,227 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; +import { getBrushLineId, getEraserLineId, getImageObjectId, getRectShapeId } from 'features/controlLayers/konva/naming'; +import type { IRect } from 'konva/lib/types'; +import { assert } from 'tsafe'; +import { v4 as uuidv4 } from 'uuid'; + +import type { + BrushLineAddedArg, + CanvasV2State, + EraserLineAddedArg, + ImageObjectAddedArg, + LayerData, + PointAddedToLineArg, + RectShapeAddedArg, +} from './types'; +import { isLine } from './types'; + +export const selectLayer = (state: CanvasV2State, id: string) => state.layers.find((layer) => layer.id === id); +export const selectLayerOrThrow = (state: CanvasV2State, id: string) => { + const layer = selectLayer(state, id); + assert(layer, `Layer with id ${id} not found`); + return layer; +}; + +export const layersReducers = { + layerAdded: { + reducer: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + state.layers.push({ + id, + type: 'layer', + isEnabled: true, + bbox: null, + bboxNeedsUpdate: false, + objects: [], + opacity: 1, + x: 0, + y: 0, + }); + }, + prepare: () => ({ payload: { id: uuidv4() } }), + }, + layerRecalled: (state, action: PayloadAction<{ data: LayerData }>) => { + state.layers.push(action.payload.data); + }, + layerIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.isEnabled = !layer.isEnabled; + }, + layerTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { + const { id, x, y } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.x = x; + layer.y = y; + }, + layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { + const { id, bbox } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.bbox = bbox; + layer.bboxNeedsUpdate = false; + if (bbox === null) { + layer.objects = []; + } + }, + layerReset: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.isEnabled = true; + layer.objects = []; + layer.bbox = null; + layer.bboxNeedsUpdate = false; + }, + layerDeleted: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + state.layers = state.layers.filter((l) => l.id !== id); + }, + layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { + const { id, opacity } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.opacity = opacity; + }, + layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + moveOneToEnd(state.layers, layer); + }, + layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + moveToEnd(state.layers, layer); + }, + layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + moveOneToStart(state.layers, layer); + }, + layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + moveToStart(state.layers, layer); + }, + layerBrushLineAdded: { + reducer: (state, action: PayloadAction) => { + const { id, points, lineId, color, width } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + + layer.objects.push({ + id: getBrushLineId(id, lineId), + type: 'brush_line', + points, + strokeWidth: width, + color, + }); + layer.bboxNeedsUpdate = true; + }, + prepare: (payload: BrushLineAddedArg) => ({ + payload: { ...payload, lineId: uuidv4() }, + }), + }, + layerEraserLineAdded: { + reducer: (state, action: PayloadAction) => { + const { id, points, lineId, width } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + + layer.objects.push({ + id: getEraserLineId(id, lineId), + type: 'eraser_line', + points, + strokeWidth: width, + }); + layer.bboxNeedsUpdate = true; + }, + prepare: (payload: EraserLineAddedArg) => ({ + payload: { ...payload, lineId: uuidv4() }, + }), + }, + layerLinePointAdded: (state, action: PayloadAction) => { + const { id, point } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + const lastObject = layer.objects[layer.objects.length - 1]; + if (!lastObject || !isLine(lastObject)) { + return; + } + lastObject.points.push(...point); + layer.bboxNeedsUpdate = true; + }, + layerRectAdded: { + reducer: (state, action: PayloadAction) => { + const { id, rect, rectId, color } = action.payload; + if (rect.height === 0 || rect.width === 0) { + // Ignore zero-area rectangles + return; + } + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.objects.push({ + type: 'rect_shape', + id: getRectShapeId(id, rectId), + ...rect, + color, + }); + layer.bboxNeedsUpdate = true; + }, + prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }), + }, + layerImageAdded: { + reducer: (state, action: PayloadAction) => { + const { id, imageId, imageDTO } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + const { width, height, image_name: name } = imageDTO; + layer.objects.push({ + type: 'image', + id: getImageObjectId(id, imageId), + x: 0, + y: 0, + width, + height, + image: { width, height, name }, + }); + layer.bboxNeedsUpdate = true; + }, + prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }), + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts deleted file mode 100644 index 66567dc47c..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts +++ /dev/null @@ -1,275 +0,0 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import type { PersistConfig, RootState } from 'app/store/store'; -import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; -import { getBrushLineId, getEraserLineId, getImageObjectId, getRectShapeId } from 'features/controlLayers/konva/naming'; -import type { IRect } from 'konva/lib/types'; -import { assert } from 'tsafe'; -import { v4 as uuidv4 } from 'uuid'; - -import type { - BrushLineAddedArg, - EraserLineAddedArg, - ImageObjectAddedArg, - LayerData, - PointAddedToLineArg, - RectShapeAddedArg, -} from './types'; -import { isLine } from './types'; - -type LayersState = { - _version: 1; - layers: LayerData[]; -}; - -const initialState: LayersState = { _version: 1, layers: [] }; -export const selectLayer = (state: LayersState, id: string) => state.layers.find((layer) => layer.id === id); -export const selectLayerOrThrow = (state: LayersState, id: string) => { - const layer = selectLayer(state, id); - assert(layer, `Layer with id ${id} not found`); - return layer; -}; - -export const layersSlice = createSlice({ - name: 'layers', - initialState, - reducers: { - layerAdded: { - reducer: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - state.layers.push({ - id, - type: 'layer', - isEnabled: true, - bbox: null, - bboxNeedsUpdate: false, - objects: [], - opacity: 1, - x: 0, - y: 0, - }); - }, - prepare: () => ({ payload: { id: uuidv4() } }), - }, - layerRecalled: (state, action: PayloadAction<{ data: LayerData }>) => { - state.layers.push(action.payload.data); - }, - layerIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.isEnabled = !layer.isEnabled; - }, - layerTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { - const { id, x, y } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.x = x; - layer.y = y; - }, - layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { - const { id, bbox } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.bbox = bbox; - layer.bboxNeedsUpdate = false; - if (bbox === null) { - layer.objects = []; - } - }, - layerReset: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.isEnabled = true; - layer.objects = []; - layer.bbox = null; - layer.bboxNeedsUpdate = false; - }, - layerDeleted: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - state.layers = state.layers.filter((l) => l.id !== id); - }, - layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { - const { id, opacity } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.opacity = opacity; - }, - layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - moveOneToEnd(state.layers, layer); - }, - layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - moveToEnd(state.layers, layer); - }, - layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - moveOneToStart(state.layers, layer); - }, - layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - moveToStart(state.layers, layer); - }, - layerBrushLineAdded: { - reducer: (state, action: PayloadAction) => { - const { id, points, lineId, color, width } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - - layer.objects.push({ - id: getBrushLineId(id, lineId), - type: 'brush_line', - points, - strokeWidth: width, - color, - }); - layer.bboxNeedsUpdate = true; - }, - prepare: (payload: BrushLineAddedArg) => ({ - payload: { ...payload, lineId: uuidv4() }, - }), - }, - layerEraserLineAdded: { - reducer: (state, action: PayloadAction) => { - const { id, points, lineId, width } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - - layer.objects.push({ - id: getEraserLineId(id, lineId), - type: 'eraser_line', - points, - strokeWidth: width, - }); - layer.bboxNeedsUpdate = true; - }, - prepare: (payload: EraserLineAddedArg) => ({ - payload: { ...payload, lineId: uuidv4() }, - }), - }, - layerLinePointAdded: (state, action: PayloadAction) => { - const { id, point } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - const lastObject = layer.objects[layer.objects.length - 1]; - if (!lastObject || !isLine(lastObject)) { - return; - } - lastObject.points.push(...point); - layer.bboxNeedsUpdate = true; - }, - layerRectAdded: { - reducer: (state, action: PayloadAction) => { - const { id, rect, rectId, color } = action.payload; - if (rect.height === 0 || rect.width === 0) { - // Ignore zero-area rectangles - return; - } - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.objects.push({ - type: 'rect_shape', - id: getRectShapeId(id, rectId), - ...rect, - color, - }); - layer.bboxNeedsUpdate = true; - }, - prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }), - }, - layerImageAdded: { - reducer: (state, action: PayloadAction) => { - const { id, imageId, imageDTO } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - const { width, height, image_name: name } = imageDTO; - layer.objects.push({ - type: 'image', - id: getImageObjectId(id, imageId), - x: 0, - y: 0, - width, - height, - image: { width, height, name }, - }); - layer.bboxNeedsUpdate = true; - }, - prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }), - }, - layerAllDeleted: (state) => { - state.layers = []; - }, - }, -}); - -export const { - layerAdded, - layerDeleted, - layerReset, - layerMovedForwardOne, - layerMovedToFront, - layerMovedBackwardOne, - layerMovedToBack, - layerIsEnabledToggled, - layerOpacityChanged, - layerTranslated, - layerBboxChanged, - layerBrushLineAdded, - layerEraserLineAdded, - layerLinePointAdded, - layerRectAdded, - layerImageAdded, - layerAllDeleted, -} = layersSlice.actions; - -export const selectLayersSlice = (state: RootState) => state.layers; - -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const migrate = (state: any): any => { - return state; -}; - -export const layersPersistConfig: PersistConfig = { - name: layersSlice.name, - initialState, - migrate, - persistDenylist: [], -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts deleted file mode 100644 index 0b341d9399..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts +++ /dev/null @@ -1,452 +0,0 @@ -import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; -import type { PersistConfig, RootState } from 'app/store/store'; -import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; -import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming'; -import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; -import { imageDTOToImageWithDims } from 'features/controlLayers/store/types'; -import { zModelIdentifierField } from 'features/nodes/types/common'; -import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; -import type { IRect } from 'konva/lib/types'; -import { isEqual } from 'lodash-es'; -import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; -import { v4 as uuidv4 } from 'uuid'; - -import type { - BrushLineAddedArg, - EraserLineAddedArg, - IPAdapterData, - PointAddedToLineArg, - RectShapeAddedArg, - RegionalGuidanceData, - RgbColor, -} from './types'; -import { isLine } from './types'; - -type RegionalGuidanceState = { - _version: 1; - regions: RegionalGuidanceData[]; - opacity: number; -}; - -const initialState: RegionalGuidanceState = { - _version: 1, - regions: [], - opacity: 0.3, -}; - -export const selectRG = (state: RegionalGuidanceState, id: string) => state.regions.find((rg) => rg.id === id); -export const selectRGOrThrow = (state: RegionalGuidanceState, id: string) => { - const rg = selectRG(state, id); - assert(rg, `Region with id ${id} not found`); - return rg; -}; - -const DEFAULT_MASK_COLORS: RgbColor[] = [ - { r: 121, g: 157, b: 219 }, // rgb(121, 157, 219) - { r: 131, g: 214, b: 131 }, // rgb(131, 214, 131) - { r: 250, g: 225, b: 80 }, // rgb(250, 225, 80) - { r: 220, g: 144, b: 101 }, // rgb(220, 144, 101) - { r: 224, g: 117, b: 117 }, // rgb(224, 117, 117) - { r: 213, g: 139, b: 202 }, // rgb(213, 139, 202) - { r: 161, g: 120, b: 214 }, // rgb(161, 120, 214) -]; - -const getRGMaskFill = (state: RegionalGuidanceState): RgbColor => { - const lastFill = state.regions.slice(-1)[0]?.fill; - let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill)); - if (i === -1) { - i = 0; - } - i = (i + 1) % DEFAULT_MASK_COLORS.length; - const fill = DEFAULT_MASK_COLORS[i]; - assert(fill, 'This should never happen'); - return fill; -}; - -export const regionalGuidanceSlice = createSlice({ - name: 'regionalGuidance', - initialState, - reducers: { - rgAdded: { - reducer: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg: RegionalGuidanceData = { - id, - type: 'regional_guidance', - isEnabled: true, - bbox: null, - bboxNeedsUpdate: false, - objects: [], - fill: getRGMaskFill(state), - x: 0, - y: 0, - autoNegative: 'invert', - positivePrompt: '', - negativePrompt: null, - ipAdapters: [], - imageCache: null, - }; - state.regions.push(rg); - }, - prepare: () => ({ payload: { id: uuidv4() } }), - }, - rgReset: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.objects = []; - rg.bbox = null; - rg.bboxNeedsUpdate = false; - rg.imageCache = null; - }, - rgRecalled: (state, action: PayloadAction<{ data: RegionalGuidanceData }>) => { - const { data } = action.payload; - state.regions.push(data); - }, - rgIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (rg) { - rg.isEnabled = !rg.isEnabled; - } - }, - rgTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { - const { id, x, y } = action.payload; - const rg = selectRG(state, id); - if (rg) { - rg.x = x; - rg.y = y; - } - }, - rgBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { - const { id, bbox } = action.payload; - const rg = selectRG(state, id); - if (rg) { - rg.bbox = bbox; - rg.bboxNeedsUpdate = false; - } - }, - rgDeleted: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - state.regions = state.regions.filter((ca) => ca.id !== id); - }, - rgGlobalOpacityChanged: (state, action: PayloadAction<{ opacity: number }>) => { - const { opacity } = action.payload; - state.opacity = opacity; - }, - rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - moveOneToEnd(state.regions, rg); - }, - rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - moveToEnd(state.regions, rg); - }, - rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - moveOneToStart(state.regions, rg); - }, - rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => { - const { id } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - moveToStart(state.regions, rg); - }, - rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { - const { id, prompt } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.positivePrompt = prompt; - }, - rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { - const { id, prompt } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.negativePrompt = prompt; - }, - rgFillChanged: (state, action: PayloadAction<{ id: string; fill: RgbColor }>) => { - const { id, fill } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.fill = fill; - }, - rgMaskImageUploaded: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO }>) => { - const { id, imageDTO } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.imageCache = imageDTOToImageWithDims(imageDTO); - }, - rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => { - const { id, autoNegative } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.autoNegative = autoNegative; - }, - rgIPAdapterAdded: (state, action: PayloadAction<{ id: string; ipAdapter: IPAdapterData }>) => { - const { id, ipAdapter } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.ipAdapters.push(ipAdapter); - }, - rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => { - const { id, ipAdapterId } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.ipAdapters = rg.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId); - }, - rgIPAdapterImageChanged: ( - state, - action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null }> - ) => { - const { id, ipAdapterId, imageDTO } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; - }, - rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => { - const { id, ipAdapterId, weight } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - ipa.weight = weight; - }, - rgIPAdapterBeginEndStepPctChanged: ( - state, - action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }> - ) => { - const { id, ipAdapterId, beginEndStepPct } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - ipa.beginEndStepPct = beginEndStepPct; - }, - rgIPAdapterMethodChanged: ( - state, - action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }> - ) => { - const { id, ipAdapterId, method } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - ipa.method = method; - }, - rgIPAdapterModelChanged: ( - state, - action: PayloadAction<{ - id: string; - ipAdapterId: string; - modelConfig: IPAdapterModelConfig | null; - }> - ) => { - const { id, ipAdapterId, modelConfig } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - if (modelConfig) { - ipa.model = zModelIdentifierField.parse(modelConfig); - } else { - ipa.model = null; - } - }, - rgIPAdapterCLIPVisionModelChanged: ( - state, - action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }> - ) => { - const { id, ipAdapterId, clipVisionModel } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); - if (!ipa) { - return; - } - ipa.clipVisionModel = clipVisionModel; - }, - rgBrushLineAdded: { - reducer: (state, action: PayloadAction) => { - const { id, points, lineId, color, width } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.objects.push({ - id: getBrushLineId(id, lineId), - type: 'brush_line', - points, - strokeWidth: width, - color, - }); - rg.bboxNeedsUpdate = true; - rg.imageCache = null; - }, - prepare: (payload: BrushLineAddedArg) => ({ - payload: { ...payload, lineId: uuidv4() }, - }), - }, - rgEraserLineAdded: { - reducer: (state, action: PayloadAction) => { - const { id, points, lineId, width } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.objects.push({ - id: getEraserLineId(id, lineId), - type: 'eraser_line', - points, - strokeWidth: width, - }); - rg.bboxNeedsUpdate = true; - rg.imageCache = null; - }, - prepare: (payload: EraserLineAddedArg) => ({ - payload: { ...payload, lineId: uuidv4() }, - }), - }, - rgLinePointAdded: (state, action: PayloadAction) => { - const { id, point } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - const lastObject = rg.objects[rg.objects.length - 1]; - if (!lastObject || !isLine(lastObject)) { - return; - } - lastObject.points.push(...point); - rg.bboxNeedsUpdate = true; - rg.imageCache = null; - }, - rgRectAdded: { - reducer: (state, action: PayloadAction) => { - const { id, rect, rectId, color } = action.payload; - if (rect.height === 0 || rect.width === 0) { - // Ignore zero-area rectangles - return; - } - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.objects.push({ - type: 'rect_shape', - id: getRectShapeId(id, rectId), - ...rect, - color, - }); - rg.bboxNeedsUpdate = true; - rg.imageCache = null; - }, - prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }), - }, - rgAllDeleted: (state) => { - state.regions = []; - }, - }, -}); - -export const { - rgAdded, - rgRecalled, - rgReset, - rgIsEnabledToggled, - rgTranslated, - rgBboxChanged, - rgDeleted, - rgGlobalOpacityChanged, - rgMovedForwardOne, - rgMovedToFront, - rgMovedBackwardOne, - rgMovedToBack, - rgPositivePromptChanged, - rgNegativePromptChanged, - rgFillChanged, - rgMaskImageUploaded, - rgAutoNegativeChanged, - rgIPAdapterAdded, - rgIPAdapterDeleted, - rgIPAdapterImageChanged, - rgIPAdapterWeightChanged, - rgIPAdapterBeginEndStepPctChanged, - rgIPAdapterMethodChanged, - rgIPAdapterModelChanged, - rgIPAdapterCLIPVisionModelChanged, - rgBrushLineAdded, - rgEraserLineAdded, - rgLinePointAdded, - rgRectAdded, - rgAllDeleted, -} = regionalGuidanceSlice.actions; - -export const selectRegionalGuidanceSlice = (state: RootState) => state.regionalGuidance; - -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const migrate = (state: any): any => { - return state; -}; - -export const regionalGuidancePersistConfig: PersistConfig = { - name: regionalGuidanceSlice.name, - initialState, - migrate, - persistDenylist: [], -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts new file mode 100644 index 0000000000..56f3b935d9 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts @@ -0,0 +1,381 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; +import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming'; +import type { CanvasV2State, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; +import { imageDTOToImageWithDims } from 'features/controlLayers/store/types'; +import { zModelIdentifierField } from 'features/nodes/types/common'; +import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; +import type { IRect } from 'konva/lib/types'; +import { isEqual } from 'lodash-es'; +import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; +import { assert } from 'tsafe'; +import { v4 as uuidv4 } from 'uuid'; + +import type { + BrushLineAddedArg, + EraserLineAddedArg, + IPAdapterData, + PointAddedToLineArg, + RectShapeAddedArg, + RegionalGuidanceData, + RgbColor, +} from './types'; +import { isLine } from './types'; + +export const selectRG = (state: CanvasV2State, id: string) => state.regions.find((rg) => rg.id === id); +export const selectRGOrThrow = (state: CanvasV2State, id: string) => { + const rg = selectRG(state, id); + assert(rg, `Region with id ${id} not found`); + return rg; +}; + +const DEFAULT_MASK_COLORS: RgbColor[] = [ + { r: 121, g: 157, b: 219 }, // rgb(121, 157, 219) + { r: 131, g: 214, b: 131 }, // rgb(131, 214, 131) + { r: 250, g: 225, b: 80 }, // rgb(250, 225, 80) + { r: 220, g: 144, b: 101 }, // rgb(220, 144, 101) + { r: 224, g: 117, b: 117 }, // rgb(224, 117, 117) + { r: 213, g: 139, b: 202 }, // rgb(213, 139, 202) + { r: 161, g: 120, b: 214 }, // rgb(161, 120, 214) +]; + +const getRGMaskFill = (state: CanvasV2State): RgbColor => { + const lastFill = state.regions.slice(-1)[0]?.fill; + let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill)); + if (i === -1) { + i = 0; + } + i = (i + 1) % DEFAULT_MASK_COLORS.length; + const fill = DEFAULT_MASK_COLORS[i]; + assert(fill, 'This should never happen'); + return fill; +}; + +export const regionsReducers = { + rgAdded: { + reducer: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg: RegionalGuidanceData = { + id, + type: 'regional_guidance', + isEnabled: true, + bbox: null, + bboxNeedsUpdate: false, + objects: [], + fill: getRGMaskFill(state), + x: 0, + y: 0, + autoNegative: 'invert', + positivePrompt: '', + negativePrompt: null, + ipAdapters: [], + imageCache: null, + }; + state.regions.push(rg); + }, + prepare: () => ({ payload: { id: uuidv4() } }), + }, + rgReset: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.objects = []; + rg.bbox = null; + rg.bboxNeedsUpdate = false; + rg.imageCache = null; + }, + rgRecalled: (state, action: PayloadAction<{ data: RegionalGuidanceData }>) => { + const { data } = action.payload; + state.regions.push(data); + }, + rgIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (rg) { + rg.isEnabled = !rg.isEnabled; + } + }, + rgTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => { + const { id, x, y } = action.payload; + const rg = selectRG(state, id); + if (rg) { + rg.x = x; + rg.y = y; + } + }, + rgBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => { + const { id, bbox } = action.payload; + const rg = selectRG(state, id); + if (rg) { + rg.bbox = bbox; + rg.bboxNeedsUpdate = false; + } + }, + rgDeleted: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + state.regions = state.regions.filter((ca) => ca.id !== id); + }, + rgGlobalOpacityChanged: (state, action: PayloadAction<{ opacity: number }>) => { + const { opacity } = action.payload; + state.maskFillOpacity = opacity; + }, + rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + moveOneToEnd(state.regions, rg); + }, + rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + moveToEnd(state.regions, rg); + }, + rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + moveOneToStart(state.regions, rg); + }, + rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + moveToStart(state.regions, rg); + }, + rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { + const { id, prompt } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.positivePrompt = prompt; + }, + rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { + const { id, prompt } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.negativePrompt = prompt; + }, + rgFillChanged: (state, action: PayloadAction<{ id: string; fill: RgbColor }>) => { + const { id, fill } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.fill = fill; + }, + rgMaskImageUploaded: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO }>) => { + const { id, imageDTO } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.imageCache = imageDTOToImageWithDims(imageDTO); + }, + rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => { + const { id, autoNegative } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.autoNegative = autoNegative; + }, + rgIPAdapterAdded: (state, action: PayloadAction<{ id: string; ipAdapter: IPAdapterData }>) => { + const { id, ipAdapter } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.ipAdapters.push(ipAdapter); + }, + rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => { + const { id, ipAdapterId } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.ipAdapters = rg.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId); + }, + rgIPAdapterImageChanged: ( + state, + action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null }> + ) => { + const { id, ipAdapterId, imageDTO } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; + }, + rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => { + const { id, ipAdapterId, weight } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + ipa.weight = weight; + }, + rgIPAdapterBeginEndStepPctChanged: ( + state, + action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }> + ) => { + const { id, ipAdapterId, beginEndStepPct } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + ipa.beginEndStepPct = beginEndStepPct; + }, + rgIPAdapterMethodChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }>) => { + const { id, ipAdapterId, method } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + ipa.method = method; + }, + rgIPAdapterModelChanged: ( + state, + action: PayloadAction<{ + id: string; + ipAdapterId: string; + modelConfig: IPAdapterModelConfig | null; + }> + ) => { + const { id, ipAdapterId, modelConfig } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + if (modelConfig) { + ipa.model = zModelIdentifierField.parse(modelConfig); + } else { + ipa.model = null; + } + }, + rgIPAdapterCLIPVisionModelChanged: ( + state, + action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }> + ) => { + const { id, ipAdapterId, clipVisionModel } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId); + if (!ipa) { + return; + } + ipa.clipVisionModel = clipVisionModel; + }, + rgBrushLineAdded: { + reducer: (state, action: PayloadAction) => { + const { id, points, lineId, color, width } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.objects.push({ + id: getBrushLineId(id, lineId), + type: 'brush_line', + points, + strokeWidth: width, + color, + }); + rg.bboxNeedsUpdate = true; + rg.imageCache = null; + }, + prepare: (payload: BrushLineAddedArg) => ({ + payload: { ...payload, lineId: uuidv4() }, + }), + }, + rgEraserLineAdded: { + reducer: (state, action: PayloadAction) => { + const { id, points, lineId, width } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.objects.push({ + id: getEraserLineId(id, lineId), + type: 'eraser_line', + points, + strokeWidth: width, + }); + rg.bboxNeedsUpdate = true; + rg.imageCache = null; + }, + prepare: (payload: EraserLineAddedArg) => ({ + payload: { ...payload, lineId: uuidv4() }, + }), + }, + rgLinePointAdded: (state, action: PayloadAction) => { + const { id, point } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + const lastObject = rg.objects[rg.objects.length - 1]; + if (!lastObject || !isLine(lastObject)) { + return; + } + lastObject.points.push(...point); + rg.bboxNeedsUpdate = true; + rg.imageCache = null; + }, + rgRectAdded: { + reducer: (state, action: PayloadAction) => { + const { id, rect, rectId, color } = action.payload; + if (rect.height === 0 || rect.width === 0) { + // Ignore zero-area rectangles + return; + } + const rg = selectRG(state, id); + if (!rg) { + return; + } + rg.objects.push({ + type: 'rect_shape', + id: getRectShapeId(id, rectId), + ...rect, + color, + }); + rg.bboxNeedsUpdate = true; + rg.imageCache = null; + }, + prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }), + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/test.ts b/invokeai/frontend/web/src/features/controlLayers/store/test.ts new file mode 100644 index 0000000000..2426f9af2c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/test.ts @@ -0,0 +1,57 @@ +import type { ActionReducerMapBuilder, PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; + +type MySlice = { + flavour: 'vanilla' | 'chocolate' | 'strawberry'; + sprinkles: boolean; + customers: { id: string; name: string }[]; +}; +const initialStateMySlice: MySlice = { flavour: 'vanilla', sprinkles: false, customers: [] }; + +const reducersInAnotherFile: SliceCaseReducers = { + sprinklesToggled: (state) => { + state.sprinkles = !state.sprinkles; + }, + customerAdded: { + reducer: (state, action: PayloadAction<{ id: string; name: string }>) => { + state.customers.push(action.payload); + }, + prepare: (name: string) => ({ payload: { name, id: crypto.randomUUID() } }), + }, +}; + +const extraReducersInAnotherFile = (builder: ActionReducerMapBuilder) => { + builder.addCase(otherSlice.actions.fooChanged, (state, action) => { + if (action.payload === 'bar') { + state.flavour = 'vanilla'; + } + }); +}; + +export const mySlice = createSlice({ + name: 'mySlice', + initialState: initialStateMySlice, + reducers: { + ...reducersInAnotherFile, + flavourChanged: (state, action: PayloadAction) => { + state.flavour = action.payload; + }, + }, + extraReducers: extraReducersInAnotherFile, +}); + +type OtherSlice = { + something: string; +}; + +const initialStateOtherSlice: OtherSlice = { something: 'foo' }; + +export const otherSlice = createSlice({ + name: 'otherSlice', + initialState: initialStateOtherSlice, + reducers: { + fooChanged: (state, action: PayloadAction) => { + state.something = action.payload; + }, + }, +}); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 8388ed0530..da1610559b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -783,6 +783,11 @@ export type CanvasV2State = { aspectRatio: AspectRatioState; }; bbox: IRect; + layers: LayerData[]; + controlAdapters: ControlAdapterData[]; + ipAdapters: IPAdapterData[]; + regions: RegionalGuidanceData[]; + maskFillOpacity: number; }; export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number }; diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx index 7ff8cb1b9e..3d33999c71 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx @@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors'; import { diff --git a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts index 456872e23b..ce36e9080d 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts +++ b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts @@ -7,7 +7,7 @@ import { } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdaptersState } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import type { CanvasV2State } from 'features/controlLayers/store/types'; import { isControlAdapterLayer, diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx index 87e78f22b4..fd9a52a69d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -15,7 +15,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import type { ImageUsage } from 'features/deleteImageModal/store/types'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index 1ae4b7b6bc..b2b1f9eff0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -6,7 +6,7 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard'; import { useDownloadImage } from 'common/hooks/useDownloadImage'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice'; -import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; +import { iiLayerAdded } from 'features/controlLayers/store/canvasV2Slice'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import { useImageActions } from 'features/gallery/hooks/useImageActions'; import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx index d1f874271d..d5c23ecb90 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx @@ -4,7 +4,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query'; import { adHocPostProcessingRequested } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; +import { iiLayerAdded } from 'features/controlLayers/store/canvasV2Slice'; import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems'; diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.ts index f55f085b7d..d0f151118c 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import { deepClone } from 'common/util/deepClone'; import { objectKeys } from 'common/util/objectKeys'; -import { shouldConcatPromptsChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { shouldConcatPromptsChanged } from 'features/controlLayers/store/canvasV2Slice'; import type { LayerData } from 'features/controlLayers/store/types'; import type { LoRA } from 'features/lora/store/loraSlice'; import type { diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 9e0ecf5b8b..70027d0c97 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -19,7 +19,7 @@ import { positivePromptChanged, regionalGuidanceRecalled, widthChanged, -} from 'features/controlLayers/store/controlLayersSlice'; +} from 'features/controlLayers/store/canvasV2Slice'; import type { LayerData } from 'features/controlLayers/store/types'; import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice'; import type { LoRA } from 'features/lora/store/loraSlice'; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlLayers.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlLayers.ts index 9ee41158b4..78702fccfe 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlLayers.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlLayers.ts @@ -5,7 +5,7 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; import { RG_LAYER_NAME } from 'features/controlLayers/konva/naming'; import { renderers } from 'features/controlLayers/konva/renderers/layers'; -import { regionalGuidanceMaskImageUploaded } from 'features/controlLayers/store/controlLayersSlice'; +import { regionalGuidanceMaskImageUploaded } from 'features/controlLayers/store/canvasV2Slice'; import type { InitialImageLayer, LayerData, RegionalGuidanceLayer } from 'features/controlLayers/store/types'; import { isControlAdapterLayer, diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index 73141cf325..e415c2d5fa 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { negativePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { negativePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt'; diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index 32c66231b9..f7c2c285ff 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { positivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx index 08b591f9b1..56b188c3bf 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx @@ -1,7 +1,7 @@ import { Flex } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { StageComponent } from 'features/controlLayers/components/StageComponent'; -import { $isPreviewVisible } from 'features/controlLayers/store/controlLayersSlice'; +import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice'; import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview'; import { memo } from 'react'; diff --git a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts index 683f5479f9..74aef5f79c 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts @@ -1,7 +1,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; -import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; +import { iiLayerAdded } from 'features/controlLayers/store/canvasV2Slice'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { toast } from 'features/toast/toast'; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index 9c2d8ebebe..834d66f0d0 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -2,7 +2,7 @@ import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-a import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import type { PropsWithChildren } from 'react'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index 1f22c1b845..f295ffd32f 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { negativePrompt2Changed } from 'features/controlLayers/store/controlLayersSlice'; +import { negativePrompt2Changed } from 'features/controlLayers/store/canvasV2Slice'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index 0b86f3014d..8e31185345 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { positivePrompt2Changed } from 'features/controlLayers/store/controlLayersSlice'; +import { positivePrompt2Changed } from 'features/controlLayers/store/canvasV2Slice'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index dc3b24402b..9048437e3a 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -1,6 +1,6 @@ import { IconButton, Tooltip } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { shouldConcatPromptsChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { shouldConcatPromptsChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx index 499a0a307c..52278cfd60 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -2,7 +2,7 @@ import type { FormLabelProps } from '@invoke-ai/ui-library'; import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; +import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { HrfSettings } from 'features/hrf/components/HrfSettings'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx index 658f47196a..13670c674f 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx @@ -1,5 +1,5 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { ParamHeight } from 'features/parameters/components/Core/ParamHeight'; import { ParamWidth } from 'features/parameters/components/Core/ParamWidth'; import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview'; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx index bf2b915090..0196336e20 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx @@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent'; -import { $isPreviewVisible } from 'features/controlLayers/store/controlLayersSlice'; +import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice'; import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls';