feat(ui): split out session state from canvas rendering state

This commit is contained in:
psychedelicious 2024-08-26 22:20:04 +10:00
parent a58b91b221
commit 100832c66d
26 changed files with 182 additions and 123 deletions

View File

@ -1,10 +1,10 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { import {
rasterLayerAdded,
sessionStagingAreaImageAccepted, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, 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 type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
@ -55,7 +55,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
effect: (action, api) => { effect: (action, api) => {
const { index } = action.payload; const { index } = action.payload;
const state = api.getState(); 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'); assert(stagingAreaImage, 'No staged image found to accept');
const { x, y } = state.canvasV2.bbox.rect; const { x, y } = state.canvasV2.bbox.rect;

View File

@ -5,7 +5,7 @@ import type { SerializableObject } from 'common/types';
import type { Result } from 'common/util/result'; import type { Result } from 'common/util/result';
import { isErr, withResult, withResultAsync } from 'common/util/result'; import { isErr, withResult, withResultAsync } from 'common/util/result';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; 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 { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph'; import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph';
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph'; import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
@ -31,13 +31,13 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
let didStartStaging = false; let didStartStaging = false;
if (!state.canvasV2.session.isStaging && state.canvasV2.session.mode === 'compose') { if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') {
dispatch(sessionStartedStaging()); dispatch(sessionStartedStaging());
didStartStaging = true; didStartStaging = true;
} }
const abortStaging = () => { const abortStaging = () => {
if (didStartStaging && getState().canvasV2.session.isStaging) { if (didStartStaging && getState().canvasSession.isStaging) {
dispatch(sessionStagingAreaReset()); dispatch(sessionStagingAreaReset());
} }
}; };

View File

@ -6,6 +6,7 @@ import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice'; import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
import { canvasSessionPersistConfig, canvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice'; import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice'; import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
@ -63,6 +64,7 @@ const allReducers = {
[paramsSlice.name]: paramsSlice.reducer, [paramsSlice.name]: paramsSlice.reducer,
[toolSlice.name]: toolSlice.reducer, [toolSlice.name]: toolSlice.reducer,
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer, [canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
[canvasSessionSlice.name]: canvasSessionSlice.reducer,
}; };
const rootReducer = combineReducers(allReducers); const rootReducer = combineReducers(allReducers);
@ -107,6 +109,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[paramsPersistConfig.name]: paramsPersistConfig, [paramsPersistConfig.name]: paramsPersistConfig,
[toolPersistConfig.name]: toolPersistConfig, [toolPersistConfig.name]: toolPersistConfig,
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig, [canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
[canvasSessionPersistConfig.name]: canvasSessionPersistConfig,
}; };
const unserialize: UnserializeFunction = (data, key) => { const unserialize: UnserializeFunction = (data, key) => {

View File

@ -1,13 +1,13 @@
import { Button, ButtonGroup } from '@invoke-ai/ui-library'; import { Button, ButtonGroup } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const CanvasModeSwitcher = memo(() => { export const CanvasModeSwitcher = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); 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 onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]); const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);

View File

@ -33,9 +33,11 @@ export const CanvasEditor = memo(() => {
<ControlLayersToolbar /> <ControlLayersToolbar />
<StageComponent /> <StageComponent />
<Flex position="absolute" bottom={16} gap={2} align="center" justify="center"> <Flex position="absolute" bottom={16} gap={2} align="center" justify="center">
<StagingAreaIsStagingGate> <CanvasManagerProviderGate>
<StagingAreaToolbar /> <StagingAreaIsStagingGate>
</StagingAreaIsStagingGate> <StagingAreaToolbar />
</StagingAreaIsStagingGate>
</CanvasManagerProviderGate>
</Flex> </Flex>
<Flex position="absolute" bottom={16}> <Flex position="absolute" bottom={16}>
<CanvasManagerProviderGate> <CanvasManagerProviderGate>

View File

@ -3,7 +3,7 @@ import type { PropsWithChildren } from 'react';
import { memo } from 'react'; import { memo } from 'react';
export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => { export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => {
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
if (!isStaging) { if (!isStaging) {
return null; return null;

View File

@ -9,7 +9,7 @@ import {
sessionStagedImageDiscarded, sessionStagedImageDiscarded,
sessionStagingAreaImageAccepted, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, sessionStagingAreaReset,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasSessionSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -27,7 +27,7 @@ import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/ima
export const StagingAreaToolbar = memo(() => { export const StagingAreaToolbar = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const session = useAppSelector((s) => s.canvasV2.session); const session = useAppSelector((s) => s.canvasSession);
const canvasManager = useCanvasManager(); const canvasManager = useCanvasManager();
const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage); const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage);
const images = useMemo(() => session.stagedImages, [session]); const images = useMemo(() => session.stagedImages, [session]);

View File

@ -14,7 +14,7 @@ export const ToolBboxButton = memo(() => {
const isSelected = useToolIsSelected('bbox'); const isSelected = useToolIsSelected('bbox');
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging; return isTransforming || isFiltering || isStaging;
}, [isFiltering, isStaging, isTransforming]); }, [isFiltering, isStaging, isTransforming]);

View File

@ -13,7 +13,7 @@ export const ToolBrushButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const selectBrush = useSelectTool('brush'); const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush'); const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {

View File

@ -14,7 +14,7 @@ export const ToolColorPickerButton = memo(() => {
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const selectColorPicker = useSelectTool('colorPicker'); const selectColorPicker = useSelectTool('colorPicker');
const isSelected = useToolIsSelected('colorPicker'); const isSelected = useToolIsSelected('colorPicker');
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging; return isTransforming || isFiltering || isStaging;

View File

@ -13,7 +13,7 @@ export const ToolEraserButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const selectEraser = useSelectTool('eraser'); const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser'); const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {

View File

@ -15,7 +15,7 @@ export const ToolMoveButton = memo(() => {
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const selectMove = useSelectTool('move'); const selectMove = useSelectTool('move');
const isSelected = useToolIsSelected('move'); const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false; return false;

View File

@ -15,7 +15,7 @@ export const ToolRectButton = memo(() => {
const isSelected = useToolIsSelected('rect'); const isSelected = useToolIsSelected('rect');
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false; return false;

View File

@ -12,7 +12,7 @@ export const ToolViewButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const selectView = useSelectTool('view'); const selectView = useSelectTool('view');
const isSelected = useToolIsSelected('view'); const isSelected = useToolIsSelected('view');
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {

View File

@ -15,7 +15,7 @@ export function useCanvasDeleteLayerHotkey() {
useAssertSingleton(useCanvasDeleteLayerHotkey.name); useAssertSingleton(useCanvasDeleteLayerHotkey.name);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const deleteSelectedLayer = useCallback(() => { const deleteSelectedLayer = useCallback(() => {
if (selectedEntityIdentifier === null) { if (selectedEntityIdentifier === null) {

View File

@ -4,6 +4,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; 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 { CanvasSettingsState } from 'features/controlLayers/store/canvasSettingsSlice';
import type { CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
@ -19,6 +20,9 @@ export class CanvasRenderingModule extends CanvasModuleBase {
state: CanvasV2State | null = null; state: CanvasV2State | null = null;
settings: CanvasSettingsState | null = null; settings: CanvasSettingsState | null = null;
session: CanvasSessionState | null = null;
isFirstRender = true;
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super(); super();
@ -30,42 +34,79 @@ export class CanvasRenderingModule extends CanvasModuleBase {
} }
render = async () => { render = async () => {
const state = this.manager.stateApi.getCanvasState(); if (!this.state || !this.settings || !this.session) {
const settings = this.manager.stateApi.getSettings();
if (!this.state || !this.settings) {
this.log.trace('First render'); 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; const prevState = this.state;
this.state = state; this.state = state;
const prevSettings = this.settings; if (prevState === state) {
this.settings = settings;
if (prevState === state && prevSettings === settings) {
// No changes to state - no need to render // No changes to state - no need to render
return; return;
} }
this.renderBackground(settings, prevSettings);
await this.renderRasterLayers(state, prevState); await this.renderRasterLayers(state, prevState);
await this.renderControlLayers(prevState, state); await this.renderControlLayers(prevState, state);
await this.renderRegionalGuidance(prevState, state); await this.renderRegionalGuidance(prevState, state);
await this.renderInpaintMasks(state, prevState); await this.renderInpaintMasks(state, prevState);
await this.renderBbox(state, prevState); await this.renderBbox(state, prevState);
await this.renderStagingArea(state, prevState);
this.arrangeEntities(state, prevState); this.arrangeEntities(state, prevState);
this.manager.stateApi.$toolState.set(this.manager.stateApi.getToolState()); this.manager.stateApi.$toolState.set(this.manager.stateApi.getToolState());
this.manager.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier); this.manager.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier);
this.manager.stateApi.$selectedEntity.set(this.manager.stateApi.getSelectedEntity()); this.manager.stateApi.$selectedEntity.set(this.manager.stateApi.getSelectedEntity());
this.manager.stateApi.$currentFill.set(this.manager.stateApi.getCurrentFill()); this.manager.stateApi.$currentFill.set(this.manager.stateApi.getCurrentFill());
};
// We have no prev state for the first render renderSettings = () => {
if (!prevState && !prevSettings) { const settings = this.manager.stateApi.getSettings();
this.manager.setCanvasManager();
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 => { getLoggingContext = (): SerializableObject => {
@ -210,8 +251,8 @@ export class CanvasRenderingModule extends CanvasModuleBase {
} }
}; };
renderStagingArea = async (state: CanvasV2State, prevState: CanvasV2State | null) => { renderStagingArea = async (session: CanvasSessionState, prevSession: CanvasSessionState | null) => {
if (!prevState || state.session !== prevState.session) { if (!prevSession || session !== prevSession) {
await this.manager.preview.stagingArea.render(); await this.manager.preview.stagingArea.render();
} }
}; };

View File

@ -165,7 +165,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
return this.getCanvasState().inpaintMasks; return this.getCanvasState().inpaintMasks;
}; };
getSession = () => { getSession = () => {
return this.getCanvasState().session; return this.store.getState().canvasSession;
}; };
getIsSelected = (id: string) => { getIsSelected = (id: string) => {
return this.getCanvasState().selectedEntityIdentifier?.id === id; return this.getCanvasState().selectedEntityIdentifier?.id === id;

View File

@ -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<CanvasSessionState> = {
name: canvasSessionSlice.name,
initialState,
migrate,
persistDenylist: [],
};
export const sessionStagingAreaImageAccepted = createAction<{ index: number }>(
`${canvasV2Slice.name}/sessionStagingAreaImageAccepted`
);

View File

@ -1,5 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit'; 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 type { PersistConfig } from 'app/store/store';
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils'; import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
import { deepClone } from 'common/util/deepClone'; 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 { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersReducers';
import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors'; import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify'; import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
@ -65,12 +64,6 @@ const initialState: CanvasV2State = {
height: 512, height: 512,
}, },
}, },
session: {
mode: 'generate',
isStaging: false,
stagedImages: [],
selectedStagedImageIndex: 0,
},
}; };
export const canvasV2Slice = createSlice({ export const canvasV2Slice = createSlice({
@ -86,7 +79,6 @@ export const canvasV2Slice = createSlice({
...bboxReducers, ...bboxReducers,
// move out // move out
...lorasReducers, ...lorasReducers,
...sessionReducers,
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => { entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
const { entityIdentifier } = action.payload; const { entityIdentifier } = action.payload;
state.selectedEntityIdentifier = entityIdentifier; state.selectedEntityIdentifier = entityIdentifier;
@ -339,7 +331,6 @@ export const canvasV2Slice = createSlice({
state.bbox.rect.height = state.bbox.optimalDimension; state.bbox.rect.height = state.bbox.optimalDimension;
const size = pick(state.bbox.rect, 'width', 'height'); const size = pick(state.bbox.rect, 'width', 'height');
state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, state.bbox.optimalDimension); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, state.bbox.optimalDimension);
state.session = deepClone(initialState.session);
state.ipAdapters = deepClone(initialState.ipAdapters); state.ipAdapters = deepClone(initialState.ipAdapters);
state.rasterLayers = deepClone(initialState.rasterLayers); state.rasterLayers = deepClone(initialState.rasterLayers);
@ -458,14 +449,6 @@ export const {
// inpaintMaskRecalled, // inpaintMaskRecalled,
inpaintMaskFillColorChanged, inpaintMaskFillColorChanged,
inpaintMaskFillStyleChanged, inpaintMaskFillStyleChanged,
// Staging
sessionStartedStaging,
sessionImageStaged,
sessionStagedImageDiscarded,
sessionStagingAreaReset,
sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
sessionModeChanged,
} = canvasV2Slice.actions; } = canvasV2Slice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@ -479,7 +462,3 @@ export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
migrate, migrate,
persistDenylist: [], persistDenylist: [],
}; };
export const sessionStagingAreaImageAccepted = createAction<{ index: number }>(
`${canvasV2Slice.name}/sessionStagingAreaImageAccepted`
);

View File

@ -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<CanvasV2State>;

View File

@ -730,12 +730,6 @@ export type CanvasV2State = {
scaleMethod: BoundingBoxScaleMethod; scaleMethod: BoundingBoxScaleMethod;
optimalDimension: number; optimalDimension: number;
}; };
session: {
mode: SessionMode;
isStaging: boolean;
stagedImages: StagingAreaImage[];
selectedStagedImageIndex: number;
};
}; };
export type StageAttrs = { export type StageAttrs = {

View File

@ -21,9 +21,9 @@ export const addInpaint = async (
): Promise<Invocation<'canvas_v2_mask_and_crop'>> => { ): Promise<Invocation<'canvas_v2_mask_and_crop'>> => {
denoise.denoising_start = denoising_start; denoise.denoising_start = denoising_start;
const { params, canvasV2 } = state; const { params, canvasV2, canvasSession } = state;
const { bbox, session } = canvasV2; const { bbox } = canvasV2;
const { mode } = session; const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);
const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect); const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect);

View File

@ -22,9 +22,9 @@ export const addOutpaint = async (
): Promise<Invocation<'canvas_v2_mask_and_crop'>> => { ): Promise<Invocation<'canvas_v2_mask_and_crop'>> => {
denoise.denoising_start = denoising_start; denoise.denoising_start = denoising_start;
const { params, canvasV2 } = state; const { params, canvasV2, canvasSession } = state;
const { bbox, session } = canvasV2; const { bbox } = canvasV2;
const { mode } = session; const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);
const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect); const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect);

View File

@ -31,8 +31,8 @@ export const buildSD1Graph = async (
const generationMode = manager.compositor.getGenerationMode(); const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SD1/SD2 graph'); log.debug({ generationMode }, 'Building SD1/SD2 graph');
const { canvasV2, params, canvasSettings } = state; const { canvasV2, params, canvasSettings, canvasSession } = state;
const { bbox, session } = canvasV2; const { bbox } = canvasV2;
const { const {
model, model,
@ -274,7 +274,7 @@ export const buildSD1Graph = async (
canvasOutput = addWatermarker(g, canvasOutput); canvasOutput = addWatermarker(g, canvasOutput);
} }
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave; const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave;
g.updateNode(canvasOutput, { g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'), id: getPrefixedId('canvas_output'),

View File

@ -31,8 +31,8 @@ export const buildSDXLGraph = async (
const generationMode = manager.compositor.getGenerationMode(); const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SDXL graph'); log.debug({ generationMode }, 'Building SDXL graph');
const { params, canvasV2, canvasSettings } = state; const { params, canvasV2, canvasSettings, canvasSession } = state;
const { bbox, session } = canvasV2; const { bbox } = canvasV2;
const { const {
model, model,
@ -277,7 +277,7 @@ export const buildSDXLGraph = async (
canvasOutput = addWatermarker(g, canvasOutput); canvasOutput = addWatermarker(g, canvasOutput);
} }
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave; const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave;
g.updateNode(canvasOutput, { g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'), id: getPrefixedId('canvas_output'),

View File

@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppDispatch, RootState } from 'app/store/store'; import type { AppDispatch, RootState } from 'app/store/store';
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; 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 { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice';
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
import { zNodeStatus } from 'features/nodes/types/invocation'; import { zNodeStatus } from 'features/nodes/types/invocation';
@ -114,7 +114,7 @@ export const buildOnInvocationComplete = (
}; };
const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => { const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => {
const session = getState().canvasV2.session; const session = getState().canvasSession;
const imageDTO = await getResultImageDTO(data); const imageDTO = await getResultImageDTO(data);