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

View File

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

View File

@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { deepClone } from 'common/util/deepClone';
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 { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
import { zNodeStatus } from 'features/nodes/types/invocation';
@ -42,8 +42,8 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi
// handle tab-specific logic
if (data.origin === 'canvas') {
if (data.invocation_source_id === CANVAS_OUTPUT && canvasV2.stagingArea.isStaging) {
dispatch(stagingAreaImageAdded({ imageDTO }));
if (data.invocation_source_id === CANVAS_OUTPUT && canvasV2.session.isStaging) {
dispatch(sessionImageStaged({ imageDTO }));
}
} else if (data.origin === 'workflows') {
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 {
$shouldShowStagedImage,
stagingAreaCanceledStaging,
stagingAreaImageAccepted,
stagingAreaImageDiscarded,
stagingAreaNextImageSelected,
stagingAreaPreviousImageSelected,
sessionStagingCanceled,
sessionStagedImageAccepted,
sessionStagedImageDiscarded,
sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
} from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
@ -24,7 +24,7 @@ import {
} from 'react-icons/pi';
export const StagingAreaToolbar = memo(() => {
const isStaging = useAppSelector((s) => s.canvasV2.stagingArea.isStaging);
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
if (!isStaging) {
return null;
@ -37,30 +37,30 @@ StagingAreaToolbar.displayName = 'StagingAreaToolbar';
export const StagingAreaToolbarContent = memo(() => {
const dispatch = useAppDispatch();
const stagingArea = useAppSelector((s) => s.canvasV2.stagingArea);
const stagingArea = useAppSelector((s) => s.canvasV2.session);
const shouldShowStagedImage = useStore($shouldShowStagedImage);
const images = useMemo(() => stagingArea.images, [stagingArea]);
const images = useMemo(() => stagingArea.stagedImages, [stagingArea]);
const selectedImageDTO = useMemo(() => {
return images[stagingArea.selectedImageIndex] ?? null;
}, [images, stagingArea.selectedImageIndex]);
return images[stagingArea.selectedStagedImageIndex] ?? null;
}, [images, stagingArea.selectedStagedImageIndex]);
// const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation();
const { t } = useTranslation();
const onPrev = useCallback(() => {
dispatch(stagingAreaPreviousImageSelected());
dispatch(sessionPrevStagedImageSelected());
}, [dispatch]);
const onNext = useCallback(() => {
dispatch(stagingAreaNextImageSelected());
dispatch(sessionNextStagedImageSelected());
}, [dispatch]);
const onAccept = useCallback(() => {
if (!selectedImageDTO) {
return;
}
dispatch(stagingAreaImageAccepted({ imageDTO: selectedImageDTO }));
dispatch(sessionStagedImageAccepted({ imageDTO: selectedImageDTO }));
}, [dispatch, selectedImageDTO]);
const onDiscardOne = useCallback(() => {
@ -68,14 +68,14 @@ export const StagingAreaToolbarContent = memo(() => {
return;
}
if (images.length === 1) {
dispatch(stagingAreaCanceledStaging());
dispatch(sessionStagingCanceled());
} else {
dispatch(stagingAreaImageDiscarded({ imageDTO: selectedImageDTO }));
dispatch(sessionStagedImageDiscarded({ imageDTO: selectedImageDTO }));
}
}, [dispatch, selectedImageDTO, images.length]);
const onDiscardAll = useCallback(() => {
dispatch(stagingAreaCanceledStaging());
dispatch(sessionStagingCanceled());
}, [dispatch]);
const onToggleShouldShowStagedImage = useCallback(() => {
@ -109,11 +109,11 @@ export const StagingAreaToolbarContent = memo(() => {
const counterText = useMemo(() => {
if (images.length > 0) {
return `${(stagingArea.selectedImageIndex ?? 0) + 1} of ${images.length}`;
return `${(stagingArea.selectedStagedImageIndex ?? 0) + 1} of ${images.length}`;
} else {
return `0 of 0`;
}
}, [images.length, stagingArea.selectedImageIndex]);
}, [images.length, stagingArea.selectedStagedImageIndex]);
return (
<>

View File

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

View File

@ -281,7 +281,7 @@ export class CanvasManager {
// 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');
this.preview.stagingArea.render();
}

View File

@ -25,7 +25,7 @@ export class CanvasStagingArea {
const shouldShowStagedImage = this.manager.stateApi.getShouldShowStagedImage();
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.image) {

View File

@ -232,7 +232,7 @@ export class CanvasStateApi {
return this.getState().settings.maskOpacity;
};
getStagingAreaState = () => {
return this.getState().stagingArea;
return this.getState().session;
};
getIsSelected = (id: string) => {
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 { paramsReducers } from 'features/controlLayers/store/paramsReducers';
import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
import { stagingAreaReducers } from 'features/controlLayers/store/stagingAreaReducers';
import { toolReducers } from 'features/controlLayers/store/toolReducers';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
@ -122,10 +122,11 @@ const initialState: CanvasV2State = {
refinerNegativeAestheticScore: 2.5,
refinerStart: 0.8,
},
stagingArea: {
session: {
isActive: false,
isStaging: false,
images: [],
selectedImageIndex: 0,
stagedImages: [],
selectedStagedImageIndex: 0,
},
};
@ -144,7 +145,7 @@ export const canvasV2Slice = createSlice({
...toolReducers,
...bboxReducers,
...inpaintMaskReducers,
...stagingAreaReducers,
...sessionReducers,
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
const { width, updateAspectRatio, clamp } = action.payload;
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.regions = deepClone(initialState.regions);
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
state.stagingArea = deepClone(initialState.stagingArea);
state.session = deepClone(initialState.session);
state.tool = deepClone(initialState.tool);
},
},
@ -350,13 +351,14 @@ export const {
imEraserLineAdded,
imRectShapeAdded,
// Staging
stagingAreaStartedStaging,
stagingAreaImageAdded,
stagingAreaImageDiscarded,
stagingAreaImageAccepted,
stagingAreaCanceledStaging,
stagingAreaNextImageSelected,
stagingAreaPreviousImageSelected,
sessionStarted,
sessionStartedStaging,
sessionImageStaged,
sessionStagedImageDiscarded,
sessionStagedImageAccepted,
sessionStagingCanceled,
sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
} = canvasV2Slice.actions;
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;
refinerStart: number;
};
stagingArea: {
session: {
isActive: boolean;
isStaging: boolean;
images: ImageDTO[];
selectedImageIndex: number;
stagedImages: ImageDTO[];
selectedStagedImageIndex: number;
};
};

View File

@ -9,7 +9,7 @@ import { useListImagesQuery } from 'services/api/endpoints/images';
* Registers gallery hotkeys. This hook is a singleton.
*/
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 queryArgs = useAppSelector(selectListImagesQueryArgs);