mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds full-app image drag and drop, also image paste
This commit is contained in:
parent
2e9463089d
commit
101fe9efa9
517
frontend/dist/assets/index.01c74d27.js
vendored
517
frontend/dist/assets/index.01c74d27.js
vendored
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.18143ecd.js
vendored
Normal file
517
frontend/dist/assets/index.18143ecd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.61df3eff.css
vendored
1
frontend/dist/assets/index.61df3eff.css
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.ff30f6a0.css
vendored
Normal file
1
frontend/dist/assets/index.ff30f6a0.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||
<script type="module" crossorigin src="./assets/index.01c74d27.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.61df3eff.css">
|
||||
<script type="module" crossorigin src="./assets/index.18143ecd.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.ff30f6a0.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -17,5 +17,5 @@
|
||||
}
|
||||
|
||||
.app-console {
|
||||
z-index: 9999;
|
||||
z-index: 20;
|
||||
}
|
||||
|
@ -7,11 +7,13 @@ import { useAppDispatch } from './store';
|
||||
import { requestSystemConfig } from './socketio/actions';
|
||||
import { keepGUIAlive } from './utils';
|
||||
import InvokeTabs from '../features/tabs/InvokeTabs';
|
||||
import ImageUploader from '../common/components/ImageUploader';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
const App = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isReady, setIsReady] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -21,14 +23,16 @@ const App = () => {
|
||||
|
||||
return isReady ? (
|
||||
<div className="App">
|
||||
<ProgressBar />
|
||||
<div className="app-content">
|
||||
<SiteHeader />
|
||||
<InvokeTabs />
|
||||
</div>
|
||||
<div className="app-console">
|
||||
<Console />
|
||||
</div>
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<div className="app-content">
|
||||
<SiteHeader />
|
||||
<InvokeTabs />
|
||||
</div>
|
||||
<div className="app-console">
|
||||
<Console />
|
||||
</div>
|
||||
</ImageUploader>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
|
8
frontend/src/app/contexts/ImageUploaderTriggerContext.ts
Normal file
8
frontend/src/app/contexts/ImageUploaderTriggerContext.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
type VoidFunc = () => void;
|
||||
|
||||
type ImageUploaderTriggerContextType = VoidFunc | null;
|
||||
|
||||
export const ImageUploaderTriggerContext =
|
||||
createContext<ImageUploaderTriggerContextType>(null);
|
4
frontend/src/app/invokeai.d.ts
vendored
4
frontend/src/app/invokeai.d.ts
vendored
@ -197,7 +197,9 @@ export declare type ImageUrlResponse = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export declare type ImageUploadDestination = 'img2img' | 'inpainting';
|
||||
|
||||
export declare type UploadImagePayload = {
|
||||
file: File;
|
||||
destination: 'img2img' | 'inpainting';
|
||||
destination?: ImageUploadDestination;
|
||||
};
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
clearIntermediateImage,
|
||||
GalleryState,
|
||||
removeImage,
|
||||
setCurrentImage,
|
||||
setIntermediateImage,
|
||||
} from '../../features/gallery/gallerySlice';
|
||||
|
||||
@ -305,6 +306,10 @@ const makeSocketIOListeners = (
|
||||
dispatch(setImageToInpaint(image));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dispatch(setCurrentImage(image));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
|
72
frontend/src/common/components/ImageUploader.scss
Normal file
72
frontend/src/common/components/ImageUploader.scss
Normal file
@ -0,0 +1,72 @@
|
||||
.dropzone-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.dropzone-overlay {
|
||||
opacity: 0.5;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.is-drag-accept {
|
||||
box-shadow: inset 0 0 20rem 1rem var(--status-good-color);
|
||||
}
|
||||
|
||||
&.is-drag-reject {
|
||||
box-shadow: inset 0 0 20rem 1rem var(--status-bad-color);
|
||||
}
|
||||
|
||||
&.is-handling-upload {
|
||||
box-shadow: inset 0 0 20rem 1rem var(--status-working-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-uploader-button-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--subtext-color-bright);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--inpaint-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.image-upload-button-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-upload-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
width: 4rem !important;
|
||||
height: 4rem !important;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.2rem !important;
|
||||
}
|
||||
}
|
173
frontend/src/common/components/ImageUploader.tsx
Normal file
173
frontend/src/common/components/ImageUploader.tsx
Normal file
@ -0,0 +1,173 @@
|
||||
import { useCallback, ReactNode, useState, useEffect } from 'react';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||
import { tabMap } from '../../features/tabs/InvokeTabs';
|
||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { Heading, Spinner, useToast } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
import { uploadImage } from '../../app/socketio/actions';
|
||||
import { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai';
|
||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
||||
|
||||
const appSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
(options: OptionsState) => {
|
||||
const { activeTab } = options;
|
||||
return {
|
||||
activeTabName: tabMap[activeTab],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
type ImageUploaderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ImageUploader = (props: ImageUploaderProps) => {
|
||||
const { children } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { activeTabName } = useAppSelector(appSelector);
|
||||
const toast = useToast({});
|
||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||
|
||||
const fileRejectionCallback = useCallback(
|
||||
(rejection: FileRejection) => {
|
||||
setIsHandlingUpload(true);
|
||||
const msg = rejection.errors.reduce(
|
||||
(acc: string, cur: { message: string }) => acc + '\n' + cur.message,
|
||||
''
|
||||
);
|
||||
toast({
|
||||
title: 'Upload failed',
|
||||
description: msg,
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
const fileAcceptedCallback = useCallback(
|
||||
(file: File) => {
|
||||
setIsHandlingUpload(true);
|
||||
const payload: UploadImagePayload = { file };
|
||||
if (['img2img', 'inpainting'].includes(activeTabName)) {
|
||||
payload.destination = activeTabName as ImageUploadDestination;
|
||||
}
|
||||
dispatch(uploadImage(payload));
|
||||
},
|
||||
[dispatch, activeTabName]
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||
fileRejections.forEach((rejection: FileRejection) => {
|
||||
fileRejectionCallback(rejection);
|
||||
});
|
||||
|
||||
acceptedFiles.forEach((file: File) => {
|
||||
fileAcceptedCallback(file);
|
||||
});
|
||||
},
|
||||
[fileAcceptedCallback, fileRejectionCallback]
|
||||
);
|
||||
|
||||
const {
|
||||
getRootProps,
|
||||
getInputProps,
|
||||
isDragAccept,
|
||||
isDragReject,
|
||||
isDragActive,
|
||||
open,
|
||||
} = useDropzone({
|
||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||
noClick: true,
|
||||
onDrop,
|
||||
maxFiles: 1,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const pasteImageListener = (e: ClipboardEvent) => {
|
||||
const dataTransferItemList = e.clipboardData?.items;
|
||||
if (!dataTransferItemList) return;
|
||||
|
||||
const imageItems: Array<DataTransferItem> = [];
|
||||
|
||||
for (const item of dataTransferItemList) {
|
||||
if (
|
||||
item.kind === 'file' &&
|
||||
['image/png', 'image/jpg'].includes(item.type)
|
||||
) {
|
||||
imageItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageItems.length) return;
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (imageItems.length > 1) {
|
||||
toast({
|
||||
description:
|
||||
'Multiple images pasted, may only upload one image at a time',
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const file = imageItems[0].getAsFile();
|
||||
|
||||
if (!file) {
|
||||
toast({
|
||||
description: 'Unable to load file',
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: UploadImagePayload = { file };
|
||||
if (['img2img', 'inpainting'].includes(activeTabName)) {
|
||||
payload.destination = activeTabName as ImageUploadDestination;
|
||||
}
|
||||
|
||||
dispatch(uploadImage(payload));
|
||||
};
|
||||
document.addEventListener('paste', pasteImageListener);
|
||||
return () => {
|
||||
document.removeEventListener('paste', pasteImageListener);
|
||||
};
|
||||
}, [dispatch, toast, activeTabName]);
|
||||
|
||||
return (
|
||||
<ImageUploaderTriggerContext.Provider value={open}>
|
||||
<div {...getRootProps({ style: {} })}>
|
||||
<input {...getInputProps()} />
|
||||
{children}
|
||||
{isDragActive && (
|
||||
<div className="dropzone-container">
|
||||
{isDragAccept && (
|
||||
<div className="dropzone-overlay is-drag-accept">
|
||||
<Heading size={'lg'}>Drop Images</Heading>
|
||||
</div>
|
||||
)}
|
||||
{isDragReject && (
|
||||
<div className="dropzone-overlay is-drag-reject">
|
||||
<Heading size={'lg'}>Invalid Upload</Heading>
|
||||
<Heading size={'md'}>Must be single JPEG or PNG image</Heading>
|
||||
</div>
|
||||
)}
|
||||
{isHandlingUpload && (
|
||||
<div className="dropzone-overlay is-handling-upload">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ImageUploaderTriggerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploader;
|
31
frontend/src/common/components/ImageUploaderButton.tsx
Normal file
31
frontend/src/common/components/ImageUploaderButton.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Heading } from '@chakra-ui/react';
|
||||
import { useContext } from 'react';
|
||||
import { FaUpload } from 'react-icons/fa';
|
||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
||||
|
||||
type ImageUploaderButtonProps = {
|
||||
styleClass?: string;
|
||||
};
|
||||
|
||||
const ImageUploaderButton = (props: ImageUploaderButtonProps) => {
|
||||
const { styleClass } = props;
|
||||
const open = useContext(ImageUploaderTriggerContext);
|
||||
|
||||
const handleClickUpload = () => {
|
||||
open && open();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`image-uploader-button-outer ${styleClass}`}
|
||||
onClick={handleClickUpload}
|
||||
>
|
||||
<div className="image-upload-button">
|
||||
<FaUpload />
|
||||
<Heading size={'lg'}>Click or Drag and Drop</Heading>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploaderButton;
|
@ -1,39 +0,0 @@
|
||||
.image-upload-zone {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--subtext-color-bright);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--inpaint-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.image-upload-child-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-upload-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
width: 4rem !important;
|
||||
height: 4rem !important;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.2rem !important;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import { Heading, useToast } from '@chakra-ui/react';
|
||||
import { useCallback } from 'react';
|
||||
import { FileRejection } from 'react-dropzone';
|
||||
import { FaUpload } from 'react-icons/fa';
|
||||
import ImageUploader from '../../features/options/ImageUploader';
|
||||
|
||||
interface InvokeImageUploaderProps {
|
||||
handleFile: (file: File) => void;
|
||||
styleClass?: string;
|
||||
}
|
||||
|
||||
export default function InvokeImageUploader(props: InvokeImageUploaderProps) {
|
||||
const { handleFile, styleClass } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// Callbacks to for handling file upload attempts
|
||||
const fileAcceptedCallback = useCallback(
|
||||
(file: File) => handleFile(file),
|
||||
[handleFile]
|
||||
);
|
||||
|
||||
const fileRejectionCallback = useCallback(
|
||||
(rejection: FileRejection) => {
|
||||
const msg = rejection.errors.reduce(
|
||||
(acc: string, cur: { message: string }) => acc + '\n' + cur.message,
|
||||
''
|
||||
);
|
||||
|
||||
toast({
|
||||
title: 'Upload failed',
|
||||
description: msg,
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="image-upload-zone">
|
||||
<ImageUploader
|
||||
fileAcceptedCallback={fileAcceptedCallback}
|
||||
fileRejectionCallback={fileRejectionCallback}
|
||||
styleClass={
|
||||
styleClass
|
||||
? `${styleClass} image-upload-child-wrapper`
|
||||
: `image-upload-child-wrapper`
|
||||
}
|
||||
>
|
||||
<div className="image-upload-child">
|
||||
<FaUpload />
|
||||
<Heading size={'lg'}>Upload or Drop Image Here</Heading>
|
||||
</div>
|
||||
</ImageUploader>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
.hoverable-image-context-menu {
|
||||
z-index: 999;
|
||||
z-index: 15;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--context-menu-bg-color);
|
||||
|
@ -1,73 +0,0 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import {
|
||||
cloneElement,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
SyntheticEvent,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||
|
||||
type ImageUploaderProps = {
|
||||
/**
|
||||
* Component which, on click, should open the upload interface.
|
||||
*/
|
||||
children: ReactElement;
|
||||
/**
|
||||
* Callback to handle uploading the selected file.
|
||||
*/
|
||||
fileAcceptedCallback: (file: File) => void;
|
||||
/**
|
||||
* Callback to handle a file being rejected.
|
||||
*/
|
||||
fileRejectionCallback: (rejection: FileRejection) => void;
|
||||
// Styling
|
||||
styleClass?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* File upload using react-dropzone.
|
||||
* Needs a child to be the button to activate the upload interface.
|
||||
*/
|
||||
const ImageUploader = ({
|
||||
children,
|
||||
fileAcceptedCallback,
|
||||
fileRejectionCallback,
|
||||
styleClass,
|
||||
}: ImageUploaderProps) => {
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||
fileRejections.forEach((rejection: FileRejection) => {
|
||||
fileRejectionCallback(rejection);
|
||||
});
|
||||
|
||||
acceptedFiles.forEach((file: File) => {
|
||||
fileAcceptedCallback(file);
|
||||
});
|
||||
},
|
||||
[fileAcceptedCallback, fileRejectionCallback]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps, open } = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||
},
|
||||
});
|
||||
|
||||
const handleClickUploadIcon = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
open();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box {...getRootProps()} className={styleClass}>
|
||||
<input {...getInputProps({ multiple: false })} />
|
||||
{cloneElement(children, {
|
||||
onClick: handleClickUploadIcon,
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploader;
|
@ -1,12 +1,9 @@
|
||||
import { uploadImage } from '../../../app/socketio/actions';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
||||
import { RootState, useAppSelector } from '../../../app/store';
|
||||
import ImageUploadButton from '../../../common/components/ImageUploaderButton';
|
||||
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
||||
import InitImagePreview from './InitImagePreview';
|
||||
|
||||
const ImageToImageDisplay = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const initialImage = useAppSelector(
|
||||
(state: RootState) => state.options.initialImage
|
||||
);
|
||||
@ -18,11 +15,7 @@ const ImageToImageDisplay = () => {
|
||||
<InitImagePreview />
|
||||
</div>
|
||||
) : (
|
||||
<InvokeImageUploader
|
||||
handleFile={(file: File) =>
|
||||
dispatch(uploadImage({ file, destination: 'img2img' }))
|
||||
}
|
||||
/>
|
||||
<ImageUploadButton />
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -16,6 +16,7 @@ import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import {
|
||||
addLine,
|
||||
addPointToCurrentLine,
|
||||
clearImageToInpaint,
|
||||
setCursorPosition,
|
||||
setIsDrawing,
|
||||
} from './inpaintingSlice';
|
||||
@ -33,6 +34,7 @@ import InpaintingBoundingBoxPreview, {
|
||||
} from './components/InpaintingBoundingBoxPreview';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import KeyboardEventManager from './components/KeyboardEventManager';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
|
||||
// Use a closure allow other components to use these things... not ideal...
|
||||
export let stageRef: MutableRefObject<StageType | null>;
|
||||
@ -57,6 +59,8 @@ const InpaintingCanvas = () => {
|
||||
shouldShowBoundingBox,
|
||||
} = useAppSelector(inpaintingCanvasSelector);
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// set the closure'd refs
|
||||
stageRef = useRef<StageType>(null);
|
||||
maskLayerRef = useRef<Konva.Layer>(null);
|
||||
@ -80,9 +84,18 @@ const InpaintingCanvas = () => {
|
||||
inpaintingImageElementRef.current = image;
|
||||
setCanvasBgImage(image);
|
||||
};
|
||||
image.onerror = () => {
|
||||
toast({
|
||||
title: 'Unable to Load Image',
|
||||
description: `Image ${imageToInpaint.url} failed to load`,
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
dispatch(clearImageToInpaint());
|
||||
};
|
||||
image.src = imageToInpaint.url;
|
||||
}
|
||||
}, [imageToInpaint, dispatch, stageScale]);
|
||||
}, [imageToInpaint, dispatch, stageScale, toast]);
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { uploadImage } from '../../../app/socketio/actions';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
||||
import ImageUploadButton from '../../../common/components/ImageUploaderButton';
|
||||
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
||||
import { OptionsState } from '../../options/optionsSlice';
|
||||
import InpaintingCanvas from './InpaintingCanvas';
|
||||
@ -36,10 +35,7 @@ const InpaintingDisplay = () => {
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const resizeCallback = _.debounce(
|
||||
() => dispatch(setNeedsCache(true)),
|
||||
250
|
||||
);
|
||||
const resizeCallback = _.debounce(() => dispatch(setNeedsCache(true)), 250);
|
||||
window.addEventListener('resize', resizeCallback);
|
||||
return () => window.removeEventListener('resize', resizeCallback);
|
||||
}, [dispatch]);
|
||||
@ -52,11 +48,7 @@ const InpaintingDisplay = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<InvokeImageUploader
|
||||
handleFile={(file: File) =>
|
||||
dispatch(uploadImage({ file, destination: 'inpainting' }))
|
||||
}
|
||||
/>
|
||||
<ImageUploadButton />
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -51,7 +51,7 @@
|
||||
@use '../common/components/IAICheckbox.scss';
|
||||
@use '../common/components/IAIPopover.scss';
|
||||
@use '../common/components/IAIColorPicker.scss';
|
||||
@use '../common/components/InvokeImageUploader.scss';
|
||||
@use '../common/components/ImageUploader.scss';
|
||||
@use '../common/components/WorkInProgress/WorkInProgress.scss';
|
||||
@use '../common/components/GuidePopover.scss';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user