From 100832c66d47a643cb0fb5ad1ebb7945ca156540 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:20:04 +1000 Subject: [PATCH] feat(ui): split out session state from canvas rendering state --- .../addCommitStagingAreaImageListener.ts | 6 +- .../listeners/enqueueRequestedLinear.ts | 6 +- invokeai/frontend/web/src/app/store/store.ts | 3 + .../components/CanvasModeSwitcher.tsx | 4 +- .../components/ControlLayersEditor.tsx | 8 +- .../StagingArea/StagingAreaIsStagingGate.tsx | 2 +- .../StagingArea/StagingAreaToolbar.tsx | 4 +- .../components/Tool/ToolBboxButton.tsx | 2 +- .../components/Tool/ToolBrushButton.tsx | 2 +- .../components/Tool/ToolColorPickerButton.tsx | 2 +- .../components/Tool/ToolEraserButton.tsx | 2 +- .../components/Tool/ToolMoveButton.tsx | 2 +- .../components/Tool/ToolRectButton.tsx | 2 +- .../components/Tool/ToolViewButton.tsx | 2 +- .../hooks/useCanvasDeleteLayerHotkey.ts | 2 +- .../konva/CanvasRenderingModule.ts | 71 ++++++++++++---- .../konva/CanvasStateApiModule.ts | 2 +- .../controlLayers/store/canvasSessionSlice.ts | 83 +++++++++++++++++++ .../controlLayers/store/canvasV2Slice.ts | 23 +---- .../controlLayers/store/sessionReducers.ts | 43 ---------- .../src/features/controlLayers/store/types.ts | 6 -- .../nodes/util/graph/generation/addInpaint.ts | 6 +- .../util/graph/generation/addOutpaint.ts | 6 +- .../util/graph/generation/buildSD1Graph.ts | 6 +- .../util/graph/generation/buildSDXLGraph.ts | 6 +- .../services/events/onInvocationComplete.ts | 4 +- 26 files changed, 182 insertions(+), 123 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts delete mode 100644 invokeai/frontend/web/src/features/controlLayers/store/sessionReducers.ts 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 70540b13c5..7c533717e7 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 @@ -1,10 +1,10 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { - rasterLayerAdded, sessionStagingAreaImageAccepted, sessionStagingAreaReset, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSessionSlice'; +import { rasterLayerAdded } from 'features/controlLayers/store/canvasV2Slice'; import type { CanvasRasterLayerState } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import { toast } from 'features/toast/toast'; @@ -55,7 +55,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => { effect: (action, api) => { const { index } = action.payload; const state = api.getState(); - const stagingAreaImage = state.canvasV2.session.stagedImages[index]; + const stagingAreaImage = state.canvasSession.stagedImages[index]; assert(stagingAreaImage, 'No staged image found to accept'); const { x, y } = state.canvasV2.bbox.rect; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts index 63d2fdc756..89777f5081 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts @@ -5,7 +5,7 @@ import type { SerializableObject } from 'common/types'; import type { Result } from 'common/util/result'; import { isErr, withResult, withResultAsync } from 'common/util/result'; import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; -import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasV2Slice'; +import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasSessionSlice'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph'; import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph'; @@ -31,13 +31,13 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) let didStartStaging = false; - if (!state.canvasV2.session.isStaging && state.canvasV2.session.mode === 'compose') { + if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') { dispatch(sessionStartedStaging()); didStartStaging = true; } const abortStaging = () => { - if (didStartStaging && getState().canvasV2.session.isStaging) { + if (didStartStaging && getState().canvasSession.isStaging) { dispatch(sessionStagingAreaReset()); } }; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index f7aee74dbc..aabc77829a 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -6,6 +6,7 @@ import { errorHandler } from 'app/store/enhancers/reduxRemember/errors'; import type { SerializableObject } from 'common/types'; 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 { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice'; @@ -63,6 +64,7 @@ const allReducers = { [paramsSlice.name]: paramsSlice.reducer, [toolSlice.name]: toolSlice.reducer, [canvasSettingsSlice.name]: canvasSettingsSlice.reducer, + [canvasSessionSlice.name]: canvasSessionSlice.reducer, }; const rootReducer = combineReducers(allReducers); @@ -107,6 +109,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [paramsPersistConfig.name]: paramsPersistConfig, [toolPersistConfig.name]: toolPersistConfig, [canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig, + [canvasSessionPersistConfig.name]: canvasSessionPersistConfig, }; const unserialize: UnserializeFunction = (data, key) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx index 81b9114153..7613782e6f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx @@ -1,13 +1,13 @@ import { Button, ButtonGroup } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { sessionModeChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { sessionModeChanged } from 'features/controlLayers/store/canvasSessionSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; export const CanvasModeSwitcher = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const mode = useAppSelector((s) => s.canvasV2.session.mode); + const mode = useAppSelector((s) => s.canvasSession.mode); const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]); const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx index 682e757076..fabc93604c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx @@ -33,9 +33,11 @@ export const CanvasEditor = memo(() => { - - - + + + + + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaIsStagingGate.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaIsStagingGate.tsx index 51fc64c96f..c5cbe43755 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaIsStagingGate.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaIsStagingGate.tsx @@ -3,7 +3,7 @@ import type { PropsWithChildren } from 'react'; import { memo } from 'react'; export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => { - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); if (!isStaging) { return null; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx index ed4bcb38b3..12d56517f5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx @@ -9,7 +9,7 @@ import { sessionStagedImageDiscarded, sessionStagingAreaImageAccepted, sessionStagingAreaReset, -} from 'features/controlLayers/store/canvasV2Slice'; +} from 'features/controlLayers/store/canvasSessionSlice'; import { memo, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -27,7 +27,7 @@ import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/ima export const StagingAreaToolbar = memo(() => { const dispatch = useAppDispatch(); - const session = useAppSelector((s) => s.canvasV2.session); + const session = useAppSelector((s) => s.canvasSession); const canvasManager = useCanvasManager(); const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage); const images = useMemo(() => session.stagedImages, [session]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx index 5f676d5f98..cde84309da 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx @@ -14,7 +14,7 @@ export const ToolBboxButton = memo(() => { const isSelected = useToolIsSelected('bbox'); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging; }, [isFiltering, isStaging, isTransforming]); 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 c508ab4d70..170416619d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx @@ -13,7 +13,7 @@ export const ToolBrushButton = memo(() => { const { t } = useTranslation(); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const selectBrush = useSelectTool('brush'); const isSelected = useToolIsSelected('brush'); const isDrawingToolAllowed = useAppSelector((s) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx index e4258170ac..8d1bbddc68 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx @@ -14,7 +14,7 @@ export const ToolColorPickerButton = memo(() => { const isTransforming = useIsTransforming(); const selectColorPicker = useSelectTool('colorPicker'); const isSelected = useToolIsSelected('colorPicker'); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isDisabled = useMemo(() => { return isTransforming || isFiltering || isStaging; 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 78b5942113..2ac15df287 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx @@ -13,7 +13,7 @@ export const ToolEraserButton = memo(() => { const { t } = useTranslation(); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const selectEraser = useSelectTool('eraser'); const isSelected = useToolIsSelected('eraser'); const isDrawingToolAllowed = useAppSelector((s) => { 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 91a8155a8e..f1422e9311 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx @@ -15,7 +15,7 @@ export const ToolMoveButton = memo(() => { const isTransforming = useIsTransforming(); const selectMove = useSelectTool('move'); const isSelected = useToolIsSelected('move'); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isDrawingToolAllowed = useAppSelector((s) => { if (!s.canvasV2.selectedEntityIdentifier?.type) { return false; 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 3b5b1e338f..8e0bfa083f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx @@ -15,7 +15,7 @@ export const ToolRectButton = memo(() => { const isSelected = useToolIsSelected('rect'); const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isDrawingToolAllowed = useAppSelector((s) => { if (!s.canvasV2.selectedEntityIdentifier?.type) { return false; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx index 6b94eaf0cc..0a82651302 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx @@ -12,7 +12,7 @@ export const ToolViewButton = memo(() => { const { t } = useTranslation(); const isTransforming = useIsTransforming(); const isFiltering = useIsFiltering(); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const selectView = useSelectTool('view'); const isSelected = useToolIsSelected('view'); const isDisabled = useMemo(() => { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts index 88f444401c..9f2261c887 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts @@ -15,7 +15,7 @@ export function useCanvasDeleteLayerHotkey() { useAssertSingleton(useCanvasDeleteLayerHotkey.name); const dispatch = useAppDispatch(); const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); - const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); + const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const deleteSelectedLayer = useCallback(() => { if (selectedEntityIdentifier === null) { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts index f62b275eb8..6be2f8d248 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRenderingModule.ts @@ -4,6 +4,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; 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 { Logger } from 'roarr'; @@ -19,6 +20,9 @@ export class CanvasRenderingModule extends CanvasModuleBase { state: CanvasV2State | null = null; settings: CanvasSettingsState | null = null; + session: CanvasSessionState | null = null; + + isFirstRender = true; constructor(manager: CanvasManager) { super(); @@ -30,42 +34,79 @@ export class CanvasRenderingModule extends CanvasModuleBase { } render = async () => { - const state = this.manager.stateApi.getCanvasState(); - const settings = this.manager.stateApi.getSettings(); - - if (!this.state || !this.settings) { + if (!this.state || !this.settings || !this.session) { this.log.trace('First render'); } + await this.renderCanvas(); + this.renderSettings(); + await this.renderSession(); + + // We have no prev state for the first render + if (this.isFirstRender) { + this.isFirstRender = false; + this.manager.setCanvasManager(); + } + }; + + renderCanvas = async () => { + const state = this.manager.stateApi.getCanvasState(); + const prevState = this.state; this.state = state; - const prevSettings = this.settings; - this.settings = settings; - - if (prevState === state && prevSettings === settings) { + if (prevState === state) { // No changes to state - no need to render return; } - this.renderBackground(settings, prevSettings); await this.renderRasterLayers(state, prevState); await this.renderControlLayers(prevState, state); await this.renderRegionalGuidance(prevState, state); await this.renderInpaintMasks(state, prevState); await this.renderBbox(state, prevState); - await this.renderStagingArea(state, prevState); this.arrangeEntities(state, prevState); this.manager.stateApi.$toolState.set(this.manager.stateApi.getToolState()); this.manager.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier); this.manager.stateApi.$selectedEntity.set(this.manager.stateApi.getSelectedEntity()); this.manager.stateApi.$currentFill.set(this.manager.stateApi.getCurrentFill()); + }; - // We have no prev state for the first render - if (!prevState && !prevSettings) { - this.manager.setCanvasManager(); + renderSettings = () => { + const settings = this.manager.stateApi.getSettings(); + + if (!this.settings) { + this.log.trace('First settings render'); } + + const prevSettings = this.settings; + this.settings = settings; + + if (prevSettings === settings) { + // No changes to state - no need to render + return; + } + + this.renderBackground(settings, prevSettings); + }; + + renderSession = async () => { + const session = this.manager.stateApi.getSession(); + + if (!this.session) { + this.log.trace('First session render'); + } + + const prevSession = this.session; + this.session = session; + + if (prevSession === session) { + // No changes to state - no need to render + return; + } + + await this.renderStagingArea(session, prevSession); }; getLoggingContext = (): SerializableObject => { @@ -210,8 +251,8 @@ export class CanvasRenderingModule extends CanvasModuleBase { } }; - renderStagingArea = async (state: CanvasV2State, prevState: CanvasV2State | null) => { - if (!prevState || state.session !== prevState.session) { + renderStagingArea = async (session: CanvasSessionState, prevSession: CanvasSessionState | null) => { + if (!prevSession || session !== prevSession) { await this.manager.preview.stagingArea.render(); } }; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts index 1d5d28ef8c..eff144968c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts @@ -165,7 +165,7 @@ export class CanvasStateApiModule extends CanvasModuleBase { return this.getCanvasState().inpaintMasks; }; getSession = () => { - return this.getCanvasState().session; + return this.store.getState().canvasSession; }; getIsSelected = (id: string) => { return this.getCanvasState().selectedEntityIdentifier?.id === id; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts new file mode 100644 index 0000000000..f8d70511d0 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts @@ -0,0 +1,83 @@ +import { createAction, createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import type { PersistConfig } from 'app/store/store'; +import { canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; +import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types'; + +export type CanvasSessionState = { + mode: SessionMode; + isStaging: boolean; + stagedImages: StagingAreaImage[]; + selectedStagedImageIndex: number; +}; + +const initialState: CanvasSessionState = { + mode: 'generate', + isStaging: false, + stagedImages: [], + selectedStagedImageIndex: 0, +}; + +export const canvasSessionSlice = createSlice({ + name: 'canvasSession', + initialState, + reducers: { + sessionStartedStaging: (state) => { + state.isStaging = true; + state.selectedStagedImageIndex = 0; + }, + sessionImageStaged: (state, action: PayloadAction<{ stagingAreaImage: StagingAreaImage }>) => { + const { stagingAreaImage } = action.payload; + state.stagedImages.push(stagingAreaImage); + state.selectedStagedImageIndex = state.stagedImages.length - 1; + }, + sessionNextStagedImageSelected: (state) => { + state.selectedStagedImageIndex = (state.selectedStagedImageIndex + 1) % state.stagedImages.length; + }, + sessionPrevStagedImageSelected: (state) => { + state.selectedStagedImageIndex = + (state.selectedStagedImageIndex - 1 + state.stagedImages.length) % state.stagedImages.length; + }, + sessionStagedImageDiscarded: (state, action: PayloadAction<{ index: number }>) => { + const { index } = action.payload; + state.stagedImages.splice(index, 1); + state.selectedStagedImageIndex = Math.min(state.selectedStagedImageIndex, state.stagedImages.length - 1); + if (state.stagedImages.length === 0) { + state.isStaging = false; + } + }, + sessionStagingAreaReset: (state) => { + state.isStaging = false; + state.stagedImages = []; + state.selectedStagedImageIndex = 0; + }, + sessionModeChanged: (state, action: PayloadAction<{ mode: SessionMode }>) => { + const { mode } = action.payload; + state.mode = mode; + }, + }, +}); + +export const { + sessionStartedStaging, + sessionImageStaged, + sessionStagedImageDiscarded, + sessionStagingAreaReset, + sessionNextStagedImageSelected, + sessionPrevStagedImageSelected, + sessionModeChanged, +} = canvasSessionSlice.actions; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +const migrate = (state: any): any => { + return state; +}; + +export const canvasSessionPersistConfig: PersistConfig = { + name: canvasSessionSlice.name, + initialState, + migrate, + persistDenylist: [], +}; +export const sessionStagingAreaImageAccepted = createAction<{ index: number }>( + `${canvasV2Slice.name}/sessionStagingAreaImageAccepted` +); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index b7667b58eb..8951797f77 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -1,5 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; -import { createAction, createSlice } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig } from 'app/store/store'; import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; import { deepClone } from 'common/util/deepClone'; @@ -13,7 +13,6 @@ import { modelChanged } from 'features/controlLayers/store/paramsSlice'; import { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersReducers'; import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors'; -import { sessionReducers } from 'features/controlLayers/store/sessionReducers'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; @@ -65,12 +64,6 @@ const initialState: CanvasV2State = { height: 512, }, }, - session: { - mode: 'generate', - isStaging: false, - stagedImages: [], - selectedStagedImageIndex: 0, - }, }; export const canvasV2Slice = createSlice({ @@ -86,7 +79,6 @@ export const canvasV2Slice = createSlice({ ...bboxReducers, // move out ...lorasReducers, - ...sessionReducers, entitySelected: (state, action: PayloadAction) => { const { entityIdentifier } = action.payload; state.selectedEntityIdentifier = entityIdentifier; @@ -339,7 +331,6 @@ export const canvasV2Slice = createSlice({ state.bbox.rect.height = state.bbox.optimalDimension; const size = pick(state.bbox.rect, 'width', 'height'); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, state.bbox.optimalDimension); - state.session = deepClone(initialState.session); state.ipAdapters = deepClone(initialState.ipAdapters); state.rasterLayers = deepClone(initialState.rasterLayers); @@ -458,14 +449,6 @@ export const { // inpaintMaskRecalled, inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged, - // Staging - sessionStartedStaging, - sessionImageStaged, - sessionStagedImageDiscarded, - sessionStagingAreaReset, - sessionNextStagedImageSelected, - sessionPrevStagedImageSelected, - sessionModeChanged, } = canvasV2Slice.actions; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -479,7 +462,3 @@ export const canvasV2PersistConfig: PersistConfig = { migrate, persistDenylist: [], }; - -export const sessionStagingAreaImageAccepted = createAction<{ index: number }>( - `${canvasV2Slice.name}/sessionStagingAreaImageAccepted` -); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/sessionReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/sessionReducers.ts deleted file mode 100644 index 2e77f1220d..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/sessionReducers.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; -import type { CanvasV2State, SessionMode, StagingAreaImage } from 'features/controlLayers/store/types'; - -export const sessionReducers = { - sessionStartedStaging: (state) => { - state.session.isStaging = true; - state.session.selectedStagedImageIndex = 0; - }, - sessionImageStaged: (state, action: PayloadAction<{ stagingAreaImage: StagingAreaImage }>) => { - const { stagingAreaImage } = action.payload; - state.session.stagedImages.push(stagingAreaImage); - state.session.selectedStagedImageIndex = state.session.stagedImages.length - 1; - }, - sessionNextStagedImageSelected: (state) => { - state.session.selectedStagedImageIndex = - (state.session.selectedStagedImageIndex + 1) % state.session.stagedImages.length; - }, - sessionPrevStagedImageSelected: (state) => { - state.session.selectedStagedImageIndex = - (state.session.selectedStagedImageIndex - 1 + state.session.stagedImages.length) % - state.session.stagedImages.length; - }, - sessionStagedImageDiscarded: (state, action: PayloadAction<{ index: number }>) => { - const { index } = action.payload; - state.session.stagedImages.splice(index, 1); - state.session.selectedStagedImageIndex = Math.min( - state.session.selectedStagedImageIndex, - state.session.stagedImages.length - 1 - ); - if (state.session.stagedImages.length === 0) { - state.session.isStaging = false; - } - }, - sessionStagingAreaReset: (state) => { - state.session.isStaging = false; - state.session.stagedImages = []; - state.session.selectedStagedImageIndex = 0; - }, - sessionModeChanged: (state, action: PayloadAction<{ mode: SessionMode }>) => { - const { mode } = action.payload; - state.session.mode = mode; - }, -} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 7f20ee06f3..74231cdb9f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -730,12 +730,6 @@ export type CanvasV2State = { scaleMethod: BoundingBoxScaleMethod; optimalDimension: number; }; - session: { - mode: SessionMode; - isStaging: boolean; - stagedImages: StagingAreaImage[]; - selectedStagedImageIndex: number; - }; }; export type StageAttrs = { 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 438f76d28d..3f8c4fbb02 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 @@ -21,9 +21,9 @@ export const addInpaint = async ( ): Promise> => { denoise.denoising_start = denoising_start; - const { params, canvasV2 } = state; - const { bbox, session } = canvasV2; - const { mode } = session; + const { params, canvasV2, canvasSession } = state; + const { bbox } = canvasV2; + const { mode } = canvasSession; const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(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 7a2b95da97..c0f298bfbd 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 @@ -22,9 +22,9 @@ export const addOutpaint = async ( ): Promise> => { denoise.denoising_start = denoising_start; - const { params, canvasV2 } = state; - const { bbox, session } = canvasV2; - const { mode } = session; + const { params, canvasV2, canvasSession } = state; + const { bbox } = canvasV2; + const { mode } = canvasSession; const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(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 cf724b227e..73d3c9e85a 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 @@ -31,8 +31,8 @@ export const buildSD1Graph = async ( const generationMode = manager.compositor.getGenerationMode(); log.debug({ generationMode }, 'Building SD1/SD2 graph'); - const { canvasV2, params, canvasSettings } = state; - const { bbox, session } = canvasV2; + const { canvasV2, params, canvasSettings, canvasSession } = state; + const { bbox } = canvasV2; const { model, @@ -274,7 +274,7 @@ export const buildSD1Graph = async ( canvasOutput = addWatermarker(g, canvasOutput); } - const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave; + const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave; g.updateNode(canvasOutput, { id: getPrefixedId('canvas_output'), 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 976e59527c..a85bd57a66 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 @@ -31,8 +31,8 @@ export const buildSDXLGraph = async ( const generationMode = manager.compositor.getGenerationMode(); log.debug({ generationMode }, 'Building SDXL graph'); - const { params, canvasV2, canvasSettings } = state; - const { bbox, session } = canvasV2; + const { params, canvasV2, canvasSettings, canvasSession } = state; + const { bbox } = canvasV2; const { model, @@ -277,7 +277,7 @@ export const buildSDXLGraph = async ( canvasOutput = addWatermarker(g, canvasOutput); } - const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave; + const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave; g.updateNode(canvasOutput, { id: getPrefixedId('canvas_output'), diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts b/invokeai/frontend/web/src/services/events/onInvocationComplete.ts index 5b737b41ff..09cd840bd1 100644 --- a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts +++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.ts @@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger'; import type { AppDispatch, RootState } from 'app/store/store'; import type { SerializableObject } from 'common/types'; import { deepClone } from 'common/util/deepClone'; -import { sessionImageStaged } from 'features/controlLayers/store/canvasV2Slice'; +import { sessionImageStaged } from 'features/controlLayers/store/canvasSessionSlice'; import { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; import { zNodeStatus } from 'features/nodes/types/invocation'; @@ -114,7 +114,7 @@ export const buildOnInvocationComplete = ( }; const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => { - const session = getState().canvasV2.session; + const session = getState().canvasSession; const imageDTO = await getResultImageDTO(data);