mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): refactor base image uploading logic
This commit is contained in:
parent
5e4457445f
commit
e1e5266fc3
@ -552,8 +552,8 @@
|
|||||||
"canceled": "Processing Canceled",
|
"canceled": "Processing Canceled",
|
||||||
"tempFoldersEmptied": "Temp Folder Emptied",
|
"tempFoldersEmptied": "Temp Folder Emptied",
|
||||||
"uploadFailed": "Upload failed",
|
"uploadFailed": "Upload failed",
|
||||||
"uploadFailedMultipleImagesDesc": "Multiple images pasted, may only upload one image at a time",
|
|
||||||
"uploadFailedUnableToLoadDesc": "Unable to load file",
|
"uploadFailedUnableToLoadDesc": "Unable to load file",
|
||||||
|
"uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image",
|
||||||
"downloadImageStarted": "Image Download Started",
|
"downloadImageStarted": "Image Download Started",
|
||||||
"imageCopied": "Image Copied",
|
"imageCopied": "Image Copied",
|
||||||
"imageLinkCopied": "Image Link Copied",
|
"imageLinkCopied": "Image Link Copied",
|
||||||
|
@ -22,6 +22,7 @@ import { languageSelector } from 'features/system/store/systemSelectors';
|
|||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
|
import AuxiliaryProgressIndicator from './AuxiliaryProgressIndicator';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -99,6 +100,8 @@ const App = ({
|
|||||||
<GalleryDrawer />
|
<GalleryDrawer />
|
||||||
<ParametersDrawer />
|
<ParametersDrawer />
|
||||||
|
|
||||||
|
{/* <AuxiliaryProgressIndicator /> */}
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!isApplicationReady && !loadingOverridden && (
|
{!isApplicationReady && !loadingOverridden && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { Flex, Spinner, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const selector = createSelector(systemSelector, (system) => {
|
||||||
|
const { isUploading } = system;
|
||||||
|
|
||||||
|
let tooltip = '';
|
||||||
|
|
||||||
|
if (isUploading) {
|
||||||
|
tooltip = 'Uploading...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tooltip,
|
||||||
|
shouldShow: isUploading,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AuxiliaryProgressIndicator = () => {
|
||||||
|
const { shouldShow, tooltip } = useAppSelector(selector);
|
||||||
|
|
||||||
|
if (!shouldShow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'base.600',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip label={tooltip} placement="right" hasArrow>
|
||||||
|
<Spinner />
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AuxiliaryProgressIndicator);
|
@ -3,6 +3,7 @@ import { startAppListening } from '..';
|
|||||||
import { uploadAdded } from 'features/gallery/store/uploadsSlice';
|
import { uploadAdded } from 'features/gallery/store/uploadsSlice';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { imageUploaded } from 'services/thunks/image';
|
import { imageUploaded } from 'services/thunks/image';
|
||||||
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
|
||||||
export const addImageUploadedListener = () => {
|
export const addImageUploadedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -17,6 +18,8 @@ export const addImageUploadedListener = () => {
|
|||||||
|
|
||||||
dispatch(uploadAdded(image));
|
dispatch(uploadAdded(image));
|
||||||
|
|
||||||
|
dispatch(addToast({ title: 'Image Uploaded', status: 'success' }));
|
||||||
|
|
||||||
if (state.gallery.shouldAutoSwitchToNewImages) {
|
if (state.gallery.shouldAutoSwitchToNewImages) {
|
||||||
dispatch(imageSelected(image));
|
dispatch(imageSelected(image));
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputProps,
|
InputProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
import { ChangeEvent, memo } from 'react';
|
import { ChangeEvent, memo } from 'react';
|
||||||
|
|
||||||
interface IAIInputProps extends InputProps {
|
interface IAIInputProps extends InputProps {
|
||||||
@ -31,7 +32,7 @@ const IAIInput = (props: IAIInputProps) => {
|
|||||||
{...formControlProps}
|
{...formControlProps}
|
||||||
>
|
>
|
||||||
{label !== '' && <FormLabel>{label}</FormLabel>}
|
{label !== '' && <FormLabel>{label}</FormLabel>}
|
||||||
<Input {...rest} />
|
<Input {...rest} onPaste={stopPastePropagation} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
|
|
||||||
import { FocusEvent, memo, useEffect, useState } from 'react';
|
import { FocusEvent, memo, useEffect, useState } from 'react';
|
||||||
@ -125,6 +126,7 @@ const IAINumberInput = (props: Props) => {
|
|||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
onPaste={stopPastePropagation}
|
||||||
>
|
>
|
||||||
<NumberInputField {...numberInputFieldProps} />
|
<NumberInputField {...numberInputFieldProps} />
|
||||||
{showStepper && (
|
{showStepper && (
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import { Textarea, TextareaProps, forwardRef } from '@chakra-ui/react';
|
||||||
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const IAITextarea = forwardRef((props: TextareaProps, ref) => {
|
||||||
|
return <Textarea ref={ref} onPaste={stopPastePropagation} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default memo(IAITextarea);
|
@ -6,10 +6,12 @@ import { FaUndo, FaUpload } from 'react-icons/fa';
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
|
|
||||||
const InitialImageButtons = () => {
|
const InitialImageButtons = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { openUploader } = useImageUploader();
|
||||||
|
|
||||||
const handleResetInitialImage = useCallback(() => {
|
const handleResetInitialImage = useCallback(() => {
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
@ -27,7 +29,11 @@ const InitialImageButtons = () => {
|
|||||||
aria-label={t('accessibility.reset')}
|
aria-label={t('accessibility.reset')}
|
||||||
onClick={handleResetInitialImage}
|
onClick={handleResetInitialImage}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton icon={<FaUpload />} aria-label={t('common.upload')} />
|
<IAIIconButton
|
||||||
|
icon={<FaUpload />}
|
||||||
|
onClick={openUploader}
|
||||||
|
aria-label={t('common.upload')}
|
||||||
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,8 @@ import {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||||
@ -17,6 +19,24 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { imageUploaded } from 'services/thunks/image';
|
import { imageUploaded } from 'services/thunks/image';
|
||||||
import ImageUploadOverlay from './ImageUploadOverlay';
|
import ImageUploadOverlay from './ImageUploadOverlay';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
import { filter, map, some } from 'lodash-es';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import { ErrorCode } from 'react-dropzone';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[systemSelector, activeTabNameSelector],
|
||||||
|
(system, activeTabName) => {
|
||||||
|
const { isConnected, isUploading } = system;
|
||||||
|
|
||||||
|
const isUploaderDisabled = !isConnected || isUploading;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isUploaderDisabled,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
type ImageUploaderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -25,24 +45,20 @@ type ImageUploaderProps = {
|
|||||||
const ImageUploader = (props: ImageUploaderProps) => {
|
const ImageUploader = (props: ImageUploaderProps) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const { isUploaderDisabled, activeTabName } = useAppSelector(selector);
|
||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
const { setOpenUploader } = useImageUploader();
|
const { setOpenUploaderFunction } = useImageUploader();
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
(rejection: FileRejection) => {
|
(rejection: FileRejection) => {
|
||||||
setIsHandlingUpload(true);
|
setIsHandlingUpload(true);
|
||||||
const msg = rejection.errors.reduce(
|
|
||||||
(acc: string, cur: { message: string }) => `${acc}\n${cur.message}`,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
toaster({
|
toaster({
|
||||||
title: t('toast.uploadFailed'),
|
title: t('toast.uploadFailed'),
|
||||||
description: msg,
|
description: rejection.errors.map((error) => error.message).join('\n'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
isClosable: true,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, toaster]
|
[t, toaster]
|
||||||
@ -57,6 +73,15 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||||
|
if (fileRejections.length > 1) {
|
||||||
|
toaster({
|
||||||
|
title: t('toast.uploadFailed'),
|
||||||
|
description: t('toast.uploadFailedInvalidUploadDesc'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fileRejections.forEach((rejection: FileRejection) => {
|
fileRejections.forEach((rejection: FileRejection) => {
|
||||||
fileRejectionCallback(rejection);
|
fileRejectionCallback(rejection);
|
||||||
});
|
});
|
||||||
@ -65,7 +90,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
fileAcceptedCallback(file);
|
fileAcceptedCallback(file);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[fileAcceptedCallback, fileRejectionCallback]
|
[t, toaster, fileAcceptedCallback, fileRejectionCallback]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -74,73 +99,49 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
isDragAccept,
|
isDragAccept,
|
||||||
isDragReject,
|
isDragReject,
|
||||||
isDragActive,
|
isDragActive,
|
||||||
|
inputRef,
|
||||||
open,
|
open,
|
||||||
} = useDropzone({
|
} = useDropzone({
|
||||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||||
noClick: true,
|
noClick: true,
|
||||||
onDrop,
|
onDrop,
|
||||||
onDragOver: () => setIsHandlingUpload(true),
|
onDragOver: () => setIsHandlingUpload(true),
|
||||||
maxFiles: 1,
|
disabled: isUploaderDisabled,
|
||||||
|
multiple: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
setOpenUploader(open);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pasteImageListener = (e: ClipboardEvent) => {
|
const handlePaste = async (e: ClipboardEvent) => {
|
||||||
const dataTransferItemList = e.clipboardData?.items;
|
if (!inputRef.current) {
|
||||||
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) {
|
|
||||||
toaster({
|
|
||||||
description: t('toast.uploadFailedMultipleImagesDesc'),
|
|
||||||
status: 'error',
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = imageItems[0].getAsFile();
|
if (e.clipboardData?.files) {
|
||||||
|
inputRef.current.files = e.clipboardData.files;
|
||||||
if (!file) {
|
inputRef.current?.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
toaster({
|
|
||||||
description: t('toast.uploadFailedUnableToLoadDesc'),
|
|
||||||
status: 'error',
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(imageUploaded({ imageType: 'uploads', formData: { file } }));
|
|
||||||
};
|
};
|
||||||
document.addEventListener('paste', pasteImageListener);
|
|
||||||
|
setOpenUploaderFunction(open);
|
||||||
|
document.addEventListener('paste', handlePaste);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('paste', pasteImageListener);
|
document.removeEventListener('paste', handlePaste);
|
||||||
|
setOpenUploaderFunction(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [t, dispatch, toaster, activeTabName]);
|
}, [inputRef, open, setOpenUploaderFunction]);
|
||||||
|
|
||||||
const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(
|
const overlaySecondaryText = useMemo(() => {
|
||||||
activeTabName
|
if (['img2img', 'unifiedCanvas'].includes(activeTabName)) {
|
||||||
)
|
return ` to ${String(t(`common.${activeTabName}` as ResourceKey))}`;
|
||||||
? ` to ${String(t(`common.${activeTabName}` as ResourceKey))}`
|
}
|
||||||
: ``;
|
|
||||||
|
return '';
|
||||||
|
}, [t, activeTabName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageUploaderTriggerContext.Provider value={open}>
|
|
||||||
<Box
|
<Box
|
||||||
{...getRootProps({ style: {} })}
|
{...getRootProps({ style: {} })}
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
onKeyDown={(e: KeyboardEvent) => {
|
||||||
@ -159,7 +160,6 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</ImageUploaderTriggerContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Flex, Heading, Icon } from '@chakra-ui/react';
|
import { Flex, Heading, Icon } from '@chakra-ui/react';
|
||||||
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
|
||||||
type ImageUploaderButtonProps = {
|
type ImageUploaderButtonProps = {
|
||||||
@ -9,11 +8,7 @@ type ImageUploaderButtonProps = {
|
|||||||
|
|
||||||
const ImageUploaderButton = (props: ImageUploaderButtonProps) => {
|
const ImageUploaderButton = (props: ImageUploaderButtonProps) => {
|
||||||
const { styleClass } = props;
|
const { styleClass } = props;
|
||||||
const open = useContext(ImageUploaderTriggerContext);
|
const { openUploader } = useImageUploader();
|
||||||
|
|
||||||
const handleClickUpload = () => {
|
|
||||||
open && open();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -26,7 +21,7 @@ const ImageUploaderButton = (props: ImageUploaderButtonProps) => {
|
|||||||
className={styleClass}
|
className={styleClass}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
onClick={handleClickUpload}
|
onClick={openUploader}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
import IAIIconButton from './IAIIconButton';
|
import IAIIconButton from './IAIIconButton';
|
||||||
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
|
|
||||||
const ImageUploaderIconButton = () => {
|
const ImageUploaderIconButton = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const openImageUploader = useContext(ImageUploaderTriggerContext);
|
const { openUploader } = useImageUploader();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('accessibility.uploadImage')}
|
aria-label={t('accessibility.uploadImage')}
|
||||||
tooltip="Upload Image"
|
tooltip="Upload Image"
|
||||||
icon={<FaUpload />}
|
icon={<FaUpload />}
|
||||||
onClick={openImageUploader || undefined}
|
onClick={openUploader}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@ const Loading = () => {
|
|||||||
height="24px !important"
|
height="24px !important"
|
||||||
right="1.5rem"
|
right="1.5rem"
|
||||||
bottom="1.5rem"
|
bottom="1.5rem"
|
||||||
speed="1.2s"
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
let openFunction: () => void;
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
let openUploader = () => {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const useImageUploader = () => {
|
const useImageUploader = () => {
|
||||||
return {
|
const setOpenUploaderFunction = useCallback(
|
||||||
setOpenUploader: (open?: () => void) => {
|
(openUploaderFunction?: () => void) => {
|
||||||
if (open) {
|
if (openUploaderFunction) {
|
||||||
openFunction = open;
|
openUploader = openUploaderFunction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openUploader: openFunction,
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
setOpenUploaderFunction,
|
||||||
|
openUploader,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { ClipboardEvent } from 'react';
|
||||||
|
|
||||||
|
export const stopPastePropagation = (e: ClipboardEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
@ -81,7 +81,7 @@ const IAICanvasResizer = () => {
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Spinner thickness="2px" speed="1s" size="xl" />
|
<Spinner thickness="2px" size="xl" />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,10 @@ const GalleryProgressImage = () => {
|
|||||||
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
|
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Spinner sx={{ position: 'absolute', top: 1, right: 1, opacity: 0.7 }} />
|
<Spinner
|
||||||
|
sx={{ position: 'absolute', top: 1, right: 1, opacity: 0.7 }}
|
||||||
|
speed="1.2s"
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FormControl, Textarea } from '@chakra-ui/react';
|
import { FormControl } from '@chakra-ui/react';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IAITextarea from 'common/components/IAITextarea';
|
||||||
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ const ParamNegativeConditioning = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<IAITextarea
|
||||||
id="negativePrompt"
|
id="negativePrompt"
|
||||||
name="negativePrompt"
|
name="negativePrompt"
|
||||||
value={negativePrompt}
|
value={negativePrompt}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, FormControl, Textarea } from '@chakra-ui/react';
|
import { Box, FormControl } from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
||||||
@ -16,6 +16,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
|
import IAITextarea from 'common/components/IAITextarea';
|
||||||
|
|
||||||
const promptInputSelector = createSelector(
|
const promptInputSelector = createSelector(
|
||||||
[(state: RootState) => state.generation, activeTabNameSelector],
|
[(state: RootState) => state.generation, activeTabNameSelector],
|
||||||
@ -72,7 +73,7 @@ const ParamPositiveConditioning = () => {
|
|||||||
<FormControl
|
<FormControl
|
||||||
isInvalid={prompt.length === 0 || Boolean(prompt.match(/^[\s\r\n]+$/))}
|
isInvalid={prompt.length === 0 || Boolean(prompt.match(/^[\s\r\n]+$/))}
|
||||||
>
|
>
|
||||||
<Textarea
|
<IAITextarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
name="prompt"
|
name="prompt"
|
||||||
placeholder={t('parameters.promptPlaceholder')}
|
placeholder={t('parameters.promptPlaceholder')}
|
||||||
|
@ -3,24 +3,6 @@ import { SystemState } from './systemSlice';
|
|||||||
/**
|
/**
|
||||||
* System slice persist denylist
|
* System slice persist denylist
|
||||||
*/
|
*/
|
||||||
const itemsToDenylist: (keyof SystemState)[] = [
|
|
||||||
'currentIteration',
|
|
||||||
'currentStatus',
|
|
||||||
'currentStep',
|
|
||||||
'isCancelable',
|
|
||||||
'isConnected',
|
|
||||||
'isESRGANAvailable',
|
|
||||||
'isGFPGANAvailable',
|
|
||||||
'isProcessing',
|
|
||||||
'socketId',
|
|
||||||
'totalIterations',
|
|
||||||
'totalSteps',
|
|
||||||
'openModel',
|
|
||||||
'isCancelScheduled',
|
|
||||||
'progressImage',
|
|
||||||
'wereModelsReceived',
|
|
||||||
'wasSchemaParsed',
|
|
||||||
];
|
|
||||||
export const systemPersistDenylist: (keyof SystemState)[] = [
|
export const systemPersistDenylist: (keyof SystemState)[] = [
|
||||||
'currentIteration',
|
'currentIteration',
|
||||||
'currentStatus',
|
'currentStatus',
|
||||||
@ -39,8 +21,5 @@ export const systemPersistDenylist: (keyof SystemState)[] = [
|
|||||||
'wereModelsReceived',
|
'wereModelsReceived',
|
||||||
'wasSchemaParsed',
|
'wasSchemaParsed',
|
||||||
'isPersisted',
|
'isPersisted',
|
||||||
|
'isUploading',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const systemDenylist = itemsToDenylist.map(
|
|
||||||
(denylistItem) => `system.${denylistItem}`
|
|
||||||
);
|
|
||||||
|
@ -25,6 +25,7 @@ import { TFuncKey } from 'i18next';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
import { LANGUAGES } from '../components/LanguagePicker';
|
import { LANGUAGES } from '../components/LanguagePicker';
|
||||||
|
import { imageUploaded } from 'services/thunks/image';
|
||||||
|
|
||||||
export type CancelStrategy = 'immediate' | 'scheduled';
|
export type CancelStrategy = 'immediate' | 'scheduled';
|
||||||
|
|
||||||
@ -93,6 +94,7 @@ export interface SystemState {
|
|||||||
isPersisted: boolean;
|
isPersisted: boolean;
|
||||||
shouldAntialiasProgressImage: boolean;
|
shouldAntialiasProgressImage: boolean;
|
||||||
language: keyof typeof LANGUAGES;
|
language: keyof typeof LANGUAGES;
|
||||||
|
isUploading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialSystemState: SystemState = {
|
export const initialSystemState: SystemState = {
|
||||||
@ -128,6 +130,7 @@ export const initialSystemState: SystemState = {
|
|||||||
infillMethods: ['tile', 'patchmatch'],
|
infillMethods: ['tile', 'patchmatch'],
|
||||||
isPersisted: false,
|
isPersisted: false,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
isUploading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const systemSlice = createSlice({
|
export const systemSlice = createSlice({
|
||||||
@ -456,6 +459,27 @@ export const systemSlice = createSlice({
|
|||||||
builder.addCase(parsedOpenAPISchema, (state) => {
|
builder.addCase(parsedOpenAPISchema, (state) => {
|
||||||
state.wasSchemaParsed = true;
|
state.wasSchemaParsed = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Uploading Started
|
||||||
|
*/
|
||||||
|
builder.addCase(imageUploaded.pending, (state) => {
|
||||||
|
state.isUploading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Uploading Complete
|
||||||
|
*/
|
||||||
|
builder.addCase(imageUploaded.rejected, (state) => {
|
||||||
|
state.isUploading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Uploading Complete
|
||||||
|
*/
|
||||||
|
builder.addCase(imageUploaded.fulfilled, (state) => {
|
||||||
|
state.isUploading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
|
Spacer,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
@ -35,6 +36,7 @@ import NodesTab from './tabs/Nodes/NodesTab';
|
|||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
import ResizeHandle from './tabs/ResizeHandle';
|
import ResizeHandle from './tabs/ResizeHandle';
|
||||||
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
||||||
|
import AuxiliaryProgressIndicator from 'app/components/AuxiliaryProgressIndicator';
|
||||||
|
|
||||||
export interface InvokeTabInfo {
|
export interface InvokeTabInfo {
|
||||||
id: InvokeTabName;
|
id: InvokeTabName;
|
||||||
@ -162,6 +164,8 @@ const InvokeTabs = () => {
|
|||||||
justifyContent={{ base: 'center', xl: 'start' }}
|
justifyContent={{ base: 'center', xl: 'start' }}
|
||||||
>
|
>
|
||||||
{tabs}
|
{tabs}
|
||||||
|
<Spacer />
|
||||||
|
<AuxiliaryProgressIndicator />
|
||||||
</TabList>
|
</TabList>
|
||||||
<PanelGroup
|
<PanelGroup
|
||||||
autoSaveId="app"
|
autoSaveId="app"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user