feat(ui): "stagingArea" -> "session"

This commit is contained in:
psychedelicious 2024-07-08 15:44:42 +10:00
parent 0c539ff00b
commit d4da00e607
13 changed files with 116 additions and 108 deletions

View File

@ -4,8 +4,8 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { import {
layerAdded, layerAdded,
layerImageAdded, layerImageAdded,
stagingAreaCanceledStaging, sessionStagingCanceled,
stagingAreaImageAccepted, sessionStagedImageAccepted,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
@ -14,7 +14,7 @@ import { assert } from 'tsafe';
export const addStagingListeners = (startAppListening: AppStartListening) => { export const addStagingListeners = (startAppListening: AppStartListening) => {
startAppListening({ startAppListening({
matcher: isAnyOf(stagingAreaCanceledStaging, stagingAreaImageAccepted), matcher: isAnyOf(sessionStagingCanceled, sessionStagedImageAccepted),
effect: async (_, { dispatch }) => { effect: async (_, { dispatch }) => {
const log = logger('canvas'); const log = logger('canvas');
@ -47,7 +47,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
}); });
startAppListening({ startAppListening({
actionCreator: stagingAreaImageAccepted, actionCreator: sessionStagedImageAccepted,
effect: async (action, api) => { effect: async (action, api) => {
const { imageDTO } = action.payload; const { imageDTO } = action.payload;
const { layers, selectedEntityIdentifier, bbox } = api.getState().canvasV2; const { layers, selectedEntityIdentifier, bbox } = api.getState().canvasV2;

View File

@ -1,7 +1,7 @@
import { enqueueRequested } from 'app/store/actions'; import { enqueueRequested } from 'app/store/actions';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { getCanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getCanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { stagingAreaCanceledStaging, stagingAreaStartedStaging } from 'features/controlLayers/store/canvasV2Slice'; import { sessionStagingCanceled, sessionStartedStaging } from 'features/controlLayers/store/canvasV2Slice';
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';
@ -18,8 +18,8 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
const { prepend } = action.payload; const { prepend } = action.payload;
let didStartStaging = false; let didStartStaging = false;
if (!state.canvasV2.stagingArea.isStaging) { if (!state.canvasV2.session.isStaging) {
dispatch(stagingAreaStartedStaging()); dispatch(sessionStartedStaging());
didStartStaging = true; didStartStaging = true;
} }
@ -48,8 +48,8 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
req.reset(); req.reset();
await req.unwrap(); await req.unwrap();
} catch { } catch {
if (didStartStaging && getState().canvasV2.stagingArea.isStaging) { if (didStartStaging && getState().canvasV2.session.isStaging) {
dispatch(stagingAreaCanceledStaging()); dispatch(sessionStagingCanceled());
} }
} }
}, },

View File

@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { stagingAreaImageAdded } from 'features/controlLayers/store/canvasV2Slice'; import { sessionImageStaged } from 'features/controlLayers/store/canvasV2Slice';
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';
@ -42,8 +42,8 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi
// handle tab-specific logic // handle tab-specific logic
if (data.origin === 'canvas') { if (data.origin === 'canvas') {
if (data.invocation_source_id === CANVAS_OUTPUT && canvasV2.stagingArea.isStaging) { if (data.invocation_source_id === CANVAS_OUTPUT && canvasV2.session.isStaging) {
dispatch(stagingAreaImageAdded({ imageDTO })); dispatch(sessionImageStaged({ imageDTO }));
} }
} else if (data.origin === 'workflows') { } else if (data.origin === 'workflows') {
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]); const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);

View File

@ -3,11 +3,11 @@ import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$shouldShowStagedImage, $shouldShowStagedImage,
stagingAreaCanceledStaging, sessionStagingCanceled,
stagingAreaImageAccepted, sessionStagedImageAccepted,
stagingAreaImageDiscarded, sessionStagedImageDiscarded,
stagingAreaNextImageSelected, sessionNextStagedImageSelected,
stagingAreaPreviousImageSelected, sessionPrevStagedImageSelected,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -24,7 +24,7 @@ import {
} from 'react-icons/pi'; } from 'react-icons/pi';
export const StagingAreaToolbar = memo(() => { export const StagingAreaToolbar = memo(() => {
const isStaging = useAppSelector((s) => s.canvasV2.stagingArea.isStaging); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
if (!isStaging) { if (!isStaging) {
return null; return null;
@ -37,30 +37,30 @@ StagingAreaToolbar.displayName = 'StagingAreaToolbar';
export const StagingAreaToolbarContent = memo(() => { export const StagingAreaToolbarContent = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const stagingArea = useAppSelector((s) => s.canvasV2.stagingArea); const stagingArea = useAppSelector((s) => s.canvasV2.session);
const shouldShowStagedImage = useStore($shouldShowStagedImage); const shouldShowStagedImage = useStore($shouldShowStagedImage);
const images = useMemo(() => stagingArea.images, [stagingArea]); const images = useMemo(() => stagingArea.stagedImages, [stagingArea]);
const selectedImageDTO = useMemo(() => { const selectedImageDTO = useMemo(() => {
return images[stagingArea.selectedImageIndex] ?? null; return images[stagingArea.selectedStagedImageIndex] ?? null;
}, [images, stagingArea.selectedImageIndex]); }, [images, stagingArea.selectedStagedImageIndex]);
// const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation(); // const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation();
const { t } = useTranslation(); const { t } = useTranslation();
const onPrev = useCallback(() => { const onPrev = useCallback(() => {
dispatch(stagingAreaPreviousImageSelected()); dispatch(sessionPrevStagedImageSelected());
}, [dispatch]); }, [dispatch]);
const onNext = useCallback(() => { const onNext = useCallback(() => {
dispatch(stagingAreaNextImageSelected()); dispatch(sessionNextStagedImageSelected());
}, [dispatch]); }, [dispatch]);
const onAccept = useCallback(() => { const onAccept = useCallback(() => {
if (!selectedImageDTO) { if (!selectedImageDTO) {
return; return;
} }
dispatch(stagingAreaImageAccepted({ imageDTO: selectedImageDTO })); dispatch(sessionStagedImageAccepted({ imageDTO: selectedImageDTO }));
}, [dispatch, selectedImageDTO]); }, [dispatch, selectedImageDTO]);
const onDiscardOne = useCallback(() => { const onDiscardOne = useCallback(() => {
@ -68,14 +68,14 @@ export const StagingAreaToolbarContent = memo(() => {
return; return;
} }
if (images.length === 1) { if (images.length === 1) {
dispatch(stagingAreaCanceledStaging()); dispatch(sessionStagingCanceled());
} else { } else {
dispatch(stagingAreaImageDiscarded({ imageDTO: selectedImageDTO })); dispatch(sessionStagedImageDiscarded({ imageDTO: selectedImageDTO }));
} }
}, [dispatch, selectedImageDTO, images.length]); }, [dispatch, selectedImageDTO, images.length]);
const onDiscardAll = useCallback(() => { const onDiscardAll = useCallback(() => {
dispatch(stagingAreaCanceledStaging()); dispatch(sessionStagingCanceled());
}, [dispatch]); }, [dispatch]);
const onToggleShouldShowStagedImage = useCallback(() => { const onToggleShouldShowStagedImage = useCallback(() => {
@ -109,11 +109,11 @@ export const StagingAreaToolbarContent = memo(() => {
const counterText = useMemo(() => { const counterText = useMemo(() => {
if (images.length > 0) { if (images.length > 0) {
return `${(stagingArea.selectedImageIndex ?? 0) + 1} of ${images.length}`; return `${(stagingArea.selectedStagedImageIndex ?? 0) + 1} of ${images.length}`;
} else { } else {
return `0 of 0`; return `0 of 0`;
} }
}, [images.length, stagingArea.selectedImageIndex]); }, [images.length, stagingArea.selectedStagedImageIndex]);
return ( return (
<> <>

View File

@ -43,7 +43,7 @@ export const ToolChooser: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const isStaging = useAppSelector((s) => s.canvasV2.stagingArea.isStaging); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
const isDrawingToolDisabled = useMemo( const isDrawingToolDisabled = useMemo(
() => !getIsDrawingToolEnabled(selectedEntityIdentifier), () => !getIsDrawingToolEnabled(selectedEntityIdentifier),
[selectedEntityIdentifier] [selectedEntityIdentifier]

View File

@ -281,7 +281,7 @@ export class CanvasManager {
// debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged); // debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
} }
if (this.isFirstRender || state.stagingArea !== this.prevState.stagingArea) { if (this.isFirstRender || state.session !== this.prevState.session) {
log.debug('Rendering staging area'); log.debug('Rendering staging area');
this.preview.stagingArea.render(); this.preview.stagingArea.render();
} }

View File

@ -25,7 +25,7 @@ export class CanvasStagingArea {
const shouldShowStagedImage = this.manager.stateApi.getShouldShowStagedImage(); const shouldShowStagedImage = this.manager.stateApi.getShouldShowStagedImage();
const lastProgressEvent = this.manager.stateApi.getLastProgressEvent(); const lastProgressEvent = this.manager.stateApi.getLastProgressEvent();
this.imageDTO = stagingArea.images[stagingArea.selectedImageIndex] ?? null; this.imageDTO = stagingArea.stagedImages[stagingArea.selectedStagedImageIndex] ?? null;
if (this.imageDTO) { if (this.imageDTO) {
if (this.image) { if (this.image) {

View File

@ -232,7 +232,7 @@ export class CanvasStateApi {
return this.getState().settings.maskOpacity; return this.getState().settings.maskOpacity;
}; };
getStagingAreaState = () => { getStagingAreaState = () => {
return this.getState().stagingArea; return this.getState().session;
}; };
getIsSelected = (id: string) => { getIsSelected = (id: string) => {
return this.getSelectedEntity()?.id === id; return this.getSelectedEntity()?.id === id;

View File

@ -13,8 +13,8 @@ import { layersReducers } from 'features/controlLayers/store/layersReducers';
import { lorasReducers } from 'features/controlLayers/store/lorasReducers'; import { lorasReducers } from 'features/controlLayers/store/lorasReducers';
import { paramsReducers } from 'features/controlLayers/store/paramsReducers'; import { paramsReducers } from 'features/controlLayers/store/paramsReducers';
import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { settingsReducers } from 'features/controlLayers/store/settingsReducers'; import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
import { stagingAreaReducers } from 'features/controlLayers/store/stagingAreaReducers';
import { toolReducers } from 'features/controlLayers/store/toolReducers'; import { toolReducers } from 'features/controlLayers/store/toolReducers';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
@ -122,10 +122,11 @@ const initialState: CanvasV2State = {
refinerNegativeAestheticScore: 2.5, refinerNegativeAestheticScore: 2.5,
refinerStart: 0.8, refinerStart: 0.8,
}, },
stagingArea: { session: {
isActive: false,
isStaging: false, isStaging: false,
images: [], stagedImages: [],
selectedImageIndex: 0, selectedStagedImageIndex: 0,
}, },
}; };
@ -144,7 +145,7 @@ export const canvasV2Slice = createSlice({
...toolReducers, ...toolReducers,
...bboxReducers, ...bboxReducers,
...inpaintMaskReducers, ...inpaintMaskReducers,
...stagingAreaReducers, ...sessionReducers,
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => { widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
const { width, updateAspectRatio, clamp } = action.payload; const { width, updateAspectRatio, clamp } = action.payload;
state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width; state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
@ -184,7 +185,7 @@ export const canvasV2Slice = createSlice({
state.layers = deepClone(initialState.layers); state.layers = deepClone(initialState.layers);
state.regions = deepClone(initialState.regions); state.regions = deepClone(initialState.regions);
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier); state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
state.stagingArea = deepClone(initialState.stagingArea); state.session = deepClone(initialState.session);
state.tool = deepClone(initialState.tool); state.tool = deepClone(initialState.tool);
}, },
}, },
@ -350,13 +351,14 @@ export const {
imEraserLineAdded, imEraserLineAdded,
imRectShapeAdded, imRectShapeAdded,
// Staging // Staging
stagingAreaStartedStaging, sessionStarted,
stagingAreaImageAdded, sessionStartedStaging,
stagingAreaImageDiscarded, sessionImageStaged,
stagingAreaImageAccepted, sessionStagedImageDiscarded,
stagingAreaCanceledStaging, sessionStagedImageAccepted,
stagingAreaNextImageSelected, sessionStagingCanceled,
stagingAreaPreviousImageSelected, sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
} = canvasV2Slice.actions; } = canvasV2Slice.actions;
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;

View File

@ -0,0 +1,62 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { ImageDTO } from 'services/api/types';
export const sessionReducers = {
sessionStarted: (state) => {
state.session.isActive = true;
},
sessionStartedStaging: (state) => {
state.session.isStaging = true;
state.session.selectedStagedImageIndex = 0;
// When we start staging, the user should not be interacting with the stage except to move it around. Set the tool
// to view.
state.tool.selectedBuffer = state.tool.selected;
state.tool.selected = 'view';
},
sessionImageStaged: (state, action: PayloadAction<{ imageDTO: ImageDTO }>) => {
const { imageDTO } = action.payload;
state.session.stagedImages.push(imageDTO);
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<{ imageDTO: ImageDTO }>) => {
const { imageDTO } = action.payload;
state.session.stagedImages = state.session.stagedImages.filter((image) => image.image_name !== imageDTO.image_name);
state.session.selectedStagedImageIndex = Math.min(
state.session.selectedStagedImageIndex,
state.session.stagedImages.length - 1
);
if (state.session.stagedImages.length === 0) {
state.session.isStaging = false;
}
},
sessionStagedImageAccepted: (state, _: PayloadAction<{ imageDTO: ImageDTO }>) => {
// When we finish staging, reset the tool back to the previous selection.
state.session.isStaging = false;
state.session.stagedImages = [];
state.session.selectedStagedImageIndex = 0;
if (state.tool.selectedBuffer) {
state.tool.selected = state.tool.selectedBuffer;
state.tool.selectedBuffer = null;
}
},
sessionStagingCanceled: (state) => {
state.session.isStaging = false;
state.session.stagedImages = [];
state.session.selectedStagedImageIndex = 0;
// When we finish staging, reset the tool back to the previous selection.
if (state.tool.selectedBuffer) {
state.tool.selected = state.tool.selectedBuffer;
state.tool.selectedBuffer = null;
}
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -1,57 +0,0 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { ImageDTO } from 'services/api/types';
export const stagingAreaReducers = {
stagingAreaStartedStaging: (state) => {
state.stagingArea.isStaging = true;
state.stagingArea.selectedImageIndex = 0;
// When we start staging, the user should not be interacting with the stage except to move it around. Set the tool
// to view.
state.tool.selectedBuffer = state.tool.selected;
state.tool.selected = 'view';
},
stagingAreaImageAdded: (state, action: PayloadAction<{ imageDTO: ImageDTO }>) => {
const { imageDTO } = action.payload;
state.stagingArea.images.push(imageDTO);
state.stagingArea.selectedImageIndex = state.stagingArea.images.length - 1;
},
stagingAreaNextImageSelected: (state) => {
state.stagingArea.selectedImageIndex = (state.stagingArea.selectedImageIndex + 1) % state.stagingArea.images.length;
},
stagingAreaPreviousImageSelected: (state) => {
state.stagingArea.selectedImageIndex =
(state.stagingArea.selectedImageIndex - 1 + state.stagingArea.images.length) % state.stagingArea.images.length;
},
stagingAreaImageDiscarded: (state, action: PayloadAction<{ imageDTO: ImageDTO }>) => {
const { imageDTO } = action.payload;
state.stagingArea.images = state.stagingArea.images.filter((image) => image.image_name !== imageDTO.image_name);
state.stagingArea.selectedImageIndex = Math.min(
state.stagingArea.selectedImageIndex,
state.stagingArea.images.length - 1
);
if (state.stagingArea.images.length === 0) {
state.stagingArea.isStaging = false;
}
},
stagingAreaImageAccepted: (state, _: PayloadAction<{ imageDTO: ImageDTO }>) => {
// When we finish staging, reset the tool back to the previous selection.
state.stagingArea.isStaging = false;
state.stagingArea.images = [];
state.stagingArea.selectedImageIndex = 0;
if (state.tool.selectedBuffer) {
state.tool.selected = state.tool.selectedBuffer;
state.tool.selectedBuffer = null;
}
},
stagingAreaCanceledStaging: (state) => {
state.stagingArea.isStaging = false;
state.stagingArea.images = [];
state.stagingArea.selectedImageIndex = 0;
// When we finish staging, reset the tool back to the previous selection.
if (state.tool.selectedBuffer) {
state.tool.selected = state.tool.selectedBuffer;
state.tool.selectedBuffer = null;
}
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -895,10 +895,11 @@ export type CanvasV2State = {
refinerNegativeAestheticScore: number; refinerNegativeAestheticScore: number;
refinerStart: number; refinerStart: number;
}; };
stagingArea: { session: {
isActive: boolean;
isStaging: boolean; isStaging: boolean;
images: ImageDTO[]; stagedImages: ImageDTO[];
selectedImageIndex: number; selectedStagedImageIndex: number;
}; };
}; };

View File

@ -9,7 +9,7 @@ import { useListImagesQuery } from 'services/api/endpoints/images';
* Registers gallery hotkeys. This hook is a singleton. * Registers gallery hotkeys. This hook is a singleton.
*/ */
export const useGalleryHotkeys = () => { export const useGalleryHotkeys = () => {
const isStaging = useAppSelector((s) => s.canvasV2.stagingArea.isStaging); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
const { goNext, goPrev, isNextEnabled, isPrevEnabled } = useGalleryPagination(); const { goNext, goPrev, isNextEnabled, isPrevEnabled } = useGalleryPagination();
const queryArgs = useAppSelector(selectListImagesQueryArgs); const queryArgs = useAppSelector(selectListImagesQueryArgs);