mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): split out session state from canvas rendering state
This commit is contained in:
parent
a58b91b221
commit
100832c66d
@ -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;
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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) => {
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
<CanvasManagerProviderGate>
|
||||||
<StagingAreaIsStagingGate>
|
<StagingAreaIsStagingGate>
|
||||||
<StagingAreaToolbar />
|
<StagingAreaToolbar />
|
||||||
</StagingAreaIsStagingGate>
|
</StagingAreaIsStagingGate>
|
||||||
|
</CanvasManagerProviderGate>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex position="absolute" bottom={16}>
|
<Flex position="absolute" bottom={16}>
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
|
@ -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;
|
||||||
|
@ -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]);
|
||||||
|
@ -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]);
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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`
|
||||||
|
);
|
@ -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`
|
|
||||||
);
|
|
||||||
|
@ -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>;
|
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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'),
|
||||||
|
@ -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'),
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user