diff --git a/frontend/package.json b/frontend/package.json index 0327ed0f99..5b4ca2c587 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "react-icons": "^4.4.0", "react-image-pan-zoom-rotate": "^1.6.0", "react-konva": "^18.2.3", + "react-konva-utils": "^0.3.0", "react-redux": "^8.0.2", "react-transition-group": "^4.4.5", "redux-deep-persist": "^1.0.6", diff --git a/frontend/src/app/socketio/listeners.ts b/frontend/src/app/socketio/listeners.ts index fc1472ec5b..3d7e6fb86e 100644 --- a/frontend/src/app/socketio/listeners.ts +++ b/frontend/src/app/socketio/listeners.ts @@ -38,7 +38,7 @@ import { requestSystemConfig, } from './actions'; import { - addImageToOutpaintingSesion, + addImageToOutpainting, setImageToInpaint, } from 'features/canvas/canvasSlice'; import { tabMap } from 'features/tabs/InvokeTabs'; @@ -119,7 +119,7 @@ const makeSocketIOListeners = ( if (data.generationMode === 'outpainting' && data.boundingBox) { const { boundingBox } = data; dispatch( - addImageToOutpaintingSesion({ + addImageToOutpainting({ image: newImage, boundingBox, }) diff --git a/frontend/src/app/store.ts b/frontend/src/app/store.ts index 39ee82e93e..6b20d53982 100644 --- a/frontend/src/app/store.ts +++ b/frontend/src/app/store.ts @@ -94,6 +94,18 @@ export const store = configureStore({ immutableCheck: false, serializableCheck: false, }).concat(socketioMiddleware()), + devTools: { + actionsDenylist: [ + // 'canvas/setCursorPosition', + // 'canvas/setStageCoordinates', + // 'canvas/setStageScale', + // 'canvas/setIsDrawing', + // 'canvas/setBoundingBoxCoordinates', + // 'canvas/setBoundingBoxDimensions', + // 'canvas/setIsDrawing', + // 'canvas/addPointToCurrentLine', + ], + }, }); export type AppGetState = typeof store.getState; diff --git a/frontend/src/common/util/parameterTranslation.ts b/frontend/src/common/util/parameterTranslation.ts index 2d010b3815..c47b8e3c56 100644 --- a/frontend/src/common/util/parameterTranslation.ts +++ b/frontend/src/common/util/parameterTranslation.ts @@ -111,7 +111,7 @@ export const frontendToBackendParameters = ( canvasImageLayerRef.current ) { const { - objects, + layerState: { objects }, boundingBoxCoordinates, boundingBoxDimensions, inpaintReplace, diff --git a/frontend/src/features/canvas/IAICanvas.tsx b/frontend/src/features/canvas/IAICanvas.tsx index 14c9115332..a7bca897c7 100644 --- a/frontend/src/features/canvas/IAICanvas.tsx +++ b/frontend/src/features/canvas/IAICanvas.tsx @@ -9,6 +9,7 @@ import { useAppSelector } from 'app/store'; import { baseCanvasImageSelector, currentCanvasSelector, + isStagingSelector, outpaintingCanvasSelector, } from 'features/canvas/canvasSlice'; @@ -33,17 +34,16 @@ import IAICanvasObjectRenderer from './IAICanvasObjectRenderer'; import IAICanvasGrid from './IAICanvasGrid'; import IAICanvasIntermediateImage from './IAICanvasIntermediateImage'; import IAICanvasStatusText from './IAICanvasStatusText'; -import { Box, Button } from '@chakra-ui/react'; -import { rgbaColorToRgbString, rgbaColorToString } from './util/colorToString'; +import IAICanvasStagingArea from './IAICanvasStagingArea'; const canvasSelector = createSelector( [ currentCanvasSelector, outpaintingCanvasSelector, - baseCanvasImageSelector, + isStagingSelector, activeTabNameSelector, ], - (currentCanvas, outpaintingCanvas, baseCanvasImage, activeTabName) => { + (currentCanvas, outpaintingCanvas, isStaging, activeTabName) => { const { isMaskEnabled, stageScale, @@ -54,29 +54,23 @@ const canvasSelector = createSelector( stageDimensions, stageCoordinates, tool, - layer, - boundingBoxCoordinates, - boundingBoxDimensions, isMovingStage, - maskColor, } = currentCanvas; const { shouldShowGrid } = outpaintingCanvas; let stageCursor: string | undefined = ''; - if (tool === 'move') { - if (isTransformingBoundingBox) { - stageCursor = undefined; - } else if (isMouseOverBoundingBox) { - stageCursor = 'move'; - } else if (activeTabName === 'outpainting') { - if (isMovingStage) { - stageCursor = 'grabbing'; - } else { - stageCursor = 'grab'; - } + if (tool === 'move' || isStaging) { + if (isMovingStage) { + stageCursor = 'grabbing'; + } else { + stageCursor = 'grab'; } + } else if (isTransformingBoundingBox) { + stageCursor = undefined; + } else if (isMouseOverBoundingBox) { + stageCursor = 'move'; } else { stageCursor = 'none'; } @@ -91,11 +85,8 @@ const canvasSelector = createSelector( stageDimensions, stageScale, tool, - layer, - boundingBoxCoordinates, - boundingBoxDimensions, - maskColorString: rgbaColorToString({ ...maskColor, a: 0.5 }), - outpaintingOnly: activeTabName === 'outpainting', + isOnOutpaintingTab: activeTabName === 'outpainting', + isStaging, }; }, { @@ -120,11 +111,8 @@ const IAICanvas = () => { stageDimensions, stageScale, tool, - layer, - outpaintingOnly, - boundingBoxCoordinates, - boundingBoxDimensions, - maskColorString, + isOnOutpaintingTab, + isStaging, } = useAppSelector(canvasSelector); useCanvasHotkeys(); @@ -151,25 +139,15 @@ const IAICanvas = () => { const { handleDragStart, handleDragMove, handleDragEnd } = useCanvasDragMove(); - const panelTop = boundingBoxCoordinates.y + boundingBoxDimensions.height; - const panelLeft = boundingBoxCoordinates.x + boundingBoxDimensions.width; - return (
{ onDragMove={handleDragMove} onDragEnd={handleDragEnd} onWheel={handleWheel} - listening={tool === 'move' && !isModifyingBoundingBox} + listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox} draggable={ - tool === 'move' && !isModifyingBoundingBox && outpaintingOnly + (tool === 'move' || isStaging) && + !isModifyingBoundingBox && + isOnOutpaintingTab } > @@ -209,14 +189,21 @@ const IAICanvas = () => { - - + {!isStaging && ( + <> + + + + )} + + + {isStaging && } - {outpaintingOnly && } + {isOnOutpaintingTab && }
); diff --git a/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx b/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx index 860390460a..bbffd98c49 100644 --- a/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx +++ b/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx @@ -1,13 +1,12 @@ import { createSelector } from '@reduxjs/toolkit'; import { currentCanvasSelector, - outpaintingCanvasSelector, + isStagingSelector, setBrushColor, setBrushSize, setTool, } from './canvasSlice'; import { useAppDispatch, useAppSelector } from 'app/store'; -import { activeTabNameSelector } from 'features/options/optionsSelectors'; import _ from 'lodash'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaPaintBrush } from 'react-icons/fa'; @@ -17,35 +16,15 @@ import IAISlider from 'common/components/IAISlider'; import { Flex } from '@chakra-ui/react'; export const selector = createSelector( - [currentCanvasSelector, outpaintingCanvasSelector, activeTabNameSelector], - (currentCanvas, outpaintingCanvas, activeTabName) => { - const { - layer, - maskColor, - brushColor, - brushSize, - eraserSize, - tool, - shouldDarkenOutsideBoundingBox, - shouldShowIntermediates, - } = currentCanvas; - - const { shouldShowGrid, shouldSnapToGrid, shouldAutoSave } = - outpaintingCanvas; + [currentCanvasSelector, isStagingSelector], + (currentCanvas, isStaging) => { + const { brushColor, brushSize, tool } = currentCanvas; return { - layer, tool, - maskColor, brushColor, brushSize, - eraserSize, - activeTabName, - shouldShowGrid, - shouldSnapToGrid, - shouldAutoSave, - shouldDarkenOutsideBoundingBox, - shouldShowIntermediates, + isStaging, }; }, { @@ -57,7 +36,7 @@ export const selector = createSelector( const IAICanvasBrushButtonPopover = () => { const dispatch = useAppDispatch(); - const { tool, brushColor, brushSize } = useAppSelector(selector); + const { tool, brushColor, brushSize, isStaging } = useAppSelector(selector); return ( { aria-label="Brush (B)" tooltip="Brush (B)" icon={} - data-selected={tool === 'brush'} + data-selected={tool === 'brush' && !isStaging} onClick={() => dispatch(setTool('brush'))} + isDisabled={isStaging} /> } > diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx index 6c0b7c0410..325455b74c 100644 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx +++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx @@ -18,9 +18,10 @@ import { useToast } from '@chakra-ui/react'; const canvasMaskClearSelector = createSelector( [currentCanvasSelector, activeTabNameSelector], (currentCanvas, activeTabName) => { - const { isMaskEnabled, objects } = currentCanvas as - | InpaintingCanvasState - | OutpaintingCanvasState; + const { + isMaskEnabled, + layerState: { objects }, + } = currentCanvas as InpaintingCanvasState | OutpaintingCanvasState; return { isMaskEnabled, diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx index 212e9fd87f..67ba63301a 100644 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx +++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx @@ -11,10 +11,10 @@ import _ from 'lodash'; const canvasRedoSelector = createSelector( [currentCanvasSelector, activeTabNameSelector], (currentCanvas, activeTabName) => { - const { futureObjects } = currentCanvas; + const { futureLayerStates } = currentCanvas; return { - canRedo: futureObjects.length > 0, + canRedo: futureLayerStates.length > 0, activeTabName, }; }, @@ -34,7 +34,7 @@ export default function IAICanvasRedoButton() { }; useHotkeys( - ['meta+shift+z', 'control+shift+z', 'control+y', 'meta+y'], + ['meta+shift+z', 'ctrl+shift+z', 'control+y', 'meta+y'], () => { handleRedo(); }, diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx index 64a60edd02..0582d32637 100644 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx +++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx @@ -11,10 +11,10 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors'; const canvasUndoSelector = createSelector( [currentCanvasSelector, activeTabNameSelector], (canvas, activeTabName) => { - const { pastObjects } = canvas; + const { pastLayerStates } = canvas; return { - canUndo: pastObjects.length > 0, + canUndo: pastLayerStates.length > 0, activeTabName, }; }, @@ -35,7 +35,7 @@ export default function IAICanvasUndoButton() { }; useHotkeys( - ['meta+z', 'control+z'], + ['meta+z', 'ctrl+z'], () => { handleUndo(); }, diff --git a/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx b/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx index ae5ee21125..b36c757b82 100644 --- a/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx +++ b/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx @@ -1,5 +1,10 @@ import { createSelector } from '@reduxjs/toolkit'; -import { currentCanvasSelector, setEraserSize, setTool } from './canvasSlice'; +import { + currentCanvasSelector, + isStagingSelector, + setEraserSize, + setTool, +} from './canvasSlice'; import { useAppDispatch, useAppSelector } from 'app/store'; import _ from 'lodash'; import IAIIconButton from 'common/components/IAIIconButton'; @@ -10,13 +15,14 @@ import { Flex } from '@chakra-ui/react'; import { useHotkeys } from 'react-hotkeys-hook'; export const selector = createSelector( - [currentCanvasSelector], - (currentCanvas) => { + [currentCanvasSelector, isStagingSelector], + (currentCanvas, isStaging) => { const { eraserSize, tool } = currentCanvas; return { tool, eraserSize, + isStaging, }; }, { @@ -27,7 +33,7 @@ export const selector = createSelector( ); const IAICanvasEraserButtonPopover = () => { const dispatch = useAppDispatch(); - const { tool, eraserSize } = useAppSelector(selector); + const { tool, eraserSize, isStaging } = useAppSelector(selector); const handleSelectEraserTool = () => dispatch(setTool('eraser')); @@ -51,7 +57,8 @@ const IAICanvasEraserButtonPopover = () => { aria-label="Eraser (E)" tooltip="Eraser (E)" icon={} - data-selected={tool === 'eraser'} + data-selected={tool === 'eraser' && !isStaging} + isDisabled={isStaging} onClick={() => dispatch(setTool('eraser'))} /> } diff --git a/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx b/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx index 93c91ec619..798b6e85b1 100644 --- a/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx +++ b/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx @@ -148,7 +148,17 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => { return () => clearInterval(timer); }, []); - if (!fillPatternImage) return null; + if ( + !( + fillPatternImage && + stageCoordinates.x && + stageCoordinates.y && + stageScale && + stageDimensions.width && + stageDimensions.height + ) + ) + return null; return ( { - const { objects } = currentCanvas as - | InpaintingCanvasState - | OutpaintingCanvasState; - return { - objects, - }; + (currentCanvas) => { + return currentCanvas.layerState.objects; }, { memoizeOptions: { @@ -37,7 +32,7 @@ type InpaintingCanvasLinesProps = GroupConfig; */ const IAICanvasLines = (props: InpaintingCanvasLinesProps) => { const { ...rest } = props; - const { objects } = useAppSelector(canvasLinesSelector); + const objects = useAppSelector(canvasLinesSelector); return ( diff --git a/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx b/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx index a776ff69ee..9187b1f5ec 100644 --- a/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx +++ b/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx @@ -1,4 +1,4 @@ -import { createSelector } from '@reduxjs/toolkit'; +import { createSelector, current } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store'; import _ from 'lodash'; import { Group, Line } from 'react-konva'; @@ -13,7 +13,10 @@ import { rgbaColorToString } from './util/colorToString'; const selector = createSelector( [currentCanvasSelector], (currentCanvas) => { - return currentCanvas.objects; + const { objects } = currentCanvas.layerState; + return { + objects, + }; }, { memoizeOptions: { @@ -23,7 +26,7 @@ const selector = createSelector( ); const IAICanvasObjectRenderer = () => { - const objects = useAppSelector(selector); + const { objects } = useAppSelector(selector); if (!objects) return null; diff --git a/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx b/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx index c3504238d4..99bde3a7a0 100644 --- a/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx +++ b/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx @@ -2,6 +2,7 @@ import { ButtonGroup } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { currentCanvasSelector, + isStagingSelector, resetCanvas, setTool, uploadOutpaintingMergedImage, @@ -27,12 +28,13 @@ import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover'; import IAICanvasMaskButtonPopover from './IAICanvasMaskButtonPopover'; export const canvasControlsSelector = createSelector( - [currentCanvasSelector], - (currentCanvas) => { + [currentCanvasSelector, isStagingSelector], + (currentCanvas, isStaging) => { const { tool } = currentCanvas; return { tool, + isStaging, }; }, { @@ -44,7 +46,7 @@ export const canvasControlsSelector = createSelector( const IAICanvasOutpaintingControls = () => { const dispatch = useAppDispatch(); - const { tool } = useAppSelector(canvasControlsSelector); + const { tool, isStaging } = useAppSelector(canvasControlsSelector); return (
@@ -56,7 +58,7 @@ const IAICanvasOutpaintingControls = () => { aria-label="Move (M)" tooltip="Move (M)" icon={} - data-selected={tool === 'move'} + data-selected={tool === 'move' || isStaging} onClick={() => dispatch(setTool('move'))} /> diff --git a/frontend/src/features/canvas/IAICanvasStagingArea.tsx b/frontend/src/features/canvas/IAICanvasStagingArea.tsx new file mode 100644 index 0000000000..fc80a26b07 --- /dev/null +++ b/frontend/src/features/canvas/IAICanvasStagingArea.tsx @@ -0,0 +1,164 @@ +import { background, ButtonGroup, ChakraProvider } from '@chakra-ui/react'; +import { CacheProvider } from '@emotion/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store'; +import IAIButton from 'common/components/IAIButton'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { GroupConfig } from 'konva/lib/Group'; +import _ from 'lodash'; +import { emotionCache } from 'main'; +import { useState } from 'react'; +import { + FaArrowLeft, + FaArrowRight, + FaCheck, + FaEye, + FaEyeSlash, + FaTrash, +} from 'react-icons/fa'; +import { Group, Rect } from 'react-konva'; +import { Html } from 'react-konva-utils'; +import { + commitStagingAreaImage, + currentCanvasSelector, + discardStagedImages, + nextStagingAreaImage, + prevStagingAreaImage, +} from './canvasSlice'; +import IAICanvasImage from './IAICanvasImage'; + +const selector = createSelector( + [currentCanvasSelector], + (currentCanvas) => { + const { + layerState: { + stagingArea: { images, selectedImageIndex }, + }, + } = currentCanvas; + + return { + currentStagingAreaImage: + images.length > 0 ? images[selectedImageIndex] : undefined, + isOnFirstImage: selectedImageIndex === 0, + isOnLastImage: selectedImageIndex === images.length - 1, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: _.isEqual, + }, + } +); + +type Props = GroupConfig; + +const IAICanvasStagingArea = (props: Props) => { + const { ...rest } = props; + const dispatch = useAppDispatch(); + const { isOnFirstImage, isOnLastImage, currentStagingAreaImage } = + useAppSelector(selector); + + const [shouldShowStagedImage, setShouldShowStagedImage] = + useState(true); + + if (!currentStagingAreaImage) return null; + + const { + x, + y, + image: { width, height, url }, + } = currentStagingAreaImage; + + return ( + + + {shouldShowStagedImage && } + + + + + + + +
+ + } + onClick={() => dispatch(prevStagingAreaImage())} + data-selected={true} + isDisabled={isOnFirstImage} + /> + } + onClick={() => dispatch(nextStagingAreaImage())} + data-selected={true} + isDisabled={isOnLastImage} + /> + } + onClick={() => dispatch(commitStagingAreaImage())} + data-selected={true} + /> + : } + onClick={() => + setShouldShowStagedImage(!shouldShowStagedImage) + } + data-selected={true} + /> + } + onClick={() => dispatch(discardStagedImages())} + data-selected={true} + /> + +
+
+
+ +
+ ); +}; + +export default IAICanvasStagingArea; diff --git a/frontend/src/features/canvas/canvasSlice.ts b/frontend/src/features/canvas/canvasSlice.ts index 529cb605b3..be8f0e2f56 100644 --- a/frontend/src/features/canvas/canvasSlice.ts +++ b/frontend/src/features/canvas/canvasSlice.ts @@ -47,6 +47,9 @@ export interface GenericCanvasState { intermediateImage?: InvokeAI.Image; shouldShowIntermediates: boolean; maxHistory: number; + layerState: CanvasLayerState; + pastLayerStates: CanvasLayerState[]; + futureLayerStates: CanvasLayerState[]; } export type CanvasLayer = 'base' | 'mask'; @@ -84,7 +87,19 @@ export type CanvasLine = CanvasAnyLine & { color?: RgbaColor; }; -type CanvasObject = CanvasImage | CanvasLine | CanvasMaskLine; +export type CanvasObject = CanvasImage | CanvasLine | CanvasMaskLine; + +export type CanvasLayerState = { + objects: CanvasObject[]; + stagingArea: { + x: number; + y: number; + width: number; + height: number; + images: CanvasImage[]; + selectedImageIndex: number; + }; +}; // type guards export const isCanvasMaskLine = (obj: CanvasObject): obj is CanvasMaskLine => @@ -102,24 +117,13 @@ export const isCanvasAnyLine = ( export type OutpaintingCanvasState = GenericCanvasState & { layer: CanvasLayer; - objects: CanvasObject[]; - pastObjects: CanvasObject[][]; - futureObjects: CanvasObject[][]; shouldShowGrid: boolean; shouldSnapToGrid: boolean; shouldAutoSave: boolean; - stagingArea: { - images: CanvasImage[]; - selectedImageIndex: number; - }; }; export type InpaintingCanvasState = GenericCanvasState & { layer: 'mask'; - objects: CanvasObject[]; - pastObjects: CanvasObject[][]; - futureObjects: CanvasObject[][]; - imageToInpaint?: InvokeAI.Image; }; export type BaseCanvasState = InpaintingCanvasState | OutpaintingCanvasState; @@ -133,6 +137,18 @@ export interface CanvasState { outpainting: OutpaintingCanvasState; } +const initialLayerState: CanvasLayerState = { + objects: [], + stagingArea: { + x: -1, + y: -1, + width: -1, + height: -1, + images: [], + selectedImageIndex: -1, + }, +}; + const initialGenericCanvasState: GenericCanvasState = { tool: 'brush', brushColor: { r: 90, g: 90, b: 255, a: 1 }, @@ -164,7 +180,10 @@ const initialGenericCanvasState: GenericCanvasState = { isMoveStageKeyHeld: false, shouldShowIntermediates: true, isMovingStage: false, - maxHistory: 256, + maxHistory: 128, + layerState: initialLayerState, + futureLayerStates: [], + pastLayerStates: [], }; const initialCanvasState: CanvasState = { @@ -172,20 +191,10 @@ const initialCanvasState: CanvasState = { doesCanvasNeedScaling: false, inpainting: { layer: 'mask', - objects: [], - pastObjects: [], - futureObjects: [], ...initialGenericCanvasState, }, outpainting: { layer: 'base', - objects: [], - pastObjects: [], - futureObjects: [], - stagingArea: { - images: [], - selectedImageIndex: 0, - }, shouldShowGrid: true, shouldSnapToGrid: true, shouldAutoSave: false, @@ -230,14 +239,13 @@ export const canvasSlice = createSlice({ state[state.currentCanvas].eraserSize = action.payload; }, clearMask: (state) => { - state[state.currentCanvas].pastObjects.push( - state[state.currentCanvas].objects - ); - state[state.currentCanvas].objects = state[ + const currentCanvas = state[state.currentCanvas]; + currentCanvas.pastLayerStates.push(currentCanvas.layerState); + currentCanvas.layerState.objects = state[ state.currentCanvas - ].objects.filter((obj) => !isCanvasMaskLine(obj)); - state[state.currentCanvas].futureObjects = []; - state[state.currentCanvas].shouldPreserveMaskedArea = false; + ].layerState.objects.filter((obj) => !isCanvasMaskLine(obj)); + currentCanvas.futureLayerStates = []; + currentCanvas.shouldPreserveMaskedArea = false; }, toggleShouldInvertMask: (state) => { state[state.currentCanvas].shouldPreserveMaskedArea = @@ -271,9 +279,9 @@ export const canvasSlice = createSlice({ state[state.currentCanvas].cursorPosition = action.payload; }, clearImageToInpaint: (state) => { - state.inpainting.imageToInpaint = undefined; + // TODO + // state.inpainting.imageToInpaint = undefined; }, - setImageToOutpaint: (state, action: PayloadAction) => { const { width: canvasWidth, height: canvasHeight } = state.outpainting.stageDimensions; @@ -307,16 +315,20 @@ export const canvasSlice = createSlice({ state.outpainting.boundingBoxDimensions = newDimensions; state.outpainting.boundingBoxCoordinates = newCoordinates; - // state.outpainting.imageToInpaint = action.payload; - state.outpainting.objects = [ - { - kind: 'image', - layer: 'base', - x: 0, - y: 0, - image: action.payload, - }, - ]; + state.outpainting.pastLayerStates.push(state.outpainting.layerState); + state.outpainting.layerState = { + ...initialLayerState, + objects: [ + { + kind: 'image', + layer: 'base', + x: 0, + y: 0, + image: action.payload, + }, + ], + }; + state.outpainting.futureLayerStates = []; state.doesCanvasNeedScaling = true; }, setImageToInpaint: (state, action: PayloadAction) => { @@ -352,16 +364,22 @@ export const canvasSlice = createSlice({ state.inpainting.boundingBoxDimensions = newDimensions; state.inpainting.boundingBoxCoordinates = newCoordinates; - // state.inpainting.imageToInpaint = action.payload; - state.inpainting.objects = [ - { - kind: 'image', - layer: 'base', - x: 0, - y: 0, - image: action.payload, - }, - ]; + state.inpainting.pastLayerStates.push(state.inpainting.layerState); + + state.inpainting.layerState = { + ...initialLayerState, + objects: [ + { + kind: 'image', + layer: 'base', + x: 0, + y: 0, + image: action.payload, + }, + ], + }; + + state.outpainting.futureLayerStates = []; state.doesCanvasNeedScaling = true; }, setStageDimensions: (state, action: PayloadAction) => { @@ -487,8 +505,8 @@ export const canvasSlice = createSlice({ state[state.currentCanvas].isDrawing = action.payload; }, setClearBrushHistory: (state) => { - state[state.currentCanvas].pastObjects = []; - state[state.currentCanvas].futureObjects = []; + state[state.currentCanvas].pastLayerStates = []; + state[state.currentCanvas].futureLayerStates = []; }, setShouldUseInpaintReplace: (state, action: PayloadAction) => { state[state.currentCanvas].shouldUseInpaintReplace = action.payload; @@ -524,7 +542,7 @@ export const canvasSlice = createSlice({ setCurrentCanvas: (state, action: PayloadAction) => { state.currentCanvas = action.payload; }, - addImageToOutpaintingSesion: ( + addImageToOutpainting: ( state, action: PayloadAction<{ boundingBox: IRect; @@ -536,23 +554,151 @@ export const canvasSlice = createSlice({ if (!boundingBox || !image) return; const { x, y } = boundingBox; + const { width, height } = image; + const currentCanvas = state.outpainting; - currentCanvas.pastObjects.push([...currentCanvas.objects]); + // const { + // x: stagingX, + // y: stagingY, + // width: stagingWidth, + // height: stagingHeight, + // images: stagedImages, + // } = currentCanvas.layerState.stagingArea; - if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) { - currentCanvas.pastObjects.shift(); + currentCanvas.pastLayerStates.push(_.cloneDeep(currentCanvas.layerState)); + + if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + currentCanvas.pastLayerStates.shift(); } - currentCanvas.futureObjects = []; - - currentCanvas.objects.push({ + currentCanvas.layerState.stagingArea.images.push({ kind: 'image', layer: 'base', x, y, image, }); + + currentCanvas.layerState.stagingArea.selectedImageIndex = + currentCanvas.layerState.stagingArea.images.length - 1; + + currentCanvas.futureLayerStates = []; + + // // If the new image is in the staging area region, push it to staging area + // if ( + // x === stagingX && + // y === stagingY && + // width === stagingWidth && + // height === stagingHeight + // ) { + // console.log('pushing new image to staging area images'); + // currentCanvas.pastLayerStates.push( + // _.cloneDeep(currentCanvas.layerState) + // ); + + // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + // currentCanvas.pastLayerStates.shift(); + // } + + // currentCanvas.layerState.stagingArea.images.push({ + // kind: 'image', + // layer: 'base', + // x, + // y, + // image, + // }); + + // currentCanvas.layerState.stagingArea.selectedImageIndex = + // currentCanvas.layerState.stagingArea.images.length - 1; + + // currentCanvas.futureLayerStates = []; + // } + // // Else, if the staging area is empty, set it to this image + // else if (stagedImages.length === 0) { + // console.log('setting staging area image to be this one image'); + // // add new image to staging area + // currentCanvas.pastLayerStates.push( + // _.cloneDeep(currentCanvas.layerState) + // ); + + // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + // currentCanvas.pastLayerStates.shift(); + // } + + // currentCanvas.layerState.stagingArea = { + // images: [ + // { + // kind: 'image', + // layer: 'base', + // x, + // y, + // image, + // }, + // ], + // x, + // y, + // width: image.width, + // height: image.height, + // selectedImageIndex: 0, + // }; + + // currentCanvas.futureLayerStates = []; + // } else { + // // commit the current staging area image & set the new image as the only staging area image + // currentCanvas.pastLayerStates.push( + // _.cloneDeep(currentCanvas.layerState) + // ); + + // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + // currentCanvas.pastLayerStates.shift(); + // } + + // if (stagedImages.length === 1) { + // // commit the current staging area image + // console.log('committing current image'); + + // const { + // x: currentStagedX, + // y: currentStagedY, + // image: currentStagedImage, + // } = stagedImages[0]; + + // currentCanvas.layerState.objects.push({ + // kind: 'image', + // layer: 'base', + // x: currentStagedX, + // y: currentStagedY, + // image: currentStagedImage, + // }); + // } + + // console.log('setting staging area to this singel new image'); + // currentCanvas.layerState.stagingArea = { + // images: [ + // { + // kind: 'image', + // layer: 'base', + // x, + // y, + // image, + // }, + // ], + // x, + // y, + // width: image.width, + // height: image.height, + // selectedImageIndex: 0, + // }; + + // currentCanvas.futureLayerStates = []; + // } + }, + discardStagedImages: (state) => { + const currentCanvas = state[state.currentCanvas]; + currentCanvas.layerState.stagingArea = { + ...initialLayerState.stagingArea, + }; }, addLine: (state, action: PayloadAction) => { const currentCanvas = state[state.currentCanvas]; @@ -567,13 +713,13 @@ export const canvasSlice = createSlice({ const newColor = layer === 'base' && tool === 'brush' ? { color: brushColor } : {}; - currentCanvas.pastObjects.push(currentCanvas.objects); + currentCanvas.pastLayerStates.push(currentCanvas.layerState); - if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) { - currentCanvas.pastObjects.shift(); + if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + currentCanvas.pastLayerStates.shift(); } - currentCanvas.objects.push({ + currentCanvas.layerState.objects.push({ kind: 'line', layer, tool, @@ -582,11 +728,11 @@ export const canvasSlice = createSlice({ ...newColor, }); - currentCanvas.futureObjects = []; + currentCanvas.futureLayerStates = []; }, addPointToCurrentLine: (state, action: PayloadAction) => { const lastLine = - state[state.currentCanvas].objects.findLast(isCanvasAnyLine); + state[state.currentCanvas].layerState.objects.findLast(isCanvasAnyLine); if (!lastLine) return; @@ -594,36 +740,33 @@ export const canvasSlice = createSlice({ }, undo: (state) => { const currentCanvas = state[state.currentCanvas]; - if (currentCanvas.objects.length === 0) return; - const newObjects = currentCanvas.pastObjects.pop(); + const targetState = currentCanvas.pastLayerStates.pop(); - if (!newObjects) return; + if (!targetState) return; - currentCanvas.futureObjects.unshift(currentCanvas.objects); + currentCanvas.futureLayerStates.unshift(currentCanvas.layerState); - if (currentCanvas.futureObjects.length > currentCanvas.maxHistory) { - currentCanvas.futureObjects.pop(); + if (currentCanvas.futureLayerStates.length > currentCanvas.maxHistory) { + currentCanvas.futureLayerStates.pop(); } - currentCanvas.objects = newObjects; + currentCanvas.layerState = targetState; }, redo: (state) => { const currentCanvas = state[state.currentCanvas]; - if (currentCanvas.futureObjects.length === 0) return; + const targetState = currentCanvas.futureLayerStates.shift(); - const newObjects = currentCanvas.futureObjects.shift(); + if (!targetState) return; - if (!newObjects) return; + currentCanvas.pastLayerStates.push(currentCanvas.layerState); - currentCanvas.pastObjects.push(currentCanvas.objects); - - if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) { - currentCanvas.pastObjects.shift(); + if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + currentCanvas.pastLayerStates.shift(); } - currentCanvas.objects = newObjects; + currentCanvas.layerState = targetState; }, setShouldShowGrid: (state, action: PayloadAction) => { state.outpainting.shouldShowGrid = action.payload; @@ -641,21 +784,69 @@ export const canvasSlice = createSlice({ state[state.currentCanvas].shouldShowIntermediates = action.payload; }, resetCanvas: (state) => { - state[state.currentCanvas].pastObjects.push( - state[state.currentCanvas].objects + state[state.currentCanvas].pastLayerStates.push( + state[state.currentCanvas].layerState ); - state[state.currentCanvas].objects = []; - state[state.currentCanvas].futureObjects = []; + state[state.currentCanvas].layerState = initialLayerState; + state[state.currentCanvas].futureLayerStates = []; + }, + nextStagingAreaImage: (state) => { + const currentIndex = + state.outpainting.layerState.stagingArea.selectedImageIndex; + const length = state.outpainting.layerState.stagingArea.images.length; + + state.outpainting.layerState.stagingArea.selectedImageIndex = Math.min( + currentIndex + 1, + length - 1 + ); + }, + prevStagingAreaImage: (state) => { + const currentIndex = + state.outpainting.layerState.stagingArea.selectedImageIndex; + + state.outpainting.layerState.stagingArea.selectedImageIndex = Math.max( + currentIndex - 1, + 0 + ); + }, + commitStagingAreaImage: (state) => { + const currentCanvas = state[state.currentCanvas]; + const { images, selectedImageIndex } = + currentCanvas.layerState.stagingArea; + + currentCanvas.pastLayerStates.push(_.cloneDeep(currentCanvas.layerState)); + + if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) { + currentCanvas.pastLayerStates.shift(); + } + + const { x, y, image } = images[selectedImageIndex]; + + currentCanvas.layerState.objects.push({ + kind: 'image', + layer: 'base', + x, + y, + image, + }); + + currentCanvas.layerState.stagingArea = { + ...initialLayerState.stagingArea, + }; + + currentCanvas.futureLayerStates = []; }, }, extraReducers: (builder) => { builder.addCase(uploadOutpaintingMergedImage.fulfilled, (state, action) => { if (!action.payload) return; - state.outpainting.pastObjects.push([...state.outpainting.objects]); - state.outpainting.futureObjects = []; + state.outpainting.pastLayerStates.push({ + ...state.outpainting.layerState, + }); + state.outpainting.futureLayerStates = []; - state.outpainting.objects = [ + state.outpainting.layerState.objects = [ { kind: 'image', layer: 'base', @@ -709,13 +900,17 @@ export const { setIsMoveStageKeyHeld, setStageCoordinates, setCurrentCanvas, - addImageToOutpaintingSesion, + addImageToOutpainting, resetCanvas, setShouldShowGrid, setShouldSnapToGrid, setShouldAutoSave, setShouldShowIntermediates, setIsMovingStage, + nextStagingAreaImage, + prevStagingAreaImage, + commitStagingAreaImage, + discardStagedImages, } = canvasSlice.actions; export default canvasSlice.reducer; @@ -783,6 +978,10 @@ export const uploadOutpaintingMergedImage = createAsyncThunk( export const currentCanvasSelector = (state: RootState): BaseCanvasState => state.canvas[state.canvas.currentCanvas]; +export const isStagingSelector = (state: RootState): boolean => + state.canvas[state.canvas.currentCanvas].layerState.stagingArea.images + .length > 0; + export const outpaintingCanvasSelector = ( state: RootState ): OutpaintingCanvasState => state.canvas.outpainting; @@ -794,6 +993,6 @@ export const inpaintingCanvasSelector = ( export const baseCanvasImageSelector = createSelector( [currentCanvasSelector], (currentCanvas) => { - return currentCanvas.objects.find(isCanvasBaseImage); + return currentCanvas.layerState.objects.find(isCanvasBaseImage); } ); diff --git a/frontend/src/features/canvas/hooks/useCanvasDragMove.ts b/frontend/src/features/canvas/hooks/useCanvasDragMove.ts index dedc9f3dc9..268bdf6e75 100644 --- a/frontend/src/features/canvas/hooks/useCanvasDragMove.ts +++ b/frontend/src/features/canvas/hooks/useCanvasDragMove.ts @@ -6,17 +6,18 @@ import _ from 'lodash'; import { useCallback } from 'react'; import { currentCanvasSelector, + isStagingSelector, setIsMovingStage, setStageCoordinates, } from '../canvasSlice'; const selector = createSelector( - [currentCanvasSelector, activeTabNameSelector], - (canvas, activeTabName) => { + [currentCanvasSelector, isStagingSelector, activeTabNameSelector], + (canvas, isStaging, activeTabName) => { const { tool } = canvas; return { tool, - + isStaging, activeTabName, }; }, @@ -25,24 +26,26 @@ const selector = createSelector( const useCanvasDrag = () => { const dispatch = useAppDispatch(); - const { tool, activeTabName } = useAppSelector(selector); + const { tool, activeTabName, isStaging } = useAppSelector(selector); return { handleDragStart: useCallback(() => { - if (tool !== 'move' || activeTabName !== 'outpainting') return; + if (!(tool === 'move' || isStaging)) return; dispatch(setIsMovingStage(true)); - }, [activeTabName, dispatch, tool]), + }, [dispatch, isStaging, tool]), + handleDragMove: useCallback( (e: KonvaEventObject) => { - if (tool !== 'move' || activeTabName !== 'outpainting') return; + if (!(tool === 'move' || isStaging)) return; dispatch(setStageCoordinates(e.target.getPosition())); }, - [activeTabName, dispatch, tool] + [dispatch, isStaging, tool] ), + handleDragEnd: useCallback(() => { - if (tool !== 'move' || activeTabName !== 'outpainting') return; + if (!(tool === 'move' || isStaging)) return; dispatch(setIsMovingStage(false)); - }, [activeTabName, dispatch, tool]), + }, [dispatch, isStaging, tool]), }; }; diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts b/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts index 503daeaf77..ae72b1686f 100644 --- a/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts +++ b/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts @@ -8,18 +8,20 @@ import { MutableRefObject, useCallback } from 'react'; import { addLine, currentCanvasSelector, + isStagingSelector, setIsDrawing, setIsMovingStage, } from '../canvasSlice'; import getScaledCursorPosition from '../util/getScaledCursorPosition'; const selector = createSelector( - [activeTabNameSelector, currentCanvasSelector], - (activeTabName, currentCanvas) => { + [activeTabNameSelector, currentCanvasSelector, isStagingSelector], + (activeTabName, currentCanvas, isStaging) => { const { tool } = currentCanvas; return { tool, activeTabName, + isStaging, }; }, { memoizeOptions: { resultEqualityCheck: _.isEqual } } @@ -27,14 +29,15 @@ const selector = createSelector( const useCanvasMouseDown = (stageRef: MutableRefObject) => { const dispatch = useAppDispatch(); - const { tool } = useAppSelector(selector); + const { tool, isStaging } = useAppSelector(selector); return useCallback( (e: KonvaEventObject) => { if (!stageRef.current) return; + stageRef.current.container().focus(); - if (tool === 'move') { + if (tool === 'move' || isStaging) { dispatch(setIsMovingStage(true)); return; } @@ -50,7 +53,7 @@ const useCanvasMouseDown = (stageRef: MutableRefObject) => { // Add a new line starting from the current cursor position. dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y])); }, - [stageRef, dispatch, tool] + [stageRef, tool, isStaging, dispatch] ); }; diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts b/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts index 73231f911e..997faa058b 100644 --- a/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts +++ b/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts @@ -5,16 +5,22 @@ import Konva from 'konva'; import { KonvaEventObject } from 'konva/lib/Node'; import _ from 'lodash'; import { MutableRefObject, useCallback } from 'react'; -import { addLine, currentCanvasSelector, setIsDrawing } from '../canvasSlice'; +import { + addLine, + currentCanvasSelector, + isStagingSelector, + setIsDrawing, +} from '../canvasSlice'; import getScaledCursorPosition from '../util/getScaledCursorPosition'; const selector = createSelector( - [activeTabNameSelector, currentCanvasSelector], - (activeTabName, currentCanvas) => { + [activeTabNameSelector, currentCanvasSelector, isStagingSelector], + (activeTabName, currentCanvas, isStaging) => { const { tool } = currentCanvas; return { tool, activeTabName, + isStaging, }; }, { memoizeOptions: { resultEqualityCheck: _.isEqual } } @@ -24,7 +30,7 @@ const useCanvasMouseEnter = ( stageRef: MutableRefObject ) => { const dispatch = useAppDispatch(); - const { tool } = useAppSelector(selector); + const { tool, isStaging } = useAppSelector(selector); return useCallback( (e: KonvaEventObject) => { @@ -34,14 +40,14 @@ const useCanvasMouseEnter = ( const scaledCursorPosition = getScaledCursorPosition(stageRef.current); - if (!scaledCursorPosition || tool === 'move') return; + if (!scaledCursorPosition || tool === 'move' || isStaging) return; dispatch(setIsDrawing(true)); // Add a new line starting from the current cursor position. dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y])); }, - [stageRef, tool, dispatch] + [stageRef, tool, isStaging, dispatch] ); }; diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts b/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts index aa4cbd9557..8519e8e9ab 100644 --- a/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts +++ b/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts @@ -9,18 +9,20 @@ import { addPointToCurrentLine, currentCanvasSelector, GenericCanvasState, + isStagingSelector, setCursorPosition, } from '../canvasSlice'; import getScaledCursorPosition from '../util/getScaledCursorPosition'; const selector = createSelector( - [activeTabNameSelector, currentCanvasSelector], - (activeTabName, canvas: GenericCanvasState) => { - const { tool, isDrawing } = canvas; + [activeTabNameSelector, currentCanvasSelector, isStagingSelector], + (activeTabName, currentCanvas, isStaging) => { + const { tool, isDrawing } = currentCanvas; return { tool, isDrawing, activeTabName, + isStaging, }; }, { memoizeOptions: { resultEqualityCheck: _.isEqual } } @@ -32,7 +34,7 @@ const useCanvasMouseMove = ( lastCursorPositionRef: MutableRefObject ) => { const dispatch = useAppDispatch(); - const { isDrawing, tool } = useAppSelector(selector); + const { isDrawing, tool, isStaging } = useAppSelector(selector); return useCallback(() => { if (!stageRef.current) return; @@ -45,7 +47,7 @@ const useCanvasMouseMove = ( lastCursorPositionRef.current = scaledCursorPosition; - if (!isDrawing || tool === 'move') return; + if (!isDrawing || tool === 'move' || isStaging) return; didMouseMoveRef.current = true; dispatch( @@ -55,6 +57,7 @@ const useCanvasMouseMove = ( didMouseMoveRef, dispatch, isDrawing, + isStaging, lastCursorPositionRef, stageRef, tool, diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts b/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts index 685d43b3e2..64c6d68da1 100644 --- a/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts +++ b/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts @@ -9,19 +9,21 @@ import { addPointToCurrentLine, currentCanvasSelector, GenericCanvasState, + isStagingSelector, setIsDrawing, setIsMovingStage, } from '../canvasSlice'; import getScaledCursorPosition from '../util/getScaledCursorPosition'; const selector = createSelector( - [activeTabNameSelector, currentCanvasSelector], - (activeTabName, canvas: GenericCanvasState) => { - const { tool, isDrawing } = canvas; + [activeTabNameSelector, currentCanvasSelector, isStagingSelector], + (activeTabName, currentCanvas, isStaging) => { + const { tool, isDrawing } = currentCanvas; return { tool, isDrawing, activeTabName, + isStaging, }; }, { memoizeOptions: { resultEqualityCheck: _.isEqual } } @@ -32,10 +34,10 @@ const useCanvasMouseUp = ( didMouseMoveRef: MutableRefObject ) => { const dispatch = useAppDispatch(); - const { tool, isDrawing } = useAppSelector(selector); + const { tool, isDrawing, isStaging } = useAppSelector(selector); return useCallback(() => { - if (tool === 'move') { + if (tool === 'move' || isStaging) { dispatch(setIsMovingStage(false)); return; } @@ -58,7 +60,7 @@ const useCanvasMouseUp = ( didMouseMoveRef.current = false; } dispatch(setIsDrawing(false)); - }, [didMouseMoveRef, dispatch, isDrawing, stageRef, tool]); + }, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef, tool]); }; export default useCanvasMouseUp; diff --git a/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx b/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx index 4f8307b367..993349990e 100644 --- a/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx +++ b/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx @@ -13,12 +13,12 @@ import _ from 'lodash'; const clearBrushHistorySelector = createSelector( currentCanvasSelector, (currentCanvas) => { - const { pastObjects, futureObjects } = currentCanvas as + const { pastLayerStates, futureLayerStates } = currentCanvas as | InpaintingCanvasState | OutpaintingCanvasState; return { mayClearBrushHistory: - futureObjects.length > 0 || pastObjects.length > 0 ? false : true, + futureLayerStates.length > 0 || pastLayerStates.length > 0 ? false : true, }; }, { diff --git a/frontend/src/features/tabs/CanvasWorkarea.scss b/frontend/src/features/tabs/CanvasWorkarea.scss index d3921b7a5f..f83d8f8441 100644 --- a/frontend/src/features/tabs/CanvasWorkarea.scss +++ b/frontend/src/features/tabs/CanvasWorkarea.scss @@ -69,10 +69,13 @@ } .inpainting-canvas-stage { - // border-radius: 0.5rem; - // border: 1px solid var(--border-color-light); + outline: none; + border-radius: 0.5rem; + border: 1px solid var(--border-color-light); + overflow: hidden; canvas { + outline: none; border-radius: 0.5rem; } } diff --git a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx b/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx index e5fd54f008..47ff66111c 100644 --- a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx +++ b/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx @@ -17,7 +17,9 @@ const outpaintingDisplaySelector = createSelector( (canvas: CanvasState) => { const { doesCanvasNeedScaling, - outpainting: { objects }, + outpainting: { + layerState: { objects }, + }, } = canvas; return { doesCanvasNeedScaling, diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 18ab6a62e2..02350c73bd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -13,7 +13,7 @@ export const persistor = persistStore(store); import Loading from './Loading'; import App from './app/App'; -const emotionCache = createCache({ +export const emotionCache = createCache({ key: 'invokeai-style-cache', prepend: true, }); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index bb5bac14de..8477d67ffb 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3420,7 +3420,15 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-konva@^18.2.3: +react-konva-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-konva-utils/-/react-konva-utils-0.3.0.tgz#d9099ad1a767286b24fb1b08377af2dfa5c9f176" + integrity sha512-yH5FVpDGQ8gHeClyHY533M4oSLjEfYuvn+Z29zXm9osjhuulhtJrh5k+wtyY6QSC0MG0ioqE0cjiudGl1WGB9A== + dependencies: + react-konva "^18.0.0-0" + use-image "^1.0.12" + +react-konva@^18.0.0-0, react-konva@^18.2.3: version "18.2.3" resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-18.2.3.tgz#75c658fca493bdf515b38f2a8d544fa7a9c754c4" integrity sha512-OPxjBTgaEGU9pt/VJSVM7QNXYHEZ5CkulX+4fTTvbaH+Wh+vMLbXmH3yjWw4kT/5Qi6t0UQKHPPmirCv8/9sdg== @@ -3942,7 +3950,7 @@ use-callback-ref@^1.3.0: dependencies: tslib "^2.0.0" -use-image@^1.1.0: +use-image@^1.0.12, use-image@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/use-image/-/use-image-1.1.0.tgz#dc244c34506d3cf3a8177c1f0bbfb158b9beefe5" integrity sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==