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);