From c3b53fc4f6ad9100040b21c01a6fe418d5a54f83 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:05:45 +1000 Subject: [PATCH] feat(ui): rough out undo/redo on canvas --- .../addCommitStagingAreaImageListener.ts | 5 +- .../listeners/boardAndImagesDeleted.ts | 9 ++- .../listeners/imageDeletionListeners.ts | 9 +-- .../listeners/imageDropped.ts | 7 +- .../listeners/imageUploaded.ts | 2 +- .../listeners/modelSelected.ts | 2 +- .../listeners/modelsLoaded.ts | 18 +++-- .../listeners/setDefaultSettings.ts | 2 +- invokeai/frontend/web/src/app/store/store.ts | 6 +- .../src/common/hooks/useIsReadyToEnqueue.ts | 16 ++--- .../components/CanvasAddEntityButtons.tsx | 2 +- .../CanvasEntityListMenuItems.tsx | 2 +- .../ControlLayer/ControlLayerBadges.tsx | 4 +- .../ControlLayerControlAdapter.tsx | 2 +- .../ControlLayer/ControlLayerEntityList.tsx | 8 +-- .../ControlLayerMenuItemsControlToRaster.tsx | 2 +- ...ontrolLayerMenuItemsTransparencyEffect.tsx | 8 +-- .../components/HeadsUpDisplay.tsx | 6 +- .../IPAdapter/IPAdapterImagePreview.tsx | 2 +- .../components/IPAdapter/IPAdapterList.tsx | 12 ++-- .../IPAdapter/IPAdapterSettings.tsx | 11 +++- .../InpaintMask/InpaintMaskList.tsx | 13 ++-- .../InpaintMaskMaskFillColorPicker.tsx | 13 ++-- .../RasterLayer/RasterLayerEntityList.tsx | 12 ++-- .../RasterLayerMenuItemsRasterToControl.tsx | 2 +- ...onalGuidanceAddPromptsIPAdapterButtons.tsx | 8 +-- .../RegionalGuidanceBadges.tsx | 11 +++- .../RegionalGuidanceEntityList.tsx | 12 ++-- .../RegionalGuidanceIPAdapterSettings.tsx | 20 ++++-- .../RegionalGuidanceIPAdapters.tsx | 6 +- .../RegionalGuidanceMaskFillColorPicker.tsx | 13 ++-- ...uidanceMenuItemsAddPromptsAndIPAdapter.tsx | 8 +-- .../RegionalGuidanceMenuItemsAutoNegative.tsx | 13 ++-- .../RegionalGuidanceNegativePrompt.tsx | 14 ++-- .../RegionalGuidancePositivePrompt.tsx | 14 ++-- .../RegionalGuidanceSettings.tsx | 37 +++++++---- .../Settings/CanvasSettingsResetButton.tsx | 2 +- .../components/Tool/ToolBrushButton.tsx | 12 ++-- .../components/Tool/ToolEraserButton.tsx | 12 ++-- .../components/Tool/ToolMoveButton.tsx | 12 ++-- .../components/Tool/ToolRectButton.tsx | 12 ++-- .../components/UndoRedoButtonGroup.tsx | 15 +++-- .../common/CanvasEntityContainer.tsx | 2 +- .../common/CanvasEntityEnabledToggle.tsx | 2 +- .../common/CanvasEntityMenuItemsArrange.tsx | 32 ++++----- .../common/CanvasEntityMenuItemsDelete.tsx | 2 +- .../common/CanvasEntityMenuItemsDuplicate.tsx | 2 +- .../components/common/CanvasEntityOpacity.tsx | 15 +++-- .../common/CanvasEntityPreviewImage.tsx | 4 +- .../common/CanvasEntityTitleEdit.tsx | 2 +- .../common/CanvasEntityTypeIsHiddenToggle.tsx | 2 +- .../hooks/useCanvasDeleteLayerHotkey.ts | 8 +-- .../hooks/useCanvasResetLayerHotkey.ts | 8 +-- .../controlLayers/hooks/useEntityIsEnabled.ts | 6 +- .../hooks/useEntityIsSelected.ts | 14 ++-- .../hooks/useEntityObjectCount.ts | 6 +- .../hooks/useEntitySelectionColor.ts | 6 +- .../controlLayers/hooks/useEntityTitle.ts | 6 +- .../controlLayers/hooks/useEntityTypeCount.ts | 14 ++-- .../hooks/useEntityTypeIsHidden.ts | 12 ++-- .../hooks/useLayerControlAdapter.ts | 6 +- .../konva/CanvasRenderingModule.ts | 16 ++--- .../konva/CanvasStateApiModule.ts | 6 +- .../controlLayers/store/bboxReducers.ts | 6 +- .../controlLayers/store/canvasSessionSlice.ts | 12 ++-- .../store/canvasSettingsSlice.ts | 3 +- .../{canvasV2Slice.ts => canvasSlice.ts} | 16 ++--- .../store/controlLayersReducers.ts | 4 +- .../store/inpaintMaskReducers.ts | 4 +- .../controlLayers/store/ipAdaptersReducers.ts | 4 +- .../store/rasterLayersReducers.ts | 4 +- .../controlLayers/store/regionsReducers.ts | 6 +- .../features/controlLayers/store/selectors.ts | 65 ++++++++++++------- .../src/features/controlLayers/store/types.ts | 2 +- .../components/DeleteImageModal.tsx | 8 +-- .../deleteImageModal/store/selectors.ts | 14 ++-- .../components/Boards/DeleteBoardModal.tsx | 6 +- .../src/features/metadata/util/recallers.ts | 2 +- .../util/graph/generation/addImageToImage.ts | 4 +- .../nodes/util/graph/generation/addInpaint.ts | 10 ++- .../util/graph/generation/addOutpaint.ts | 10 ++- .../util/graph/generation/buildSD1Graph.ts | 26 +++++--- .../util/graph/generation/buildSDXLGraph.ts | 26 +++++--- .../nodes/util/graph/graphBuilderUtils.ts | 4 +- .../ParamScaleBeforeProcessing.tsx | 8 ++- .../InfillAndScaling/ParamScaledHeight.tsx | 44 +++++++------ .../InfillAndScaling/ParamScaledWidth.tsx | 44 +++++++------ .../components/Core/ParamHeight.tsx | 39 ++++++----- .../parameters/components/Core/ParamWidth.tsx | 39 ++++++----- .../DocumentSize/AspectRatioSelect.tsx | 8 ++- .../DocumentSize/LockAspectRatioButton.tsx | 8 ++- .../DocumentSize/SetOptimalSizeButton.tsx | 12 ++-- .../DocumentSize/SwapDimensionsButton.tsx | 2 +- .../ImageSettingsAccordion.tsx | 10 +-- 94 files changed, 586 insertions(+), 431 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/store/{canvasV2Slice.ts => canvasSlice.ts} (97%) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts index 7c533717e7..2b5949e414 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts @@ -4,7 +4,8 @@ import { sessionStagingAreaImageAccepted, sessionStagingAreaReset, } from 'features/controlLayers/store/canvasSessionSlice'; -import { rasterLayerAdded } from 'features/controlLayers/store/canvasV2Slice'; +import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { CanvasRasterLayerState } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import { toast } from 'features/toast/toast'; @@ -58,7 +59,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => { const stagingAreaImage = state.canvasSession.stagedImages[index]; assert(stagingAreaImage, 'No staged image found to accept'); - const { x, y } = state.canvasV2.bbox.rect; + const { x, y } = selectCanvasSlice(state).bbox.rect; const { imageDTO, offsetX, offsetY } = stagingAreaImage; const imageObject = imageDTOToImageObject(imageDTO); 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 be0d9ab195..0e30802328 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,6 +1,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; -import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; +import { nodeEditorReset, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { imagesApi } from 'services/api/endpoints/images'; export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => { @@ -13,10 +14,12 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS let wasNodeEditorReset = false; - const { nodes, canvasV2 } = getState(); + const state = getState(); + const nodes = selectNodesSlice(state); + const canvas = selectCanvasSlice(state); deleted_images.forEach((image_name) => { - const imageUsage = getImageUsage(nodes.present, canvasV2, image_name); + const imageUsage = getImageUsage(nodes, canvas, image_name); if (imageUsage.isNodesImage && !wasNodeEditorReset) { dispatch(nodeEditorReset()); 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 cf1fc0ff30..258ddf4620 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 @@ -1,7 +1,8 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppDispatch, RootState } from 'app/store/store'; -import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { getEntityIdentifier } from 'features/controlLayers/store/types'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { isModalOpenChanged } from 'features/deleteImageModal/store/slice'; @@ -40,7 +41,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im }; // const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { -// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => { +// state.canvas.present.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => { // if ( // imageObject?.image.image_name === imageDTO.image_name || // processedImageObject?.image.image_name === imageDTO.image_name @@ -52,7 +53,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im // }; const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { - state.canvasV2.ipAdapters.entities.forEach((entity) => { + selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => { if (entity.ipAdapter.image?.image_name === imageDTO.image_name) { dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null })); } @@ -60,7 +61,7 @@ const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO }; const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { - state.canvasV2.rasterLayers.entities.forEach(({ id, objects }) => { + selectCanvasSlice(state).rasterLayers.entities.forEach(({ id, objects }) => { let shouldDelete = false; for (const obj of objects) { if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) { 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 1d6bdacaa6..996050c3e2 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 @@ -6,7 +6,8 @@ import { ipaImageChanged, rasterLayerAdded, rgIPAdapterImageChanged, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { CanvasControlLayerState, CanvasRasterLayerState } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; @@ -85,7 +86,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) => activeData.payload.imageDTO ) { const imageObject = imageDTOToImageObject(activeData.payload.imageDTO); - const { x, y } = getState().canvasV2.bbox.rect; + const { x, y } = selectCanvasSlice(getState()).bbox.rect; const overrides: Partial = { objects: [imageObject], position: { x, y }, @@ -103,7 +104,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) => activeData.payload.imageDTO ) { const imageObject = imageDTOToImageObject(activeData.payload.imageDTO); - const { x, y } = getState().canvasV2.bbox.rect; + const { x, y } = selectCanvasSlice(getState()).bbox.rect; const overrides: Partial = { objects: [imageObject], position: { x, y }, 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 c10fc60a4d..0a5e18a160 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,6 +1,6 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasSlice'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index fefd88800e..13a256ad4e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -46,7 +46,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = } // handle incompatible controlnets - // state.canvasV2.controlAdapters.entities.forEach((ca) => { + // state.canvas.present.controlAdapters.entities.forEach((ca) => { // if (ca.model?.base !== newBaseModel) { // modelsCleared += 1; // if (ca.isEnabled) { 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 cc452a2152..f5a3fce865 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 @@ -8,9 +8,10 @@ import { controlLayerModelChanged, ipaModelChanged, rgIPAdapterModelChanged, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSlice'; import { loraDeleted } from 'features/controlLayers/store/lorasSlice'; import { modelChanged, refinerModelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { getEntityIdentifier } from 'features/controlLayers/store/types'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; @@ -81,15 +82,12 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => { const result = zParameterModel.safeParse(defaultModelInList); if (result.success) { dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel })); - + const { bbox } = selectCanvasSlice(state); const optimalDimension = getOptimalDimension(defaultModelInList); - if (getIsSizeOptimal(state.canvasV2.bbox.rect.width, state.canvasV2.bbox.rect.height, optimalDimension)) { + if (getIsSizeOptimal(bbox.rect.width, bbox.rect.height, optimalDimension)) { return; } - const { width, height } = calculateNewSize( - state.canvasV2.bbox.aspectRatio.value, - optimalDimension * optimalDimension - ); + const { width, height } = calculateNewSize(bbox.aspectRatio.value, optimalDimension * optimalDimension); dispatch(bboxWidthChanged({ width })); dispatch(bboxHeightChanged({ height })); @@ -172,7 +170,7 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => { const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => { const caModels = models.filter(isControlNetOrT2IAdapterModelConfig); - state.canvasV2.controlLayers.entities.forEach((entity) => { + selectCanvasSlice(state).controlLayers.entities.forEach((entity) => { const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key); if (isModelAvailable) { return; @@ -183,7 +181,7 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => { const ipaModels = models.filter(isIPAdapterModelConfig); - state.canvasV2.ipAdapters.entities.forEach((entity) => { + selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => { const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key); if (isModelAvailable) { return; @@ -191,7 +189,7 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => { dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null })); }); - state.canvasV2.regions.entities.forEach((entity) => { + selectCanvasSlice(state).regions.entities.forEach((entity) => { entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => { const isModelAvailable = ipaModels.some((m) => m.key === model?.key); if (isModelAvailable) { 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 e013b3d17f..42e17b938b 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 { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice'; import { setCfgRescaleMultiplier, setCfgScale, diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 8c8dbb9681..00bdde8ff5 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -8,7 +8,7 @@ import { deepClone } from 'common/util/deepClone'; import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice'; import { canvasSessionPersistConfig, canvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice'; import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice'; -import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; +import { canvasPersistConfig, canvasSlice } from 'features/controlLayers/store/canvasSlice'; import { lorasPersistConfig, lorasSlice } from 'features/controlLayers/store/lorasSlice'; import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice'; import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice'; @@ -58,7 +58,7 @@ const allReducers = { [queueSlice.name]: queueSlice.reducer, [workflowSlice.name]: workflowSlice.reducer, [hrfSlice.name]: hrfSlice.reducer, - [canvasV2Slice.name]: canvasV2Slice.reducer, + [canvasSlice.name]: undoable(canvasSlice.reducer), [workflowSettingsSlice.name]: workflowSettingsSlice.reducer, [upscaleSlice.name]: upscaleSlice.reducer, [stylePresetSlice.name]: stylePresetSlice.reducer, @@ -104,7 +104,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig, [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig, [hrfPersistConfig.name]: hrfPersistConfig, - [canvasV2PersistConfig.name]: canvasV2PersistConfig, + [canvasPersistConfig.name]: canvasPersistConfig, [workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig, [upscalePersistConfig.name]: upscalePersistConfig, [stylePresetPersistConfig.name]: stylePresetPersistConfig, diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index f060a0e6c2..c3946a2f0a 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -3,7 +3,7 @@ import { $isConnected } from 'app/hooks/useSocketIO'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; @@ -34,14 +34,14 @@ const createSelector = (templates: Templates, isConnected: boolean) => selectNodesSlice, selectWorkflowSettingsSlice, selectDynamicPromptsSlice, - selectCanvasV2Slice, + selectCanvasSlice, selectParamsSlice, selectUpscalelice, selectConfigSlice, selectActiveTab, ], - (system, nodes, workflowSettings, dynamicPrompts, canvasV2, params, upscale, config, activeTabName) => { - const { bbox } = canvasV2; + (system, nodes, workflowSettings, dynamicPrompts, canvas, params, upscale, config, activeTabName) => { + const { bbox } = canvas; const { model, positivePrompt } = params; const reasons: { prefix?: string; content: string }[] = []; @@ -124,7 +124,7 @@ const createSelector = (templates: Templates, isConnected: boolean) => reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') }); } - canvasV2.controlLayers.entities + canvas.controlLayers.entities .filter((controlLayer) => controlLayer.isEnabled) .forEach((controlLayer, i) => { const layerLiteral = i18n.t('controlLayers.layers_one'); @@ -154,7 +154,7 @@ const createSelector = (templates: Templates, isConnected: boolean) => } }); - canvasV2.ipAdapters.entities + canvas.ipAdapters.entities .filter((entity) => entity.isEnabled) .forEach((entity, i) => { const layerLiteral = i18n.t('controlLayers.layers_one'); @@ -182,7 +182,7 @@ const createSelector = (templates: Templates, isConnected: boolean) => } }); - canvasV2.regions.entities + canvas.regions.entities .filter((entity) => entity.isEnabled) .forEach((entity, i) => { const layerLiteral = i18n.t('controlLayers.layers_one'); @@ -219,7 +219,7 @@ const createSelector = (templates: Templates, isConnected: boolean) => } }); - canvasV2.rasterLayers.entities + canvas.rasterLayers.entities .filter((entity) => entity.isEnabled) .forEach((entity, i) => { const layerLiteral = i18n.t('controlLayers.layers_one'); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx index abb689983a..7b50721185 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx @@ -6,7 +6,7 @@ import { ipaAdded, rasterLayerAdded, rgAdded, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSlice'; 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/CanvasEntityList/CanvasEntityListMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx index 38be1d477d..82ceb60929 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx @@ -7,7 +7,7 @@ import { ipaAdded, rasterLayerAdded, rgAdded, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSlice'; import { selectEntityCount } from 'features/controlLayers/store/selectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerBadges.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerBadges.tsx index ec68367b3a..126d8fabbb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerBadges.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerBadges.tsx @@ -1,7 +1,7 @@ import { Badge } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,7 +9,7 @@ export const ControlLayerBadges = memo(() => { const entityIdentifier = useEntityIdentifierContext('control_layer'); const { t } = useTranslation(); const withTransparencyEffect = useAppSelector( - (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).withTransparencyEffect + (s) => selectEntityOrThrow(selectCanvasSlice(s), entityIdentifier).withTransparencyEffect ); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx index 3b9e6d1a31..549c8fe7b6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx @@ -11,7 +11,7 @@ import { controlLayerControlModeChanged, controlLayerModelChanged, controlLayerWeightChanged, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSlice'; import type { ControlModeV2 } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerEntityList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerEntityList.tsx index 8f94481f86..42c66ddd52 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerEntityList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerEntityList.tsx @@ -3,15 +3,15 @@ import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.controlLayers.entities.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return canvas.controlLayers.entities.map(mapId).reverse(); }); export const ControlLayerEntityList = memo(() => { - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer')); + const isSelected = useAppSelector((s) => selectSelectedEntityIdentifier(s)?.type === 'control_layer'); const layerIds = useAppSelector(selectEntityIds); if (layerIds.length === 0) { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx index 924122fe24..5f1797a0a7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx @@ -1,7 +1,7 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasV2Slice'; +import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLightningBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx index 5342e975ba..ce48a6332f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx @@ -2,8 +2,8 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiDropHalfBold } from 'react-icons/pi'; @@ -14,8 +14,8 @@ export const ControlLayerMenuItemsTransparencyEffect = memo(() => { const entityIdentifier = useEntityIdentifierContext('control_layer'); const selectWithTransparencyEffect = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntityOrThrow(canvasV2, entityIdentifier); + createSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntityOrThrow(canvas, entityIdentifier); return entity.withTransparencyEffect; }), [entityIdentifier] diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx index ec7bcdaa25..28fef1854a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx @@ -1,10 +1,14 @@ import { Box, Flex, Text } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { round } from 'lodash-es'; import { memo } from 'react'; +const selectBbox = createSelector(selectCanvasSlice, (canvas) => canvas.bbox); + export const HeadsUpDisplay = memo(() => { const canvasManager = useCanvasManager(); const stageAttrs = useStore(canvasManager.stateApi.$stageAttrs); @@ -13,7 +17,7 @@ export const HeadsUpDisplay = memo(() => { const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown); const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos); const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint); - const bbox = useAppSelector((s) => s.canvasV2.bbox); + const bbox = useAppSelector(selectBbox); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx index e1f6b07857..9849c9578f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx @@ -5,7 +5,7 @@ import { $isConnected } from 'app/hooks/useSocketIO'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; -import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import type { ImageWithDims } from 'features/controlLayers/store/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx index cdfcc897b5..c2cc874a4a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx @@ -1,18 +1,22 @@ /* eslint-disable i18next/no-literal-string */ +import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { IPAdapter } from 'features/controlLayers/components/IPAdapter/IPAdapter'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.ipAdapters.entities.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return canvas.ipAdapters.entities.map(mapId).reverse(); +}); +const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => { + return selectedEntityIdentifier?.type === 'ip_adapter'; }); export const IPAdapterList = memo(() => { - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter')); + const isSelected = useAppSelector(selectIsSelected); const ipaIds = useAppSelector(selectEntityIds); if (ipaIds.length === 0) { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx index 0f8152fd9f..44b708a238 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx @@ -1,4 +1,5 @@ import { Box, Flex } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct'; import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper'; @@ -12,8 +13,8 @@ import { ipaMethodChanged, ipaModelChanged, ipaWeightChanged, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { IPAImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; @@ -25,7 +26,11 @@ import { IPAdapterModel } from './IPAdapterModel'; export const IPAdapterSettings = memo(() => { const dispatch = useAppDispatch(); const entityIdentifier = useEntityIdentifierContext('ip_adapter'); - const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapter); + const selectIPAdapter = useMemo( + () => createSelector(selectCanvasSlice, (s) => selectEntityOrThrow(s, entityIdentifier).ipAdapter), + [entityIdentifier] + ); + const ipAdapter = useAppSelector(selectIPAdapter); const onChangeBeginEndStepPct = useCallback( (beginEndStepPct: [number, number]) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskList.tsx index b4f92759e4..6b04ff511a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskList.tsx @@ -1,17 +1,22 @@ +import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.inpaintMasks.entities.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return canvas.inpaintMasks.entities.map(mapId).reverse(); +}); + +const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => { + return selectedEntityIdentifier?.type === 'inpaint_mask'; }); export const InpaintMaskList = memo(() => { - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask')); + const isSelected = useAppSelector(selectIsSelected); const entityIds = useAppSelector(selectEntityIds); if (entityIds.length === 0) { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx index 426d5261f5..6d2879eac6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx @@ -1,20 +1,25 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import type { FillStyle, RgbColor } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; export const InpaintMaskMaskFillColorPicker = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const entityIdentifier = useEntityIdentifierContext('inpaint_mask'); - const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill); + const selectFill = useMemo( + () => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).fill), + [entityIdentifier] + ); + const fill = useAppSelector(selectFill); const onChangeFillColor = useCallback( (color: RgbColor) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerEntityList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerEntityList.tsx index 82bf728d0b..4e2cbd581c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerEntityList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerEntityList.tsx @@ -1,17 +1,21 @@ +import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.rasterLayers.entities.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return canvas.rasterLayers.entities.map(mapId).reverse(); +}); +const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => { + return selectedEntityIdentifier?.type === 'raster_layer'; }); export const RasterLayerEntityList = memo(() => { - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer')); + const isSelected = useAppSelector(selectIsSelected); const layerIds = useAppSelector(selectEntityIds); if (layerIds.length === 0) { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx index f844f3aa38..7ca7692276 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx @@ -1,7 +1,7 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasV2Slice'; +import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLightningBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx index 4e1a302233..e6458e44c8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx @@ -6,8 +6,8 @@ import { rgIPAdapterAdded, rgNegativePromptChanged, rgPositivePromptChanged, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -18,8 +18,8 @@ export const RegionalGuidanceAddPromptsIPAdapterButtons = () => { const dispatch = useAppDispatch(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntityOrThrow(canvasV2, entityIdentifier); + createMemoizedSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntityOrThrow(canvas, entityIdentifier); return { canAddPositivePrompt: entity?.positivePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges.tsx index be928090f1..8e77de7218 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges.tsx @@ -1,14 +1,19 @@ import { Badge } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; -import { memo } from 'react'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; export const RegionalGuidanceBadges = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const { t } = useTranslation(); - const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative); + const selectAutoNegative = useMemo( + () => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).autoNegative), + [entityIdentifier] + ); + const autoNegative = useAppSelector(selectAutoNegative); return ( <> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList.tsx index ef6faa51b1..a7271d10db 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList.tsx @@ -1,17 +1,21 @@ +import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidance'; import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.regions.entities.map(mapId).reverse(); +const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return canvas.regions.entities.map(mapId).reverse(); +}); +const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => { + return selectedEntityIdentifier?.type === 'raster_layer'; }); export const RegionalGuidanceEntityList = memo(() => { - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance')); + const isSelected = useAppSelector(selectIsSelected); const rgIds = useAppSelector(selectEntityIds); if (rgIds.length === 0) { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx index cc60a8a023..b11cc933bd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx @@ -1,4 +1,5 @@ import { Box, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct'; import { Weight } from 'features/controlLayers/components/common/Weight'; @@ -14,8 +15,8 @@ import { rgIPAdapterMethodChanged, rgIPAdapterModelChanged, rgIPAdapterWeightChanged, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { RGIPAdapterImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; @@ -34,11 +35,16 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN const onDeleteIPAdapter = useCallback(() => { dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId })); }, [dispatch, entityIdentifier, ipAdapterId]); - const ipAdapter = useAppSelector((s) => { - const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2, entityIdentifier, ipAdapterId); - assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); - return ipa; - }); + const selectIPAdapter = useMemo( + () => + createSelector(selectCanvasSlice, (canvas) => { + const ipAdapter = selectRegionalGuidanceIPAdapter(canvas, entityIdentifier, ipAdapterId); + assert(ipAdapter, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); + return ipAdapter; + }), + [entityIdentifier, ipAdapterId] + ); + const ipAdapter = useAppSelector(selectIPAdapter); const onChangeBeginEndStepPct = useCallback( (beginEndStepPct: [number, number]) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapters.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapters.tsx index ae47edb3e4..b9c9b0bcc0 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapters.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapters.tsx @@ -4,7 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { Fragment, memo, useMemo } from 'react'; export const RegionalGuidanceIPAdapters = memo(() => { @@ -12,8 +12,8 @@ export const RegionalGuidanceIPAdapters = memo(() => { const selectIPAdapterIds = useMemo( () => - createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - const ipAdapterIds = selectEntityOrThrow(canvasV2, entityIdentifier).ipAdapters.map(({ id }) => id); + createMemoizedSelector(selectCanvasSlice, (canvas) => { + const ipAdapterIds = selectEntityOrThrow(canvas, entityIdentifier).ipAdapters.map(({ id }) => id); if (ipAdapterIds.length === 0) { return EMPTY_ARRAY; } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx index 331401b705..b950cf9995 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx @@ -1,20 +1,25 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import type { FillStyle, RgbColor } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; export const RegionalGuidanceMaskFillColorPicker = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const { t } = useTranslation(); const dispatch = useAppDispatch(); - const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill); + const selectFill = useMemo( + () => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).fill), + [entityIdentifier] + ); + const fill = useAppSelector(selectFill); const onChangeFillColor = useCallback( (color: RgbColor) => { dispatch(rgFillColorChanged({ entityIdentifier, color })); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx index d47b6cc0f9..256f46086d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx @@ -6,8 +6,8 @@ import { rgIPAdapterAdded, rgNegativePromptChanged, rgPositivePromptChanged, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -17,8 +17,8 @@ export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => { const dispatch = useAppDispatch(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntity(canvasV2, entityIdentifier); + createMemoizedSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntity(canvas, entityIdentifier); return { canAddPositivePrompt: entity?.positivePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAutoNegative.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAutoNegative.tsx index 955cea7d66..0cd3480fd9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAutoNegative.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAutoNegative.tsx @@ -1,9 +1,10 @@ import { MenuItem } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; -import { memo, useCallback } from 'react'; +import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiSelectionInverseBold } from 'react-icons/pi'; @@ -11,7 +12,11 @@ export const RegionalGuidanceMenuItemsAutoNegative = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const { t } = useTranslation(); const dispatch = useAppDispatch(); - const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative); + const selectAutoNegative = useMemo( + () => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).autoNegative), + [entityIdentifier] + ); + const autoNegative = useAppSelector(selectAutoNegative); const onClick = useCallback(() => { dispatch(rgAutoNegativeToggled({ entityIdentifier })); }, [dispatch, entityIdentifier]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceNegativePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceNegativePrompt.tsx index b83539fbc8..be2039abda 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceNegativePrompt.tsx @@ -1,19 +1,25 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { memo, useCallback, useRef } from 'react'; +import { memo, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; export const RegionalGuidanceNegativePrompt = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); - const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt ?? ''); + const selectPrompt = useMemo( + () => + createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).negativePrompt ?? ''), + [entityIdentifier] + ); + const prompt = useAppSelector(selectPrompt); const dispatch = useAppDispatch(); const textareaRef = useRef(null); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidancePositivePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidancePositivePrompt.tsx index e699dcf8b2..bf72f6f7c4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidancePositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidancePositivePrompt.tsx @@ -1,19 +1,25 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { memo, useCallback, useRef } from 'react'; +import { memo, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; export const RegionalGuidancePositivePrompt = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); - const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt ?? ''); + const selectPrompt = useMemo( + () => + createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).positivePrompt ?? ''), + [entityIdentifier] + ); + const prompt = useAppSelector(selectPrompt); const dispatch = useAppDispatch(); const textareaRef = useRef(null); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings.tsx index b2c1e3db98..e2be6ecff6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings.tsx @@ -1,10 +1,11 @@ import { Divider } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper'; import { RegionalGuidanceAddPromptsIPAdapterButtons } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { selectEntityOrThrow } from 'features/controlLayers/store/selectors'; -import { memo } from 'react'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { memo, useMemo } from 'react'; import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters'; import { RegionalGuidanceNegativePrompt } from './RegionalGuidanceNegativePrompt'; @@ -12,30 +13,38 @@ import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt export const RegionalGuidanceSettings = memo(() => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); - const hasPositivePrompt = useAppSelector( - (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt !== null + const selectFlags = useMemo( + () => + createMemoizedSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntityOrThrow(canvas, entityIdentifier); + return { + hasPositivePrompt: entity.positivePrompt !== null, + hasNegativePrompt: entity.negativePrompt !== null, + hasIPAdapters: entity.ipAdapters.length > 0, + }; + }), + [entityIdentifier] ); - const hasNegativePrompt = useAppSelector( - (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt !== null - ); - const hasIPAdapters = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapters.length > 0); + const flags = useAppSelector(selectFlags); return ( - {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } - {hasPositivePrompt && ( + {!flags.hasPositivePrompt && !flags.hasNegativePrompt && !flags.hasIPAdapters && ( + + )} + {flags.hasPositivePrompt && ( <> - {(hasNegativePrompt || hasIPAdapters) && } + {(flags.hasNegativePrompt || flags.hasIPAdapters) && } )} - {hasNegativePrompt && ( + {flags.hasNegativePrompt && ( <> - {hasIPAdapters && } + {flags.hasIPAdapters && } )} - {hasIPAdapters && } + {flags.hasIPAdapters && } ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsResetButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsResetButton.tsx index a40a3f3aae..47c5a38084 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsResetButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsResetButton.tsx @@ -1,6 +1,6 @@ import { Button } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { canvasReset } from 'features/controlLayers/store/canvasV2Slice'; +import { canvasReset } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx index 170416619d..1a998fd3b8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx @@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks'; import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; -import { isDrawableEntityType } from 'features/controlLayers/store/types'; +import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors'; import { memo, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -13,15 +14,10 @@ export const ToolBrushButton = memo(() => { const { t } = useTranslation(); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasSession.isStaging); + const isStaging = useAppSelector(selectIsStaging); const selectBrush = useSelectTool('brush'); const isSelected = useToolIsSelected('brush'); - const isDrawingToolAllowed = useAppSelector((s) => { - if (!s.canvasV2.selectedEntityIdentifier?.type) { - return false; - } - return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); - }); + const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx index 2ac15df287..be3004e7b5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx @@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks'; import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; -import { isDrawableEntityType } from 'features/controlLayers/store/types'; +import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors'; import { memo, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -13,15 +14,10 @@ export const ToolEraserButton = memo(() => { const { t } = useTranslation(); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasSession.isStaging); + const isStaging = useAppSelector(selectIsStaging); const selectEraser = useSelectTool('eraser'); const isSelected = useToolIsSelected('eraser'); - const isDrawingToolAllowed = useAppSelector((s) => { - if (!s.canvasV2.selectedEntityIdentifier?.type) { - return false; - } - return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); - }); + const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx index f1422e9311..4c1340e15f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx @@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks'; import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; -import { isDrawableEntityType } from 'features/controlLayers/store/types'; +import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors'; import { memo, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -15,13 +16,8 @@ export const ToolMoveButton = memo(() => { const isTransforming = useIsTransforming(); const selectMove = useSelectTool('move'); const isSelected = useToolIsSelected('move'); - const isStaging = useAppSelector((s) => s.canvasSession.isStaging); - const isDrawingToolAllowed = useAppSelector((s) => { - if (!s.canvasV2.selectedEntityIdentifier?.type) { - return false; - } - return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); - }); + const isStaging = useAppSelector(selectIsStaging); + const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx index 8e0bfa083f..801d603b40 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx @@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks'; import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; -import { isDrawableEntityType } from 'features/controlLayers/store/types'; +import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors'; import { memo, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -15,13 +16,8 @@ export const ToolRectButton = memo(() => { const isSelected = useToolIsSelected('rect'); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasSession.isStaging); - const isDrawingToolAllowed = useAppSelector((s) => { - if (!s.canvasV2.selectedEntityIdentifier?.type) { - return false; - } - return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); - }); + const isStaging = useAppSelector(selectIsStaging); + const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx index 0d0b8651c1..df90d86496 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx @@ -5,22 +5,25 @@ import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { PiArrowClockwiseBold, PiArrowCounterClockwiseBold } from 'react-icons/pi'; +import { useDispatch } from 'react-redux'; +import { ActionCreators } from 'redux-undo'; export const UndoRedoButtonGroup = memo(() => { const { t } = useTranslation(); + const dispatch = useDispatch(); - const mayUndo = useAppSelector(() => false); + const mayUndo = useAppSelector(() => true); const handleUndo = useCallback(() => { // TODO(psyche): Implement undo - // dispatch(undo()); - }, []); + dispatch(ActionCreators.undo()); + }, [dispatch]); useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]); - const mayRedo = useAppSelector(() => false); + const mayRedo = useAppSelector(() => true); const handleRedo = useCallback(() => { // TODO(psyche): Implement redo - // dispatch(redo()); - }, []); + dispatch(ActionCreators.redo()); + }, [dispatch]); useHotkeys(['meta+shift+z', 'ctrl+shift+z'], handleRedo, { enabled: mayRedo, preventDefault: true }, [ mayRedo, handleRedo, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityContainer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityContainer.tsx index f55283c1c0..559eec70cb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityContainer.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityContainer.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected'; import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor'; -import { entitySelected } from 'features/controlLayers/store/canvasV2Slice'; +import { entitySelected } from 'features/controlLayers/store/canvasSlice'; import type { PropsWithChildren } from 'react'; import { memo, useCallback } from 'react'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityEnabledToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityEnabledToggle.tsx index bd74304418..77ccc94812 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityEnabledToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityEnabledToggle.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { stopPropagation } from 'common/util/stopPropagation'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled'; -import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiCheckBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx index 1582cce92d..8555b65e64 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx @@ -7,41 +7,41 @@ import { entityArrangedForwardOne, entityArrangedToBack, entityArrangedToFront, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; -import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; +import type { CanvasEntityIdentifier, CanvasState } from 'features/controlLayers/store/types'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi'; const getIndexAndCount = ( - canvasV2: CanvasV2State, + canvas: CanvasState, { id, type }: CanvasEntityIdentifier ): { index: number; count: number } => { if (type === 'raster_layer') { return { - index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id), - count: canvasV2.rasterLayers.entities.length, + index: canvas.rasterLayers.entities.findIndex((entity) => entity.id === id), + count: canvas.rasterLayers.entities.length, }; } else if (type === 'control_layer') { return { - index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id), - count: canvasV2.controlLayers.entities.length, + index: canvas.controlLayers.entities.findIndex((entity) => entity.id === id), + count: canvas.controlLayers.entities.length, }; } else if (type === 'regional_guidance') { return { - index: canvasV2.regions.entities.findIndex((entity) => entity.id === id), - count: canvasV2.regions.entities.length, + index: canvas.regions.entities.findIndex((entity) => entity.id === id), + count: canvas.regions.entities.length, }; } else if (type === 'inpaint_mask') { return { - index: canvasV2.inpaintMasks.entities.findIndex((entity) => entity.id === id), - count: canvasV2.inpaintMasks.entities.length, + index: canvas.inpaintMasks.entities.findIndex((entity) => entity.id === id), + count: canvas.inpaintMasks.entities.length, }; } else if (type === 'ip_adapter') { return { - index: canvasV2.ipAdapters.entities.findIndex((entity) => entity.id === id), - count: canvasV2.ipAdapters.entities.length, + index: canvas.ipAdapters.entities.findIndex((entity) => entity.id === id), + count: canvas.ipAdapters.entities.length, }; } else { return { @@ -57,8 +57,8 @@ export const CanvasEntityMenuItemsArrange = memo(() => { const entityIdentifier = useEntityIdentifierContext(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - const { index, count } = getIndexAndCount(canvasV2, entityIdentifier); + createMemoizedSelector(selectCanvasSlice, (canvas) => { + const { index, count } = getIndexAndCount(canvas, entityIdentifier); return { canMoveForwardOne: index < count - 1, canMoveBackwardOne: index > 0, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx index 24d9a32682..29b6ec5c2f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx @@ -1,7 +1,7 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice'; +import { entityDeleted } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate.tsx index dd84a84f42..36a4f18fd5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate.tsx @@ -1,7 +1,7 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { entityDuplicated } from 'features/controlLayers/store/canvasV2Slice'; +import { entityDuplicated } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiCopyFill } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityOpacity.tsx index dc5c589510..8bac01f75b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityOpacity.tsx @@ -15,8 +15,12 @@ import { } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { snapToNearest } from 'features/controlLayers/konva/util'; -import { entityOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntity } from 'features/controlLayers/store/selectors'; +import { entityOpacityChanged } from 'features/controlLayers/store/canvasSlice'; +import { + selectCanvasSlice, + selectEntity, + selectSelectedEntityIdentifier, +} from 'features/controlLayers/store/selectors'; import { isDrawableEntity } from 'features/controlLayers/store/types'; import { clamp, round } from 'lodash-es'; import type { KeyboardEvent } from 'react'; @@ -59,13 +63,14 @@ const snapCandidates = marks.slice(1, marks.length - 1); export const CanvasEntityOpacity = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier); + const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); const opacity = useAppSelector((s) => { - const selectedEntityIdentifier = s.canvasV2.selectedEntityIdentifier; + const selectedEntityIdentifier = selectSelectedEntityIdentifier(s); if (!selectedEntityIdentifier) { return null; } - const selectedEntity = selectEntity(s.canvasV2, selectedEntityIdentifier); + const canvas = selectCanvasSlice(s); + const selectedEntity = selectEntity(canvas, selectedEntityIdentifier); if (!selectedEntity) { return null; } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityPreviewImage.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityPreviewImage.tsx index 2a7630c1a8..31dccfd2b6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityPreviewImage.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityPreviewImage.tsx @@ -5,7 +5,7 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import { memo, useEffect, useMemo, useRef } from 'react'; import { useSelector } from 'react-redux'; @@ -18,7 +18,7 @@ export const CanvasEntityPreviewImage = memo(() => { const adapter = useEntityAdapter(); const selectMaskColor = useMemo( () => - createSelector(selectCanvasV2Slice, (state) => { + createSelector(selectCanvasSlice, (state) => { const entity = selectEntity(state, entityIdentifier); if (!entity) { return null; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTitleEdit.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTitleEdit.tsx index 91ae91e2b8..8883b6615f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTitleEdit.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTitleEdit.tsx @@ -4,7 +4,7 @@ import { useBoolean } from 'common/hooks/useBoolean'; import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle'; -import { entityNameChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { entityNameChanged } from 'features/controlLayers/store/canvasSlice'; import type { ChangeEvent, KeyboardEvent } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle.tsx index 1e13042d3c..25ab3b9a42 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle.tsx @@ -2,7 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden'; import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString'; -import { allEntitiesOfTypeIsHiddenToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { allEntitiesOfTypeIsHiddenToggled } from 'features/controlLayers/store/canvasSlice'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts index 9f2261c887..3b75e2696e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts @@ -1,14 +1,14 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; -import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { entityDeleted } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; const selectSelectedEntityIdentifier = createMemoizedSelector( - selectCanvasV2Slice, - (canvasV2State) => canvasV2State.selectedEntityIdentifier + selectCanvasSlice, + (canvasState) => canvasState.selectedEntityIdentifier ); export function useCanvasDeleteLayerHotkey() { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts index c752a7348c..5418e0c2ec 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts @@ -1,14 +1,14 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; -import { entityReset } from 'features/controlLayers/store/canvasV2Slice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { entityReset } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; const selectSelectedEntityIdentifier = createMemoizedSelector( - selectCanvasV2Slice, - (canvasV2State) => canvasV2State.selectedEntityIdentifier + selectCanvasSlice, + (canvasState) => canvasState.selectedEntityIdentifier ); export function useCanvasResetLayerHotkey() { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsEnabled.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsEnabled.ts index 022cbeb212..938364d91d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsEnabled.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsEnabled.ts @@ -1,14 +1,14 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntityIsEnabled = (entityIdentifier: CanvasEntityIdentifier) => { const selectIsEnabled = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntity(canvasV2, entityIdentifier); + createSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntity(canvas, entityIdentifier); if (!entity) { return false; } else { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsSelected.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsSelected.ts index 80fe4a61b4..b07bce98a5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsSelected.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityIsSelected.ts @@ -1,12 +1,18 @@ +import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; +import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntityIsSelected = (entityIdentifier: CanvasEntityIdentifier) => { - const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier); - const isSelected = useMemo(() => { - return selectedEntityIdentifier?.id === entityIdentifier.id; - }, [selectedEntityIdentifier, entityIdentifier.id]); + const selectIsSelected = useMemo( + () => + createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => { + return selectedEntityIdentifier?.id === entityIdentifier.id; + }), + [entityIdentifier.id] + ); + const isSelected = useAppSelector(selectIsSelected); return isSelected; }; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityObjectCount.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityObjectCount.ts index 62f3a8e8d0..fe9fbedbd1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityObjectCount.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityObjectCount.ts @@ -1,14 +1,14 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import { type CanvasEntityIdentifier, isDrawableEntity } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntityObjectCount = (entityIdentifier: CanvasEntityIdentifier) => { const selectObjectCount = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntity(canvasV2, entityIdentifier); + createSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntity(canvas, entityIdentifier); if (!entity) { return 0; } else if (isDrawableEntity(entity)) { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntitySelectionColor.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntitySelectionColor.ts index 15061d28b8..9e758d0046 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntitySelectionColor.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntitySelectionColor.ts @@ -1,15 +1,15 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntitySelectionColor = (entityIdentifier: CanvasEntityIdentifier) => { const selectSelectionColor = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntity(canvasV2, entityIdentifier); + createSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntity(canvas, entityIdentifier); if (!entity) { return 'base.400'; } else if (entity.type === 'inpaint_mask') { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTitle.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTitle.ts index c5649963d8..d054083e16 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTitle.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTitle.ts @@ -1,15 +1,15 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount'; -import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { assert } from 'tsafe'; const createSelectName = (entityIdentifier: CanvasEntityIdentifier) => - createSelector(selectCanvasV2Slice, (canvasV2) => { - const entity = selectEntity(canvasV2, entityIdentifier); + createSelector(selectCanvasSlice, (canvas) => { + const entity = selectEntity(canvas, entityIdentifier); if (!entity) { return null; } diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeCount.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeCount.ts index e770f220fb..e8bb5167b8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeCount.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeCount.ts @@ -1,24 +1,24 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntityTypeCount = (type: CanvasEntityIdentifier['type']): number => { const selectEntityCount = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { + createSelector(selectCanvasSlice, (canvas) => { switch (type) { case 'control_layer': - return canvasV2.controlLayers.entities.length; + return canvas.controlLayers.entities.length; case 'raster_layer': - return canvasV2.rasterLayers.entities.length; + return canvas.rasterLayers.entities.length; case 'inpaint_mask': - return canvasV2.inpaintMasks.entities.length; + return canvas.inpaintMasks.entities.length; case 'regional_guidance': - return canvasV2.regions.entities.length; + return canvas.regions.entities.length; case 'ip_adapter': - return canvasV2.ipAdapters.entities.length; + return canvas.ipAdapters.entities.length; default: return 0; } diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeIsHidden.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeIsHidden.ts index 0e867be2be..04ed438b62 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeIsHidden.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useEntityTypeIsHidden.ts @@ -1,22 +1,22 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { useMemo } from 'react'; export const useEntityTypeIsHidden = (type: CanvasEntityIdentifier['type']): boolean => { const selectIsHidden = useMemo( () => - createSelector(selectCanvasV2Slice, (canvasV2) => { + createSelector(selectCanvasSlice, (canvas) => { switch (type) { case 'control_layer': - return canvasV2.controlLayers.isHidden; + return canvas.controlLayers.isHidden; case 'raster_layer': - return canvasV2.rasterLayers.isHidden; + return canvas.rasterLayers.isHidden; case 'inpaint_mask': - return canvasV2.inpaintMasks.isHidden; + return canvas.inpaintMasks.isHidden; case 'regional_guidance': - return canvasV2.regions.isHidden; + return canvas.regions.isHidden; case 'ip_adapter': default: return false; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts index 3c2223ecf8..8f59935d47 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts @@ -1,7 +1,7 @@ import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { deepClone } from 'common/util/deepClone'; -import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier, ControlNetConfig, @@ -16,8 +16,8 @@ import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/a export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => { const selectControlAdapter = useMemo( () => - createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => { - const layer = selectEntityOrThrow(canvasV2, entityIdentifier); + createMemoizedAppSelector(selectCanvasSlice, (canvas) => { + const layer = selectEntityOrThrow(canvas, entityIdentifier); return layer.controlAdapter; }), [entityIdentifier] diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts index 6be2f8d248..ce994085b7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts @@ -6,7 +6,7 @@ import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase' import { getPrefixedId } from 'features/controlLayers/konva/util'; import type { CanvasSessionState } from 'features/controlLayers/store/canvasSessionSlice'; import type { CanvasSettingsState } from 'features/controlLayers/store/canvasSettingsSlice'; -import type { CanvasV2State } from 'features/controlLayers/store/types'; +import type { CanvasState } from 'features/controlLayers/store/types'; import type { Logger } from 'roarr'; export class CanvasRenderingModule extends CanvasModuleBase { @@ -18,7 +18,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { manager: CanvasManager; subscriptions = new Set<() => void>(); - state: CanvasV2State | null = null; + state: CanvasState | null = null; settings: CanvasSettingsState | null = null; session: CanvasSessionState | null = null; @@ -119,7 +119,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderRasterLayers = async (state: CanvasV2State, prevState: CanvasV2State | null) => { + renderRasterLayers = async (state: CanvasState, prevState: CanvasState | null) => { const adapterMap = this.manager.adapters.rasterLayers; if (!prevState || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) { @@ -148,7 +148,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderControlLayers = async (prevState: CanvasV2State | null, state: CanvasV2State) => { + renderControlLayers = async (prevState: CanvasState | null, state: CanvasState) => { const adapterMap = this.manager.adapters.controlLayers; if (!prevState || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) { @@ -177,7 +177,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderRegionalGuidance = async (prevState: CanvasV2State | null, state: CanvasV2State) => { + renderRegionalGuidance = async (prevState: CanvasState | null, state: CanvasState) => { const adapterMap = this.manager.adapters.regionMasks; if (!prevState || state.regions.isHidden !== prevState.regions.isHidden) { @@ -211,7 +211,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderInpaintMasks = async (state: CanvasV2State, prevState: CanvasV2State | null) => { + renderInpaintMasks = async (state: CanvasState, prevState: CanvasState | null) => { const adapterMap = this.manager.adapters.inpaintMasks; if (!prevState || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) { @@ -245,7 +245,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderBbox = (state: CanvasV2State, prevState: CanvasV2State | null) => { + renderBbox = (state: CanvasState, prevState: CanvasState | null) => { if (!prevState || state.bbox !== prevState.bbox) { this.manager.preview.bbox.render(); } @@ -257,7 +257,7 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - arrangeEntities = (state: CanvasV2State, prevState: CanvasV2State | null) => { + arrangeEntities = (state: CanvasState, prevState: CanvasState | null) => { if ( !prevState || state.rasterLayers.entities !== prevState.rasterLayers.entities || diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts index eff144968c..aa3be585f2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts @@ -14,8 +14,8 @@ import { entityRectAdded, entityReset, entitySelected, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectAllRenderableEntities } from 'features/controlLayers/store/selectors'; +} from 'features/controlLayers/store/canvasSlice'; +import { selectAllRenderableEntities, selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { brushWidthChanged, eraserWidthChanged, @@ -100,7 +100,7 @@ export class CanvasStateApiModule extends CanvasModuleBase { // Reminder - use arrow functions to avoid binding issues getCanvasState = () => { - return this.store.getState().canvasV2; + return selectCanvasSlice(this.store.getState()); }; resetEntity = (arg: EntityIdentifierPayload) => { this.store.dispatch(entityReset(arg)); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts index 038e448fd5..af328d944d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts @@ -1,14 +1,14 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import { deepClone } from 'common/util/deepClone'; import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple'; -import type { BoundingBoxScaleMethod, CanvasV2State, Dimensions } from 'features/controlLayers/store/types'; +import type { BoundingBoxScaleMethod, CanvasState, Dimensions } from 'features/controlLayers/store/types'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants'; import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types'; import type { IRect } from 'konva/lib/types'; -const syncScaledSize = (state: CanvasV2State) => { +const syncScaledSize = (state: CanvasState) => { if (state.bbox.scaleMethod === 'auto') { const { width, height } = state.bbox.rect; state.bbox.scaledSize = getScaledBoundingBoxDimensions({ width, height }, state.bbox.optimalDimension); @@ -116,4 +116,4 @@ export const bboxReducers = { syncScaledSize(state); }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts index f8d70511d0..6947ffd2df 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts @@ -1,6 +1,6 @@ -import { createAction, createSlice, type PayloadAction } from '@reduxjs/toolkit'; -import type { PersistConfig } from 'app/store/store'; -import { canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; +import { createAction, createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { PersistConfig, RootState } from 'app/store/store'; +import { canvasSlice } from 'features/controlLayers/store/canvasSlice'; import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types'; export type CanvasSessionState = { @@ -79,5 +79,9 @@ export const canvasSessionPersistConfig: PersistConfig = { persistDenylist: [], }; export const sessionStagingAreaImageAccepted = createAction<{ index: number }>( - `${canvasV2Slice.name}/sessionStagingAreaImageAccepted` + `${canvasSlice.name}/sessionStagingAreaImageAccepted` ); + +export const selectCanvasSessionSlice = (s: RootState) => s.canvasSession; + +export const selectIsStaging = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.isStaging); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts index 18e8340d33..627f1f28d3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts @@ -1,5 +1,5 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; -import type { PersistConfig } from 'app/store/store'; +import type { PersistConfig, RootState } from 'app/store/store'; export type CanvasSettingsState = { imageSmoothing: boolean; @@ -51,3 +51,4 @@ export const canvasSettingsPersistConfig: PersistConfig = { migrate, persistDenylist: [], }; +export const selectCanvasSettingsSlice = (s: RootState) => s.canvasSettings; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts similarity index 97% rename from invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts rename to invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts index 92ac8162d2..189f6e32c3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts @@ -22,7 +22,7 @@ import { assert } from 'tsafe'; import type { CanvasEntityIdentifier, - CanvasV2State, + CanvasState, EntityBrushLineAddedPayload, EntityEraserLineAddedPayload, EntityIdentifierPayload, @@ -32,7 +32,7 @@ import type { } from './types'; import { getEntityIdentifier, isDrawableEntity } from './types'; -const initialState: CanvasV2State = { +const initialState: CanvasState = { _version: 3, selectedEntityIdentifier: null, rasterLayers: { @@ -64,8 +64,8 @@ const initialState: CanvasV2State = { }, }; -export const canvasV2Slice = createSlice({ - name: 'canvasV2', +export const canvasSlice = createSlice({ + name: 'canvas', initialState, reducers: { // undoable canvas state @@ -217,7 +217,7 @@ export const canvasV2Slice = createSlice({ entityDeleted: (state, action: PayloadAction) => { const { entityIdentifier } = action.payload; - let selectedEntityIdentifier: CanvasV2State['selectedEntityIdentifier'] = null; + let selectedEntityIdentifier: CanvasState['selectedEntityIdentifier'] = null; const allEntities = selectAllEntities(state); const index = allEntities.findIndex((entity) => entity.id === entityIdentifier.id); const nextIndex = allEntities.length > 1 ? (index + 1) % allEntities.length : -1; @@ -438,15 +438,15 @@ export const { // inpaintMaskRecalled, inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged, -} = canvasV2Slice.actions; +} = canvasSlice.actions; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ const migrate = (state: any): any => { return state; }; -export const canvasV2PersistConfig: PersistConfig = { - name: canvasV2Slice.name, +export const canvasPersistConfig: PersistConfig = { + name: canvasSlice.name, initialState, migrate, persistDenylist: [], diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts index 7ed4a341ce..3563f7e546 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts @@ -9,7 +9,7 @@ import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/ import type { CanvasControlLayerState, CanvasRasterLayerState, - CanvasV2State, + CanvasState, ControlModeV2, ControlNetConfig, EntityIdentifierPayload, @@ -159,4 +159,4 @@ export const controlLayersReducers = { } layer.withTransparencyEffect = !layer.withTransparencyEffect; }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts index 9056f082a6..6004ccfc36 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts @@ -3,7 +3,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util'; import { selectEntity } from 'features/controlLayers/store/selectors'; import type { CanvasInpaintMaskState, - CanvasV2State, + CanvasState, EntityIdentifierPayload, FillStyle, RgbColor, @@ -68,4 +68,4 @@ export const inpaintMaskReducers = { } entity.fill.style = style; }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts index d3bb67c9ce..fcadb3480e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersReducers.ts @@ -8,7 +8,7 @@ import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; import type { CanvasIPAdapterState, - CanvasV2State, + CanvasState, CLIPVisionModelV2, EntityIdentifierPayload, IPMethodV2, @@ -104,4 +104,4 @@ export const ipAdaptersReducers = { } entity.ipAdapter.beginEndStepPct = beginEndStepPct; }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts index 0db696b84e..25ea31f9cc 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts @@ -4,7 +4,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util'; import { selectEntity } from 'features/controlLayers/store/selectors'; import { merge } from 'lodash-es'; -import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasV2State, EntityIdentifierPayload } from './types'; +import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasState, EntityIdentifierPayload } from './types'; import { getEntityIdentifier, initialControlNet } from './types'; export const rasterLayersReducers = { @@ -67,4 +67,4 @@ export const rasterLayersReducers = { payload: { ...payload, newId: getPrefixedId('control_layer') }, }), }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts index ff2729fcc5..87687fb395 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts @@ -3,7 +3,7 @@ import { deepClone } from 'common/util/deepClone'; import { getPrefixedId } from 'features/controlLayers/konva/util'; import { selectEntity, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors'; import type { - CanvasV2State, + CanvasState, CLIPVisionModelV2, EntityIdentifierPayload, FillStyle, @@ -29,7 +29,7 @@ const DEFAULT_MASK_COLORS: RgbColor[] = [ { r: 161, g: 120, b: 214 }, // rgb(161, 120, 214) ]; -const getRGMaskFill = (state: CanvasV2State): RgbColor => { +const getRGMaskFill = (state: CanvasState): RgbColor => { const lastFill = state.regions.entities.slice(-1)[0]?.fill.color; let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill)); if (i === -1) { @@ -249,4 +249,4 @@ export const regionsReducers = { } ipAdapter.clipVisionModel = clipVisionModel; }, -} satisfies SliceCaseReducers; +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts index ffbd30867d..abd7df40e8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts @@ -1,22 +1,23 @@ import { createSelector } from '@reduxjs/toolkit'; import type { RootState } from 'app/store/store'; import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; -import type { - CanvasControlLayerState, - CanvasEntityIdentifier, - CanvasEntityState, - CanvasInpaintMaskState, - CanvasRasterLayerState, - CanvasRegionalGuidanceState, - CanvasV2State, +import { + type CanvasControlLayerState, + type CanvasEntityIdentifier, + type CanvasEntityState, + type CanvasInpaintMaskState, + type CanvasRasterLayerState, + type CanvasRegionalGuidanceState, + type CanvasState, + isDrawableEntityType, } from 'features/controlLayers/store/types'; import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { assert } from 'tsafe'; /** - * Selects the canvasV2 slice from the root state + * Selects the canvas slice from the root state */ -export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; +export const selectCanvasSlice = (state: RootState) => state.canvas.present; /** * Selects the total canvas entity count: @@ -28,13 +29,13 @@ export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; * * It does not check for validity of the entities. */ -export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => { +export const selectEntityCount = createSelector(selectCanvasSlice, (canvas) => { return ( - canvasV2.regions.entities.length + - canvasV2.ipAdapters.entities.length + - canvasV2.rasterLayers.entities.length + - canvasV2.controlLayers.entities.length + - canvasV2.inpaintMasks.entities.length + canvas.regions.entities.length + + canvas.ipAdapters.entities.length + + canvas.rasterLayers.entities.length + + canvas.controlLayers.entities.length + + canvas.inpaintMasks.entities.length ); }); @@ -46,11 +47,11 @@ export const selectOptimalDimension = createSelector(selectParamsSlice, (params) }); /** - * Selects a single entity from the canvasV2 slice. If the entity identifier is narrowed to a specific type, the + * Selects a single entity from the canvas slice. If the entity identifier is narrowed to a specific type, the * return type will be narrowed as well. */ export function selectEntity( - state: CanvasV2State, + state: CanvasState, entityIdentifier: T ): Extract | undefined { const { id, type } = entityIdentifier; @@ -80,11 +81,11 @@ export function selectEntity( } /** - * Selected an entity from the canvasV2 slice. If the entity is not found, an error is thrown. + * Selected an entity from the canvas slice. If the entity is not found, an error is thrown. * Wrapper around {@link selectEntity}. */ export function selectEntityOrThrow( - state: CanvasV2State, + state: CanvasState, entityIdentifier: T ): Extract { const entity = selectEntity(state, entityIdentifier); @@ -96,7 +97,7 @@ export function selectEntityOrThrow( * Selects all entities of the given type. */ export function selectAllEntitiesOfType( - state: CanvasV2State, + state: CanvasState, type: T ): Extract[] { let entities: CanvasEntityState[] = []; @@ -126,7 +127,7 @@ export function selectAllEntitiesOfType( /** * Selects all entities, in the order they are displayed in the list. */ -export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] { +export function selectAllEntities(state: CanvasState): CanvasEntityState[] { // These are in the same order as they are displayed in the list! return [ ...state.inpaintMasks.entities.toReversed(), @@ -145,7 +146,7 @@ export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] { * - Regional guidance */ export function selectAllRenderableEntities( - state: CanvasV2State + state: CanvasState ): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] { return [ ...state.rasterLayers.entities, @@ -159,7 +160,7 @@ export function selectAllRenderableEntities( * Selects the IP adapter for the specific Regional Guidance layer. */ export function selectRegionalGuidanceIPAdapter( - state: CanvasV2State, + state: CanvasState, entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, ipAdapterId: string ) { @@ -169,3 +170,19 @@ export function selectRegionalGuidanceIPAdapter( } return entity.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId); } + +export const selectSelectedEntityIdentifier = createSelector( + selectCanvasSlice, + (canvas) => canvas.selectedEntityIdentifier +); + +export const selectIsSelectedEntityDrawable = createSelector( + selectSelectedEntityIdentifier, + (selectedEntityIdentifier) => { + if (!selectedEntityIdentifier) { + return false; + } + return isDrawableEntityType(selectedEntityIdentifier.type); + } +); + diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index cb504d7768..3543781007 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -692,7 +692,7 @@ export type StagingAreaImage = { export type SessionMode = 'generate' | 'compose'; -export type CanvasV2State = { +export type CanvasState = { _version: 3; selectedEntityIdentifier: CanvasEntityIdentifier | null; inpaintMasks: { diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx index c42d92736c..afe2acff90 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx @@ -1,7 +1,7 @@ import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors'; import { @@ -20,11 +20,11 @@ import { useTranslation } from 'react-i18next'; import ImageUsageMessage from './ImageUsageMessage'; const selectImageUsages = createMemoizedSelector( - [selectDeleteImageModalSlice, selectNodesSlice, selectCanvasV2Slice, selectImageUsage], - (deleteImageModal, nodes, canvasV2, imagesUsage) => { + [selectDeleteImageModalSlice, selectNodesSlice, selectCanvasSlice, selectImageUsage], + (deleteImageModal, nodes, canvas, imagesUsage) => { const { imagesToDelete } = deleteImageModal; - const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) => getImageUsage(nodes, canvasV2, image_name)); + const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) => getImageUsage(nodes, canvas, image_name)); const imageUsageSummary: ImageUsage = { isLayerImage: some(allImageUsage, (i) => i.isLayerImage), diff --git a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts index 036c25b9c2..7aa602b522 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts +++ b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts @@ -1,6 +1,6 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; -import type { CanvasV2State } from 'features/controlLayers/store/types'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; +import type { CanvasState } from 'features/controlLayers/store/types'; import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import type { NodesState } from 'features/nodes/store/types'; @@ -10,14 +10,14 @@ import { some } from 'lodash-es'; import type { ImageUsage } from './types'; // TODO(psyche): handle image deletion (canvas sessions?) -export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_name: string) => { +export const getImageUsage = (nodes: NodesState, canvas: CanvasState, image_name: string) => { const isNodesImage = nodes.nodes .filter(isInvocationNode) .some((node) => some(node.data.inputs, (input) => isImageFieldInputInstance(input) && input.value?.image_name === image_name) ); - const isIPAdapterImage = canvasV2.ipAdapters.entities.some( + const isIPAdapterImage = canvas.ipAdapters.entities.some( ({ ipAdapter }) => ipAdapter.image?.image_name === image_name ); @@ -34,15 +34,15 @@ export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_ export const selectImageUsage = createMemoizedSelector( selectDeleteImageModalSlice, selectNodesSlice, - selectCanvasV2Slice, - (deleteImageModal, nodes, canvasV2) => { + selectCanvasSlice, + (deleteImageModal, nodes, canvas) => { const { imagesToDelete } = deleteImageModal; if (!imagesToDelete.length) { return []; } - const imagesUsage = imagesToDelete.map((i) => getImageUsage(nodes, canvasV2, i.image_name)); + const imagesUsage = imagesToDelete.map((i) => getImageUsage(nodes, canvas, i.image_name)); return imagesUsage; } 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 42ecf584d3..458f2fd78c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -13,7 +13,7 @@ import { import { skipToken } from '@reduxjs/toolkit/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import type { ImageUsage } from 'features/deleteImageModal/store/types'; @@ -39,8 +39,8 @@ const DeleteBoardModal = (props: Props) => { const selectImageUsageSummary = useMemo( () => - createMemoizedSelector([selectNodesSlice, selectCanvasV2Slice], (nodes, canvasV2) => { - const allImageUsage = (boardImageNames ?? []).map((imageName) => getImageUsage(nodes, canvasV2, imageName)); + createMemoizedSelector([selectNodesSlice, selectCanvasSlice], (nodes, canvas) => { + const allImageUsage = (boardImageNames ?? []).map((imageName) => getImageUsage(nodes, canvas, imageName)); const imageUsageSummary: ImageUsage = { isLayerImage: some(allImageUsage, (i) => i.isLayerImage), diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 71278e0d9c..a88bc54ee8 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -1,5 +1,5 @@ import { getStore } from 'app/store/nanostores/store'; -import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice'; import { loraAllDeleted, loraRecalled } from 'features/controlLayers/store/lorasSlice'; import { negativePrompt2Changed, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts index 5c72b548b0..a4d9804dcc 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts @@ -1,6 +1,6 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; -import type { CanvasV2State, Dimensions } from 'features/controlLayers/store/types'; +import type { CanvasState, Dimensions } from 'features/controlLayers/store/types'; import type { Graph } from 'features/nodes/util/graph/generation/Graph'; import { isEqual } from 'lodash-es'; import type { Invocation } from 'services/api/types'; @@ -13,7 +13,7 @@ export const addImageToImage = async ( vaeSource: Invocation<'main_model_loader' | 'sdxl_model_loader' | 'seamless' | 'vae_loader'>, originalSize: Dimensions, scaledSize: Dimensions, - bbox: CanvasV2State['bbox'], + bbox: CanvasState['bbox'], denoising_start: number, fp32: boolean ): Promise> => { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts index 3f8c4fbb02..f9fa00c5d6 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts @@ -1,6 +1,9 @@ import type { RootState } from 'app/store/store'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; +import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { Dimensions } from 'features/controlLayers/store/types'; import type { Graph } from 'features/nodes/util/graph/generation/Graph'; import { isEqual } from 'lodash-es'; @@ -21,8 +24,11 @@ export const addInpaint = async ( ): Promise> => { denoise.denoising_start = denoising_start; - const { params, canvasV2, canvasSession } = state; - const { bbox } = canvasV2; + const params = selectParamsSlice(state); + const canvasSession = selectCanvasSessionSlice(state); + const canvas = selectCanvasSlice(state); + + const { bbox } = canvas; const { mode } = canvasSession; const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts index c0f298bfbd..ecbc09f916 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts @@ -1,6 +1,9 @@ import type { RootState } from 'app/store/store'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; +import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import type { Dimensions } from 'features/controlLayers/store/types'; import type { Graph } from 'features/nodes/util/graph/generation/Graph'; import { getInfill } from 'features/nodes/util/graph/graphBuilderUtils'; @@ -22,8 +25,11 @@ export const addOutpaint = async ( ): Promise> => { denoise.denoising_start = denoising_start; - const { params, canvasV2, canvasSession } = state; - const { bbox } = canvasV2; + const params = selectParamsSlice(state); + const canvasSession = selectCanvasSessionSlice(state); + const canvas = selectCanvasSlice(state); + + const { bbox } = canvas; const { mode } = canvasSession; const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts index 73d3c9e85a..112fb20a60 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts @@ -2,6 +2,10 @@ import { logger } from 'app/logging/logger'; import type { RootState } from 'app/store/store'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; +import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice'; +import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters'; import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage'; @@ -31,8 +35,12 @@ export const buildSD1Graph = async ( const generationMode = manager.compositor.getGenerationMode(); log.debug({ generationMode }, 'Building SD1/SD2 graph'); - const { canvasV2, params, canvasSettings, canvasSession } = state; - const { bbox } = canvasV2; + const params = selectParamsSlice(state); + const canvasSession = selectCanvasSessionSlice(state); + const canvasSettings = selectCanvasSettingsSlice(state); + const canvas = selectCanvasSlice(state); + + const { bbox } = canvas; const { model, @@ -208,9 +216,9 @@ export const buildSD1Graph = async ( }); const controlNetResult = await addControlNets( manager, - state.canvasV2.controlLayers.entities, + canvas.controlLayers.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, controlNetCollector, modelConfig.base ); @@ -226,9 +234,9 @@ export const buildSD1Graph = async ( }); const t2iAdapterResult = await addT2IAdapters( manager, - state.canvasV2.controlLayers.entities, + canvas.controlLayers.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, t2iAdapterCollector, modelConfig.base ); @@ -242,13 +250,13 @@ export const buildSD1Graph = async ( type: 'collect', id: getPrefixedId('ip_adapter_collector'), }); - const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); + const ipAdapterResult = addIPAdapters(canvas.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); const regionsResult = await addRegions( manager, - state.canvasV2.regions.entities, + canvas.regions.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, modelConfig.base, denoise, posCond, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts index a85bd57a66..ddf8da0dcd 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts @@ -2,6 +2,10 @@ import { logger } from 'app/logging/logger'; import type { RootState } from 'app/store/store'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; +import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice'; +import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice'; +import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters'; import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage'; @@ -31,8 +35,12 @@ export const buildSDXLGraph = async ( const generationMode = manager.compositor.getGenerationMode(); log.debug({ generationMode }, 'Building SDXL graph'); - const { params, canvasV2, canvasSettings, canvasSession } = state; - const { bbox } = canvasV2; + const params = selectParamsSlice(state); + const canvasSession = selectCanvasSessionSlice(state); + const canvasSettings = selectCanvasSettingsSlice(state); + const canvas = selectCanvasSlice(state); + + const { bbox } = canvas; const { model, @@ -211,9 +219,9 @@ export const buildSDXLGraph = async ( }); const controlNetResult = await addControlNets( manager, - state.canvasV2.controlLayers.entities, + canvas.controlLayers.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, controlNetCollector, modelConfig.base ); @@ -229,9 +237,9 @@ export const buildSDXLGraph = async ( }); const t2iAdapterResult = await addT2IAdapters( manager, - state.canvasV2.controlLayers.entities, + canvas.controlLayers.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, t2iAdapterCollector, modelConfig.base ); @@ -245,13 +253,13 @@ export const buildSDXLGraph = async ( type: 'collect', id: getPrefixedId('ip_adapter_collector'), }); - const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); + const ipAdapterResult = addIPAdapters(canvas.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); const regionsResult = await addRegions( manager, - state.canvasV2.regions.entities, + canvas.regions.entities, g, - state.canvasV2.bbox.rect, + canvas.bbox.rect, modelConfig.base, denoise, posCond, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts index 91fa227055..f3fa234430 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts @@ -1,6 +1,6 @@ import type { RootState } from 'app/store/store'; import type { ParamsState } from 'features/controlLayers/store/paramsSlice'; -import type { CanvasV2State } from 'features/controlLayers/store/types'; +import type { CanvasState } from 'features/controlLayers/store/types'; import type { BoardField } from 'features/nodes/types/common'; import type { Graph } from 'features/nodes/util/graph/generation/Graph'; import { buildPresetModifiedPrompt } from 'features/stylePresets/hooks/usePresetModifiedPrompts'; @@ -62,7 +62,7 @@ export const getPresetModifiedPrompts = ( }; }; -export const getSizes = (bboxState: CanvasV2State['bbox']) => { +export const getSizes = (bboxState: CanvasState['bbox']) => { const originalSize = pick(bboxState.rect, 'width', 'height'); const scaledSize = ['auto', 'manual'].includes(bboxState.scaleMethod) ? bboxState.scaledSize : originalSize; return { originalSize, scaledSize }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx index 1a0d95d57a..46fd222ac9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx @@ -1,16 +1,20 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; -import { bboxScaleMethodChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxScaleMethodChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { isBoundingBoxScaleMethod } from 'features/controlLayers/store/types'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +const selectScaleMethod = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod); + const ParamScaleBeforeProcessing = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const scaleMethod = useAppSelector((s) => s.canvasV2.bbox.scaleMethod); + const scaleMethod = useAppSelector(selectScaleMethod); const OPTIONS: ComboboxOption[] = useMemo( () => [ diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight.tsx index 5d35387fd8..01569a83b0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight.tsx @@ -1,22 +1,26 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { selectConfigSlice } from 'features/system/store/configSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +const selectIsManual = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod === 'manual'); +const selectScaledHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.height); +const selectScaledBoundingBoxHeightConfig = createSelector( + selectConfigSlice, + (config) => config.sd.scaledBoundingBoxHeight +); + const ParamScaledHeight = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const optimalDimension = useAppSelector(selectOptimalDimension); - const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual'); - const height = useAppSelector((s) => s.canvasV2.bbox.scaledSize.height); - const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMin); - const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMax); - const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMin); - const numberInputMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMax); - const coarseStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.coarseStep); - const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.fineStep); + const isManual = useAppSelector(selectIsManual); + const scaledHeight = useAppSelector(selectScaledHeight); + const config = useAppSelector(selectScaledBoundingBoxHeightConfig); const onChange = useCallback( (height: number) => { @@ -29,21 +33,21 @@ const ParamScaledHeight = () => { {t('parameters.scaledHeight')} diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth.tsx index 7c92e2a7dc..5eb2430b1d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth.tsx @@ -1,22 +1,26 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { selectConfigSlice } from 'features/system/store/configSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +const selectIsManual = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod === 'manual'); +const selectScaledWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.width); +const selectScaledBoundingBoxWidthConfig = createSelector( + selectConfigSlice, + (config) => config.sd.scaledBoundingBoxWidth +); + const ParamScaledWidth = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const optimalDimension = useAppSelector(selectOptimalDimension); - const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual'); - const width = useAppSelector((s) => s.canvasV2.bbox.scaledSize.width); - const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMin); - const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMax); - const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMin); - const numberInputMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMax); - const coarseStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.coarseStep); - const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.fineStep); + const isManual = useAppSelector(selectIsManual); + const scaledWidth = useAppSelector(selectScaledWidth); + const config = useAppSelector(selectScaledBoundingBoxWidthConfig); const onChange = useCallback( (width: number) => { dispatch(bboxScaledSizeChanged({ width })); @@ -28,21 +32,21 @@ const ParamScaledWidth = () => { {t('parameters.scaledWidth')} diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx index 93fa1319b6..4db731c7ee 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx @@ -1,22 +1,22 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; -import { bboxHeightChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { bboxHeightChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { selectConfigSlice } from 'features/system/store/configSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height); +const selectHeightConfig = createSelector(selectConfigSlice, (config) => config.sd.height); + export const ParamHeight = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const optimalDimension = useAppSelector(selectOptimalDimension); - const height = useAppSelector((s) => s.canvasV2.bbox.rect.height); - const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); - const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); - const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin); - const numberInputMax = useAppSelector((s) => s.config.sd.height.numberInputMax); - const coarseStep = useAppSelector((s) => s.config.sd.height.coarseStep); - const fineStep = useAppSelector((s) => s.config.sd.height.fineStep); + const height = useAppSelector(selectHeight); + const config = useAppSelector(selectHeightConfig); const onChange = useCallback( (v: number) => { @@ -25,7 +25,10 @@ export const ParamHeight = memo(() => { [dispatch] ); - const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); + const marks = useMemo( + () => [config.sliderMin, optimalDimension, config.sliderMax], + [config.sliderMin, config.sliderMax, optimalDimension] + ); return ( @@ -36,20 +39,20 @@ export const ParamHeight = memo(() => { value={height} defaultValue={optimalDimension} onChange={onChange} - min={sliderMin} - max={sliderMax} - step={coarseStep} - fineStep={fineStep} + min={config.sliderMin} + max={config.sliderMax} + step={config.coarseStep} + fineStep={config.fineStep} marks={marks} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx index 66dc071d63..71d6b4945c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx @@ -1,22 +1,22 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; -import { bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { bboxWidthChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { selectConfigSlice } from 'features/system/store/configSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width); +const selectWidthConfig = createSelector(selectConfigSlice, (config) => config.sd.width); + export const ParamWidth = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const width = useAppSelector((s) => s.canvasV2.bbox.rect.width); + const width = useAppSelector(selectWidth); const optimalDimension = useAppSelector(selectOptimalDimension); - const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); - const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); - const numberInputMin = useAppSelector((s) => s.config.sd.width.numberInputMin); - const numberInputMax = useAppSelector((s) => s.config.sd.width.numberInputMax); - const coarseStep = useAppSelector((s) => s.config.sd.width.coarseStep); - const fineStep = useAppSelector((s) => s.config.sd.width.fineStep); + const config = useAppSelector(selectWidthConfig); const onChange = useCallback( (v: number) => { @@ -25,7 +25,10 @@ export const ParamWidth = memo(() => { [dispatch] ); - const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); + const marks = useMemo( + () => [config.sliderMin, optimalDimension, config.sliderMax], + [config.sliderMax, config.sliderMin, optimalDimension] + ); return ( @@ -36,20 +39,20 @@ export const ParamWidth = memo(() => { value={width} onChange={onChange} defaultValue={optimalDimension} - min={sliderMin} - max={sliderMax} - step={coarseStep} - fineStep={fineStep} + min={config.sliderMin} + max={config.sliderMax} + step={config.coarseStep} + fineStep={config.fineStep} marks={marks} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioSelect.tsx index f2c3fc66de..f8af1e562a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioSelect.tsx @@ -1,18 +1,22 @@ import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import type { SingleValue } from 'chakra-react-select'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; -import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants'; import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id); + export const AspectRatioSelect = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const id = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.id); + const id = useAppSelector(selectAspectRatioID); const onChange = useCallback( (v: SingleValue) => { diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/LockAspectRatioButton.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/LockAspectRatioButton.tsx index 47846ad8a0..a3a765a20e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/LockAspectRatioButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/LockAspectRatioButton.tsx @@ -1,14 +1,18 @@ import { IconButton } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { bboxAspectRatioLockToggled } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxAspectRatioLockToggled } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi'; +const selectAspectRatioIsLocked = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.isLocked); + export const LockAspectRatioButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isLocked = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.isLocked); + const isLocked = useAppSelector(selectAspectRatioIsLocked); const onClick = useCallback(() => { dispatch(bboxAspectRatioLockToggled()); }, [dispatch]); diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx index 377ae4d4b2..df43afff1d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx @@ -1,17 +1,21 @@ import { IconButton } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { bboxSizeOptimized } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; +import { bboxSizeOptimized } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { RiSparklingFill } from 'react-icons/ri'; +const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width); +const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height); + export const SetOptimalSizeButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const width = useAppSelector((s) => s.canvasV2.bbox.rect.width); - const height = useAppSelector((s) => s.canvasV2.bbox.rect.height); + const width = useAppSelector(selectWidth); + const height = useAppSelector(selectHeight); const optimalDimension = useAppSelector(selectOptimalDimension); const isSizeTooSmall = useMemo( () => getIsSizeTooSmall(width, height, optimalDimension), diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SwapDimensionsButton.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SwapDimensionsButton.tsx index f4f586c0f8..745024a1b6 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SwapDimensionsButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SwapDimensionsButton.tsx @@ -1,6 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice'; +import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowsDownUpBold } 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 a0b9e01829..8adcc23426 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -3,7 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { HrfSettings } from 'features/hrf/components/HrfSettings'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; @@ -20,15 +20,15 @@ import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createMemoizedSelector( - [selectHrfSlice, selectCanvasV2Slice, selectParamsSlice], - (hrf, canvasV2, params) => { + [selectHrfSlice, selectCanvasSlice, selectParamsSlice], + (hrf, canvas, params) => { const { shouldRandomizeSeed, model } = params; const { hrfEnabled } = hrf; const badges: string[] = []; const isSDXL = model?.base === 'sdxl'; - const { aspectRatio } = canvasV2.bbox; - const { width, height } = canvasV2.bbox.rect; + const { aspectRatio } = canvas.bbox; + const { width, height } = canvas.bbox.rect; badges.push(`${width}×${height}`); badges.push(aspectRatio.id);