diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx index 53535b42481..f97a0f35e52 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx @@ -4,9 +4,9 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { $tool, + layerReset, selectControlLayersSlice, selectedLayerDeleted, - selectedLayerReset, } from 'features/controlLayers/store/controlLayersSlice'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -22,6 +22,7 @@ export const ToolChooser: React.FC = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const isDisabled = useAppSelector(selectIsDisabled); + const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId); const tool = useStore($tool); const setToolToBrush = useCallback(() => { @@ -42,8 +43,11 @@ export const ToolChooser: React.FC = () => { useHotkeys('v', setToolToMove, { enabled: !isDisabled }, [isDisabled]); const resetSelectedLayer = useCallback(() => { - dispatch(selectedLayerReset()); - }, [dispatch]); + if (selectedLayerId === null) { + return; + } + dispatch(layerReset(selectedLayerId)); + }, [dispatch, selectedLayerId]); useHotkeys('shift+c', resetSelectedLayer); const deleteSelectedLayer = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 50023b1399b..f6a6f0b38db 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -79,17 +79,6 @@ export const isRenderableLayer = ( layer?.type === 'regional_guidance_layer' || layer?.type === 'control_adapter_layer' || layer?.type === 'initial_image_layer'; -const resetLayer = (layer: Layer) => { - if (layer.type === 'regional_guidance_layer') { - layer.maskObjects = []; - layer.bbox = null; - layer.isEnabled = true; - layer.needsPixelBbox = false; - layer.bboxNeedsUpdate = false; - layer.uploadedMaskImage = null; - return; - } -}; export const selectCALayerOrThrow = (state: ControlLayersState, layerId: string): ControlAdapterLayer => { const layer = state.layers.find((l) => l.id === layerId); @@ -164,6 +153,9 @@ export const controlLayersSlice = createSlice({ layer.x = x; layer.y = y; } + if (isRegionalGuidanceLayer(layer)) { + layer.uploadedMaskImage = null; + } }, layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => { const { layerId, bbox } = action.payload; @@ -181,8 +173,14 @@ export const controlLayersSlice = createSlice({ }, layerReset: (state, action: PayloadAction) => { const layer = state.layers.find((l) => l.id === action.payload); - if (layer) { - resetLayer(layer); + // TODO(psyche): Should other layer types also have reset functionality? + if (isRegionalGuidanceLayer(layer)) { + layer.maskObjects = []; + layer.bbox = null; + layer.isEnabled = true; + layer.needsPixelBbox = false; + layer.bboxNeedsUpdate = false; + layer.uploadedMaskImage = null; } }, layerDeleted: (state, action: PayloadAction) => { @@ -215,12 +213,6 @@ export const controlLayersSlice = createSlice({ moveToFront(renderableLayers, cb); state.layers = [...ipAdapterLayers, ...renderableLayers]; }, - selectedLayerReset: (state) => { - const layer = state.layers.find((l) => l.id === state.selectedLayerId); - if (layer) { - resetLayer(layer); - } - }, selectedLayerDeleted: (state) => { state.layers = state.layers.filter((l) => l.id !== state.selectedLayerId); state.selectedLayerId = state.layers[0]?.id ?? null; @@ -803,7 +795,6 @@ export const { layerMovedToFront, layerMovedBackward, layerMovedToBack, - selectedLayerReset, selectedLayerDeleted, allLayersDeleted, // CA Layers diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx index 37fada0b789..35abf07965f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx @@ -52,17 +52,20 @@ const CurrentImagePreview = () => { // Show and hide the next/prev buttons on mouse move const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState(false); const timeoutId = useRef(0); - const onMouseMove = useCallback(() => { + const onMouseOver = useCallback(() => { setShouldShowNextPrevButtons(true); window.clearTimeout(timeoutId.current); + }, []); + const onMouseOut = useCallback(() => { timeoutId.current = window.setTimeout(() => { setShouldShowNextPrevButtons(false); - }, 1000); + }, 500); }, []); return ( { + builder.addCase(setActiveTab, (state) => { + state.isImageViewerOpen = false; + }); builder.addMatcher(isAnyBoardDeleted, (state, action) => { const deletedBoardId = action.meta.arg.originalArgs; if (deletedBoardId === state.selectedBoardId) { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addInitialImageToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addInitialImageToLinearGraph.ts index 603708f15b6..eae45acc5be 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addInitialImageToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addInitialImageToLinearGraph.ts @@ -6,11 +6,14 @@ import { assert } from 'tsafe'; import { IMAGE_TO_LATENTS, NOISE, RESIZE } from './constants'; +/** + * Returns true if an initial image was added, false if not. + */ export const addInitialImageToLinearGraph = ( state: RootState, graph: NonNullableGraph, denoiseNodeId: string -): void => { +): boolean => { // Remove Existing UNet Connections const { img2imgStrength, vaePrecision, model } = state.generation; const { refinerModel, refinerStart } = state.sdxl; @@ -19,7 +22,7 @@ export const addInitialImageToLinearGraph = ( const initialImage = initialImageLayer?.isEnabled ? initialImageLayer?.image : null; if (!initialImage) { - return; + return false; } const isSDXL = model?.base === 'sdxl'; @@ -122,4 +125,6 @@ export const addInitialImageToLinearGraph = ( strength: img2imgStrength, init_image: initialImage.imageName, }); + + return true; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildAdHocUpscaleGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildAdHocUpscaleGraph.ts index 52c09b1db06..6c90dafd25f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildAdHocUpscaleGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildAdHocUpscaleGraph.ts @@ -1,5 +1,5 @@ import type { RootState } from 'app/store/store'; -import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; +import { getBoardField } from 'features/nodes/util/graph/graphBuilderUtils'; import type { ESRGANInvocation, Graph, NonNullableGraph } from 'services/api/types'; import { ESRGAN } from './constants'; @@ -18,7 +18,7 @@ export const buildAdHocUpscaleGraph = ({ image_name, state }: Arg): Graph => { type: 'esrgan', image: { image_name }, model_name: esrganModelName, - is_intermediate: getIsIntermediate(state), + is_intermediate: false, board: getBoardField(state), }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts index 6c04b25770d..41f9f4f7487 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts @@ -232,7 +232,7 @@ export const buildGenerationTabGraph = async (state: RootState): Promise