mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-12 21:14:59 -05:00
feat(ui): update canvas session state handling for new staging strat
This commit is contained in:
@@ -3,7 +3,7 @@ import { useAppStore } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
@@ -90,7 +90,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
|
||||
const overrides: Partial<CanvasRasterLayerState> = {
|
||||
objects: [imageObject],
|
||||
};
|
||||
store.dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
store.dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
store.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
store.dispatch(sentImageToCanvas());
|
||||
@@ -162,14 +162,14 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
|
||||
switch (destination) {
|
||||
case 'generation':
|
||||
// Go to the canvas tab, open the image viewer, and enable send-to-gallery mode
|
||||
store.dispatch(canvasSessionStarted({ sessionType: 'simple' }));
|
||||
store.dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
store.dispatch(activeTabCanvasRightPanelChanged('gallery'));
|
||||
$imageViewer.set(true);
|
||||
break;
|
||||
case 'canvas':
|
||||
// Go to the canvas tab, close the image viewer, and disable send-to-gallery mode
|
||||
store.dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
store.dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
$imageViewer.set(false);
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { TypedStartListening } from '@reduxjs/toolkit';
|
||||
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
||||
import { addStagingListeners } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
|
||||
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
||||
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
|
||||
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
||||
@@ -65,9 +64,6 @@ addEnqueueRequestedUpscale(startAppListening);
|
||||
addAnyEnqueuedListener(startAppListening);
|
||||
addBatchEnqueuedListener(startAppListening);
|
||||
|
||||
// Canvas actions
|
||||
addStagingListeners(startAppListening);
|
||||
|
||||
// Socket.IO
|
||||
addSocketConnectedEventListener(startAppListening);
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { isAnyOf } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import { stagingAreaReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
|
||||
const log = logger('canvas');
|
||||
|
||||
const matchCanvasOrStagingAreaReset = isAnyOf(stagingAreaReset, canvasReset);
|
||||
|
||||
export const addStagingListeners = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
matcher: matchCanvasOrStagingAreaReset,
|
||||
effect: async (_, { dispatch }) => {
|
||||
try {
|
||||
const req = dispatch(
|
||||
queueApi.endpoints.cancelByDestination.initiate(
|
||||
{ destination: 'canvas' },
|
||||
{ fixedCacheKey: 'cancelByBatchOrigin' }
|
||||
)
|
||||
);
|
||||
const { canceled } = await req.unwrap();
|
||||
req.reset();
|
||||
|
||||
if (canceled > 0) {
|
||||
log.debug(`Canceled ${canceled} canvas batches`);
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_SUCCEEDED',
|
||||
title: t('queue.cancelBatchSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
log.error('Failed to cancel canvas batches');
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_FAILED',
|
||||
title: t('queue.cancelBatchFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -5,7 +5,10 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { extractMessageFromAssertionError } from 'common/util/extractMessageFromAssertionError';
|
||||
import { withResult, withResultAsync } from 'common/util/result';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { canvasSessionStarted, selectCanvasSession } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import {
|
||||
canvasSessionGenerationStarted,
|
||||
selectCanvasSessionId,
|
||||
} from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { $canvasManager } from 'features/controlLayers/store/ephemeral';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
import { buildChatGPT4oGraph } from 'features/nodes/util/graph/generation/buildChatGPT4oGraph';
|
||||
@@ -33,11 +36,14 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
log.debug('Enqueue requested');
|
||||
|
||||
if (!selectCanvasSession(getState())) {
|
||||
dispatch(canvasSessionStarted({ sessionType: 'simple' }));
|
||||
if (!selectCanvasSessionId(getState())) {
|
||||
dispatch(canvasSessionGenerationStarted());
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const destination = state.canvasSession.id;
|
||||
assert(destination !== null);
|
||||
|
||||
const { prepend } = action.payload;
|
||||
|
||||
const manager = $canvasManager.get();
|
||||
@@ -96,8 +102,6 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
|
||||
const { g, seedFieldIdentifier, positivePromptFieldIdentifier } = buildGraphResult.value;
|
||||
|
||||
const destination = state.canvasSession.session?.id ?? 'canvas';
|
||||
|
||||
const prepareBatchResult = withResult(() =>
|
||||
prepareLinearUIBatch({
|
||||
state,
|
||||
|
||||
@@ -20,7 +20,6 @@ import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasT
|
||||
import { Transform } from 'features/controlLayers/components/Transform/Transform';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import type { AdvancedSessionIdentifier } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
|
||||
|
||||
@@ -53,7 +52,7 @@ const canvasBgSx = {
|
||||
},
|
||||
};
|
||||
|
||||
export const AdvancedSession = memo(({ session }: { session: AdvancedSessionIdentifier }) => {
|
||||
export const AdvancedSession = memo(({ id }: { id: string | null }) => {
|
||||
const dynamicGrid = useAppSelector(selectDynamicGrid);
|
||||
const showHUD = useAppSelector(selectShowHUD);
|
||||
|
||||
@@ -107,27 +106,29 @@ export const AdvancedSession = memo(({ session }: { session: AdvancedSessionIden
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasSessionContextProvider session={session}>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
bottom={4}
|
||||
gap={2}
|
||||
align="center"
|
||||
justify="center"
|
||||
left={4}
|
||||
right={4}
|
||||
>
|
||||
<Flex position="relative" maxW="full" w="full" h={108}>
|
||||
<StagingAreaItemsList />
|
||||
{id !== null && (
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasSessionContextProvider type="advanced" id={id}>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
bottom={4}
|
||||
gap={2}
|
||||
align="center"
|
||||
justify="center"
|
||||
left={4}
|
||||
right={4}
|
||||
>
|
||||
<Flex position="relative" maxW="full" w="full" h={108}>
|
||||
<StagingAreaItemsList />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<StagingAreaToolbar />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<StagingAreaToolbar />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CanvasSessionContextProvider>
|
||||
</CanvasManagerProviderGate>
|
||||
</CanvasSessionContextProvider>
|
||||
</CanvasManagerProviderGate>
|
||||
)}
|
||||
<Flex position="absolute" bottom={4}>
|
||||
<CanvasManagerProviderGate>
|
||||
<Filter />
|
||||
|
||||
@@ -2,26 +2,27 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { AdvancedSession } from 'features/controlLayers/components/AdvancedSession/AdvancedSession';
|
||||
import { NoSession } from 'features/controlLayers/components/NoSession/NoSession';
|
||||
import { SimpleSession } from 'features/controlLayers/components/SimpleSession/SimpleSession';
|
||||
import { selectCanvasSession } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSessionId, selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { memo } from 'react';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const CanvasMainPanelContent = memo(() => {
|
||||
const session = useAppSelector(selectCanvasSession);
|
||||
const type = useAppSelector(selectCanvasSessionType);
|
||||
const id = useAppSelector(selectCanvasSessionId);
|
||||
|
||||
if (session === null) {
|
||||
return <NoSession />;
|
||||
if (type === 'simple') {
|
||||
if (id === null) {
|
||||
return <NoSession />;
|
||||
} else {
|
||||
return <SimpleSession id={id} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (session.type === 'simple') {
|
||||
return <SimpleSession session={session} />;
|
||||
if (type === 'advanced') {
|
||||
return <AdvancedSession id={id} />;
|
||||
}
|
||||
|
||||
if (session.type === 'advanced') {
|
||||
return <AdvancedSession session={session} />;
|
||||
}
|
||||
|
||||
assert<Equals<never, typeof session>>(false, 'Unexpected session');
|
||||
assert<Equals<never, typeof type>>(false, 'Unexpected session type');
|
||||
});
|
||||
CanvasMainPanelContent.displayName = 'CanvasMainPanelContent';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Checkbox, ConfirmationAlertDialog, Flex, FormControl, FormLabel, Text }
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import {
|
||||
selectSystemShouldConfirmOnNewSession,
|
||||
shouldConfirmOnNewSessionToggled,
|
||||
@@ -20,7 +20,7 @@ export const useNewGallerySession = () => {
|
||||
const newSessionDialog = useNewGallerySessionDialog();
|
||||
|
||||
const newGallerySessionImmediate = useCallback(() => {
|
||||
dispatch(canvasSessionStarted({ sessionType: 'simple' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
dispatch(activeTabCanvasRightPanelChanged('gallery'));
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -41,7 +41,7 @@ export const useNewCanvasSession = () => {
|
||||
const newSessionDialog = useNewCanvasSessionDialog();
|
||||
|
||||
const newCanvasSessionImmediate = useCallback(() => {
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(activeTabCanvasRightPanelChanged('layers'));
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { GenerateWithControlImage } from 'features/controlLayers/components/NoSession/GenerateWithControlImage';
|
||||
import { GenerateWithStartingImage } from 'features/controlLayers/components/NoSession/GenerateWithStartingImage';
|
||||
import { GenerateWithStartingImageAndInpaintMask } from 'features/controlLayers/components/NoSession/GenerateWithStartingImageAndInpaintMask';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const NoSession = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const newSesh = useCallback(() => {
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { CanvasSessionContextProvider } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { StagingArea } from 'features/controlLayers/components/SimpleSession/StagingArea';
|
||||
import type { SimpleSessionIdentifier } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const SimpleSession = memo(({ session }: { session: SimpleSessionIdentifier }) => {
|
||||
export const SimpleSession = memo(({ id }: { id: string }) => {
|
||||
return (
|
||||
<CanvasSessionContextProvider session={session}>
|
||||
<CanvasSessionContextProvider type="simple" id={id}>
|
||||
<StagingArea />
|
||||
</CanvasSessionContextProvider>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button, Flex, FormControl, FormLabel, Spacer, Switch, Text } from '@inv
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
@@ -13,7 +13,7 @@ export const StagingAreaHeader = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const startOver = useCallback(() => {
|
||||
dispatch(canvasSessionStarted({ sessionType: 'simple' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
}, [dispatch]);
|
||||
|
||||
const onChangeAutoSwitch = useCallback(
|
||||
|
||||
@@ -3,10 +3,6 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { getOutputImageName } from 'features/controlLayers/components/SimpleSession/shared';
|
||||
import type {
|
||||
AdvancedSessionIdentifier,
|
||||
SimpleSessionIdentifier,
|
||||
} from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { ProgressImage } from 'features/nodes/types/common';
|
||||
import type { Atom, WritableAtom } from 'nanostores';
|
||||
import { atom, computed, effect } from 'nanostores';
|
||||
@@ -100,7 +96,7 @@ export const clearProgressImage = ($progressData: WritableAtom<Record<number, Pr
|
||||
};
|
||||
|
||||
type CanvasSessionContextValue = {
|
||||
session: SimpleSessionIdentifier | AdvancedSessionIdentifier;
|
||||
session: { id: string; type: 'simple' | 'advanced' };
|
||||
$items: Atom<S['SessionQueueItem'][]>;
|
||||
$itemCount: Atom<number>;
|
||||
$hasItems: Atom<boolean>;
|
||||
@@ -120,12 +116,13 @@ type CanvasSessionContextValue = {
|
||||
const CanvasSessionContext = createContext<CanvasSessionContextValue | null>(null);
|
||||
|
||||
export const CanvasSessionContextProvider = memo(
|
||||
({ session, children }: PropsWithChildren<{ session: SimpleSessionIdentifier | AdvancedSessionIdentifier }>) => {
|
||||
({ id, type, children }: PropsWithChildren<{ id: string; type: 'simple' | 'advanced' }>) => {
|
||||
/**
|
||||
* For best performance and interop with the Canvas, which is outside react but needs to interact with the react
|
||||
* app, all canvas session state is packaged as nanostores atoms. The trickiest part is syncing the queue items
|
||||
* with a nanostores atom.
|
||||
*/
|
||||
const session = useMemo(() => ({ type, id }), [type, id]);
|
||||
|
||||
/**
|
||||
* App store
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useIsRegionFocused } from 'common/hooks/focus';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectImageCount, stagingAreaReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectBboxRect, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||
import { imageNameToImageObject } from 'features/controlLayers/store/util';
|
||||
@@ -20,7 +19,6 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
const canvasManager = useCanvasManager();
|
||||
const bboxRect = useAppSelector(selectBboxRect);
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const imageCount = useAppSelector(selectImageCount);
|
||||
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
|
||||
const isCanvasFocused = useIsRegionFocused('canvas');
|
||||
const selectedItemImageName = useStore(ctx.$selectedItemOutputImageName);
|
||||
@@ -39,7 +37,6 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
};
|
||||
|
||||
dispatch(rasterLayerAdded({ overrides, isSelected: selectedEntityIdentifier?.type === 'raster_layer' }));
|
||||
dispatch(stagingAreaReset());
|
||||
}, [bboxRect, selectedItemImageName, dispatch, selectedEntityIdentifier?.type]);
|
||||
|
||||
useHotkeys(
|
||||
@@ -47,9 +44,9 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
acceptSelected,
|
||||
{
|
||||
preventDefault: true,
|
||||
enabled: isCanvasFocused && shouldShowStagedImage && imageCount > 1,
|
||||
enabled: isCanvasFocused && shouldShowStagedImage && selectedItemImageName !== null,
|
||||
},
|
||||
[isCanvasFocused, shouldShowStagedImage, imageCount]
|
||||
[isCanvasFocused, shouldShowStagedImage, selectedItemImageName]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { selectSelectedImage } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -15,7 +14,6 @@ const TOAST_ID = 'SAVE_STAGING_AREA_IMAGE_TO_GALLERY';
|
||||
|
||||
export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
|
||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||
const selectedImage = useAppSelector(selectSelectedImage);
|
||||
const ctx = useCanvasSessionContext();
|
||||
const imageName = useStore(ctx.$selectedItemOutputImageName);
|
||||
|
||||
@@ -63,7 +61,7 @@ export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => {
|
||||
icon={<PiFloppyDiskBold />}
|
||||
onClick={saveSelectedImageToGallery}
|
||||
colorScheme="invokeBlue"
|
||||
isDisabled={!selectedImage}
|
||||
isDisabled={!imageName}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
rasterLayerAdded,
|
||||
rgAdded,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectCanvasStagingAreaSlice } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import {
|
||||
selectAllRenderableEntities,
|
||||
selectBbox,
|
||||
@@ -537,7 +537,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
* Gets the canvas staging area state from redux.
|
||||
*/
|
||||
getStagingArea = () => {
|
||||
return this.runSelector(selectCanvasStagingAreaSlice);
|
||||
return this.runSelector(selectCanvasSessionSlice);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { modelChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
selectAllEntities,
|
||||
@@ -1846,7 +1846,7 @@ export const canvasSlice = createSlice({
|
||||
syncScaledSize(state);
|
||||
}
|
||||
});
|
||||
builder.addCase(canvasSessionStarted, (state) => resetState(state));
|
||||
builder.addCase(canvasSessionReset, (state) => resetState(state));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,31 +3,15 @@ import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import type { StagingAreaImage, StagingAreaProgressImage } from 'features/controlLayers/store/types';
|
||||
import { selectCanvasQueueCounts } from 'services/api/endpoints/queue';
|
||||
|
||||
export type SimpleSessionIdentifier = {
|
||||
type: 'simple';
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type AdvancedSessionIdentifier = {
|
||||
type: 'advanced';
|
||||
id: string;
|
||||
};
|
||||
|
||||
type CanvasStagingAreaState = {
|
||||
session: SimpleSessionIdentifier | AdvancedSessionIdentifier | null;
|
||||
sessionType: 'simple' | 'advanced' | null;
|
||||
images: (StagingAreaImage | StagingAreaProgressImage)[];
|
||||
selectedImageIndex: number;
|
||||
type: 'simple' | 'advanced';
|
||||
id: string | null;
|
||||
};
|
||||
|
||||
const INITIAL_STATE: CanvasStagingAreaState = {
|
||||
session: null,
|
||||
sessionType: null,
|
||||
images: [],
|
||||
selectedImageIndex: 0,
|
||||
type: 'simple',
|
||||
id: null,
|
||||
};
|
||||
|
||||
const getInitialState = (): CanvasStagingAreaState => deepClone(INITIAL_STATE);
|
||||
@@ -36,68 +20,24 @@ export const canvasSessionSlice = createSlice({
|
||||
name: 'canvasSession',
|
||||
initialState: getInitialState(),
|
||||
reducers: {
|
||||
sessionChanged: (state, action: PayloadAction<{ session: CanvasStagingAreaState['session'] }>) => {
|
||||
const { session } = action.payload;
|
||||
state.session = session;
|
||||
canvasSessionTypeChanged: (state, action: PayloadAction<{ type: CanvasStagingAreaState['type'] }>) => {
|
||||
const { type } = action.payload;
|
||||
state.type = type;
|
||||
state.id = null;
|
||||
},
|
||||
stagingAreaImageStaged: (state, action: PayloadAction<{ stagingAreaImage: StagingAreaImage }>) => {
|
||||
const { stagingAreaImage } = action.payload;
|
||||
let didReplace = false;
|
||||
const newImages = [];
|
||||
for (const i of state.images) {
|
||||
if (i.sessionId === stagingAreaImage.sessionId) {
|
||||
newImages.push(stagingAreaImage);
|
||||
didReplace = true;
|
||||
} else {
|
||||
newImages.push(i);
|
||||
}
|
||||
}
|
||||
if (!didReplace) {
|
||||
newImages.push(stagingAreaImage);
|
||||
}
|
||||
state.images = newImages;
|
||||
},
|
||||
stagingAreaGenerationStarted: (state, action: PayloadAction<{ sessionId: string }>) => {
|
||||
const { sessionId } = action.payload;
|
||||
state.images.push({ type: 'progress', sessionId });
|
||||
},
|
||||
stagingAreaGenerationFinished: (state, action: PayloadAction<{ sessionId: string }>) => {
|
||||
const { sessionId } = action.payload;
|
||||
state.images = state.images.filter((data) => data.sessionId !== sessionId);
|
||||
},
|
||||
stagingAreaImageSelected: (state, action: PayloadAction<{ index: number }>) => {
|
||||
const { index } = action.payload;
|
||||
state.selectedImageIndex = index;
|
||||
},
|
||||
stagingAreaNextStagedImageSelected: (state) => {
|
||||
state.selectedImageIndex = (state.selectedImageIndex + 1) % state.images.length;
|
||||
},
|
||||
stagingAreaPrevStagedImageSelected: (state) => {
|
||||
state.selectedImageIndex = (state.selectedImageIndex - 1 + state.images.length) % state.images.length;
|
||||
},
|
||||
stagingAreaStagedImageDiscarded: (state, action: PayloadAction<{ index: number }>) => {
|
||||
const { index } = action.payload;
|
||||
state.images.splice(index, 1);
|
||||
state.selectedImageIndex = Math.min(state.selectedImageIndex, state.images.length - 1);
|
||||
},
|
||||
stagingAreaReset: (state) => {
|
||||
state.images = [];
|
||||
state.selectedImageIndex = 0;
|
||||
},
|
||||
canvasSessionStarted: {
|
||||
reducer: (state, action: PayloadAction<{ session: CanvasStagingAreaState['session'] }>) => {
|
||||
const { session } = action.payload;
|
||||
state.session = session;
|
||||
canvasSessionGenerationStarted: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.id = id;
|
||||
},
|
||||
prepare: (payload: { sessionType: 'simple' | 'advanced' }) => ({
|
||||
payload: {
|
||||
session: {
|
||||
type: payload.sessionType,
|
||||
id: getPrefixedId(`canvas:${payload.sessionType}`),
|
||||
},
|
||||
},
|
||||
prepare: () => ({
|
||||
payload: { id: getPrefixedId('canvas') },
|
||||
}),
|
||||
},
|
||||
canvasSessionGenerationFinished: (state) => {
|
||||
state.id = null;
|
||||
},
|
||||
canvasSessionReset: () => getInitialState(),
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasReset, () => getInitialState());
|
||||
@@ -105,16 +45,10 @@ export const canvasSessionSlice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
sessionChanged,
|
||||
stagingAreaImageStaged,
|
||||
stagingAreaGenerationStarted,
|
||||
stagingAreaGenerationFinished,
|
||||
stagingAreaStagedImageDiscarded,
|
||||
stagingAreaReset,
|
||||
stagingAreaImageSelected,
|
||||
stagingAreaNextStagedImageSelected,
|
||||
stagingAreaPrevStagedImageSelected,
|
||||
canvasSessionStarted,
|
||||
canvasSessionTypeChanged,
|
||||
canvasSessionGenerationStarted,
|
||||
canvasSessionReset,
|
||||
canvasSessionGenerationFinished,
|
||||
} = canvasSessionSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -129,44 +63,8 @@ export const canvasStagingAreaPersistConfig: PersistConfig<CanvasStagingAreaStat
|
||||
persistDenylist: [],
|
||||
};
|
||||
|
||||
export const selectCanvasStagingAreaSlice = (s: RootState) => s[canvasSessionSlice.name];
|
||||
export const selectCanvasSessionSlice = (s: RootState) => s[canvasSessionSlice.name];
|
||||
|
||||
/**
|
||||
* Selects if we should be staging images. This is true if:
|
||||
* - There are staged images.
|
||||
* - There are any in-progress or pending canvas queue items.
|
||||
*/
|
||||
export const selectIsStaging = createSelector(
|
||||
selectCanvasQueueCounts,
|
||||
selectCanvasStagingAreaSlice,
|
||||
({ data }, staging) => {
|
||||
if (staging.images.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
return data.in_progress > 0 || data.pending > 0;
|
||||
}
|
||||
);
|
||||
export const selectStagedImageIndex = createSelector(
|
||||
selectCanvasStagingAreaSlice,
|
||||
(stagingArea) => stagingArea.selectedImageIndex
|
||||
);
|
||||
export const selectSelectedImage = createSelector(
|
||||
[selectCanvasStagingAreaSlice, selectStagedImageIndex],
|
||||
(stagingArea, index) => stagingArea.images[index] ?? null
|
||||
);
|
||||
export const selectStagedImages = createSelector(selectCanvasStagingAreaSlice, (stagingArea) => stagingArea.images);
|
||||
export const selectImageCount = createSelector(
|
||||
selectCanvasStagingAreaSlice,
|
||||
(stagingArea) => stagingArea.images.length
|
||||
);
|
||||
export const selectCanvasSessionType = createSelector(
|
||||
selectCanvasStagingAreaSlice,
|
||||
(canvasSession) => canvasSession.sessionType
|
||||
);
|
||||
export const selectCanvasSession = createSelector(
|
||||
selectCanvasStagingAreaSlice,
|
||||
(canvasSession) => canvasSession.session
|
||||
);
|
||||
export const selectIsStaging = createSelector(selectCanvasSessionSlice, ({ id }) => id !== null);
|
||||
export const selectCanvasSessionType = createSelector(selectCanvasSessionSlice, ({ type }) => type);
|
||||
export const selectCanvasSessionId = createSelector(selectCanvasSessionSlice, ({ id }) => id);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { LoRA } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { LoRAModelConfig } from 'services/api/types';
|
||||
@@ -64,7 +64,7 @@ export const lorasSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionStarted, () => {
|
||||
builder.addCase(canvasSessionTypeChanged, () => {
|
||||
// When a new session is requested, clear all LoRAs
|
||||
return deepClone(initialState);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { ParameterHRFMethod, ParameterStrength } from 'features/parameters/types/parameterSchemas';
|
||||
|
||||
interface HRFState {
|
||||
@@ -34,7 +34,7 @@ export const hrfSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionStarted, () => {
|
||||
builder.addCase(canvasSessionTypeChanged, () => {
|
||||
return deepClone(initialHRFState);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
rgAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectBboxModelBase, selectBboxRect } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
@@ -194,7 +194,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasRasterLayerState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||
@@ -211,7 +211,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
controlAdapter: deepClone(initialControlNet),
|
||||
} satisfies Partial<CanvasControlLayerState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
||||
@@ -227,7 +227,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasInpaintMaskState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(inpaintMaskAdded({ overrides, isSelected: true }));
|
||||
@@ -243,7 +243,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasRegionalGuidanceState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(rgAdded({ overrides, isSelected: true }));
|
||||
@@ -256,7 +256,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
case 'reference_image': {
|
||||
const ipAdapter = deepClone(selectDefaultRefImageConfig(getState()));
|
||||
ipAdapter.image = imageDTOToImageWithDims(imageDTO);
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(referenceImageAdded({ overrides: { ipAdapter }, isSelected: true }));
|
||||
if (withInpaintMask) {
|
||||
dispatch(inpaintMaskAdded({ isSelected: true, isBookmarked: true }));
|
||||
@@ -268,7 +268,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
const ipAdapter = deepClone(selectDefaultIPAdapter(getState()));
|
||||
ipAdapter.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), ipAdapter }];
|
||||
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(rgAdded({ overrides: { referenceImages }, isSelected: true }));
|
||||
if (withInpaintMask) {
|
||||
dispatch(inpaintMaskAdded({ isSelected: true, isBookmarked: true }));
|
||||
|
||||
@@ -36,9 +36,9 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
|
||||
export const selectCanvasOutputFields = (state: RootState) => {
|
||||
// Advanced session means working on canvas - images are not saved to gallery or added to a board.
|
||||
// Simple session means working in YOLO mode - images are saved to gallery & board.
|
||||
const sessionType = selectCanvasSessionType(state);
|
||||
const is_intermediate = sessionType === 'advanced';
|
||||
const board = sessionType === 'advanced' ? undefined : getBoardField(state);
|
||||
const type = selectCanvasSessionType(state);
|
||||
const is_intermediate = type === 'advanced';
|
||||
const board = type === 'advanced' ? undefined : getBoardField(state);
|
||||
|
||||
return {
|
||||
is_intermediate,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PayloadAction, Selector } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionGenerationStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { atom } from 'nanostores';
|
||||
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
@@ -29,7 +29,7 @@ export const stylePresetSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionStarted, () => {
|
||||
builder.addCase(canvasSessionGenerationStarted, () => {
|
||||
return deepClone(initialState);
|
||||
});
|
||||
builder.addMatcher(stylePresetsApi.endpoints.deleteStylePreset.matchFulfilled, (state, action) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ButtonGroup, Flex, Icon, IconButton, spinAnimation, Tooltip, useShiftMo
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectCanvasSession } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
|
||||
import { InvokeButtonTooltip } from 'features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip';
|
||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||
@@ -22,11 +22,11 @@ import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
|
||||
export const FloatingLeftPanelButtons = memo((props: { onToggle: () => void }) => {
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
const session = useAppSelector(selectCanvasSession);
|
||||
const type = useAppSelector(selectCanvasSessionType);
|
||||
|
||||
return (
|
||||
<Flex pos="absolute" transform="translate(0, -50%)" top="50%" insetInlineStart={2} direction="column" gap={2}>
|
||||
{tab === 'canvas' && session?.type === 'advanced' && (
|
||||
{tab === 'canvas' && type === 'advanced' && (
|
||||
<CanvasManagerProviderGate>
|
||||
<ToolChooser />
|
||||
</CanvasManagerProviderGate>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectCanvasSession } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { BoardsListPanelContent } from 'features/gallery/components/BoardsListPanelContent';
|
||||
import { Gallery } from 'features/gallery/components/Gallery';
|
||||
import { GalleryTopBar } from 'features/gallery/components/GalleryTopBar';
|
||||
@@ -28,7 +28,7 @@ export const RightPanelContent = memo(() => {
|
||||
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const session = useAppSelector(selectCanvasSession);
|
||||
const type = useAppSelector(selectCanvasSessionType);
|
||||
|
||||
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
@@ -77,7 +77,7 @@ export const RightPanelContent = memo(() => {
|
||||
<Panel order={1} id="gallery-wrapper-panel" collapsible {...galleryPanel.panelProps}>
|
||||
<Gallery />
|
||||
</Panel>
|
||||
{session?.type === 'advanced' && (
|
||||
{type === 'advanced' && (
|
||||
<>
|
||||
<HorizontalResizeHandle id="gallery-panel-to-layers-handle" {...galleryPanel.resizeHandleProps} />
|
||||
<Panel order={2} id="canvas-layers-panel" collapsible {...canvasLayersPanel.panelProps}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
import { workflowLoaded } from 'features/nodes/store/nodesSlice';
|
||||
import { atom } from 'nanostores';
|
||||
@@ -56,7 +56,7 @@ export const uiSlice = createSlice({
|
||||
builder.addCase(workflowLoaded, (state) => {
|
||||
state.activeTab = 'workflows';
|
||||
});
|
||||
builder.addCase(canvasSessionStarted, (state) => {
|
||||
builder.addCase(canvasSessionTypeChanged, (state) => {
|
||||
state.activeTab = 'canvas';
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user