mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): make toast/hotkey into logical components
This commit is contained in:
parent
0221ca8f49
commit
5e4457445f
@ -2,9 +2,6 @@ import ImageUploader from 'common/components/ImageUploader';
|
|||||||
import SiteHeader from 'features/system/components/SiteHeader';
|
import SiteHeader from 'features/system/components/SiteHeader';
|
||||||
import ProgressBar from 'features/system/components/ProgressBar';
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
|
|
||||||
import useToastWatcher from 'features/system/hooks/useToastWatcher';
|
|
||||||
|
|
||||||
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
|
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
|
||||||
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
|
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
|
||||||
import { Box, Flex, Grid, Portal } from '@chakra-ui/react';
|
import { Box, Flex, Grid, Portal } from '@chakra-ui/react';
|
||||||
@ -17,13 +14,14 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||||||
import Loading from 'common/components/Loading/Loading';
|
import Loading from 'common/components/Loading/Loading';
|
||||||
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
||||||
import { PartialAppConfig } from 'app/types/invokeai';
|
import { PartialAppConfig } from 'app/types/invokeai';
|
||||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
|
||||||
import { configChanged } from 'features/system/store/configSlice';
|
import { configChanged } from 'features/system/store/configSlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { useLogger } from 'app/logging/useLogger';
|
import { useLogger } from 'app/logging/useLogger';
|
||||||
import ParametersDrawer from 'features/ui/components/ParametersDrawer';
|
import ParametersDrawer from 'features/ui/components/ParametersDrawer';
|
||||||
import { languageSelector } from 'features/system/store/systemSelectors';
|
import { languageSelector } from 'features/system/store/systemSelectors';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
|
import Toaster from './Toaster';
|
||||||
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -38,9 +36,6 @@ const App = ({
|
|||||||
headerComponent,
|
headerComponent,
|
||||||
setIsReady,
|
setIsReady,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
useToastWatcher();
|
|
||||||
useGlobalHotkeys();
|
|
||||||
|
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(languageSelector);
|
||||||
|
|
||||||
const log = useLogger();
|
const log = useLogger();
|
||||||
@ -77,65 +72,69 @@ const App = ({
|
|||||||
}, [isApplicationReady, setIsReady]);
|
}, [isApplicationReady, setIsReady]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
|
<>
|
||||||
{isLightboxEnabled && <Lightbox />}
|
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
|
||||||
<ImageUploader>
|
{isLightboxEnabled && <Lightbox />}
|
||||||
<ProgressBar />
|
<ImageUploader>
|
||||||
<Grid
|
<ProgressBar />
|
||||||
gap={4}
|
<Grid
|
||||||
p={4}
|
|
||||||
gridAutoRows="min-content auto"
|
|
||||||
w={APP_WIDTH}
|
|
||||||
h={APP_HEIGHT}
|
|
||||||
>
|
|
||||||
{headerComponent || <SiteHeader />}
|
|
||||||
<Flex
|
|
||||||
gap={4}
|
gap={4}
|
||||||
w={{ base: '100vw', xl: 'full' }}
|
p={4}
|
||||||
h="full"
|
gridAutoRows="min-content auto"
|
||||||
flexDir={{ base: 'column', xl: 'row' }}
|
w={APP_WIDTH}
|
||||||
|
h={APP_HEIGHT}
|
||||||
>
|
>
|
||||||
<InvokeTabs />
|
{headerComponent || <SiteHeader />}
|
||||||
</Flex>
|
<Flex
|
||||||
</Grid>
|
gap={4}
|
||||||
</ImageUploader>
|
w={{ base: '100vw', xl: 'full' }}
|
||||||
|
h="full"
|
||||||
|
flexDir={{ base: 'column', xl: 'row' }}
|
||||||
|
>
|
||||||
|
<InvokeTabs />
|
||||||
|
</Flex>
|
||||||
|
</Grid>
|
||||||
|
</ImageUploader>
|
||||||
|
|
||||||
<GalleryDrawer />
|
<GalleryDrawer />
|
||||||
<ParametersDrawer />
|
<ParametersDrawer />
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!isApplicationReady && !loadingOverridden && (
|
{!isApplicationReady && !loadingOverridden && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="loading"
|
key="loading"
|
||||||
initial={{ opacity: 1 }}
|
initial={{ opacity: 1 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
style={{ zIndex: 3 }}
|
style={{ zIndex: 3 }}
|
||||||
>
|
>
|
||||||
<Box position="absolute" top={0} left={0} w="100vw" h="100vh">
|
<Box position="absolute" top={0} left={0} w="100vw" h="100vh">
|
||||||
<Loading />
|
<Loading />
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
onClick={handleOverrideClicked}
|
onClick={handleOverrideClicked}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
right={0}
|
right={0}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
w="2rem"
|
w="2rem"
|
||||||
h="2rem"
|
h="2rem"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
<Portal>
|
<Portal>
|
||||||
<FloatingParametersPanelButtons />
|
<FloatingParametersPanelButtons />
|
||||||
</Portal>
|
</Portal>
|
||||||
<Portal>
|
<Portal>
|
||||||
<FloatingGalleryButton />
|
<FloatingGalleryButton />
|
||||||
</Portal>
|
</Portal>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Toaster />
|
||||||
|
<GlobalHotkeys />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
togglePinParametersPanel,
|
togglePinParametersPanel,
|
||||||
} from 'features/ui/store/uiSlice';
|
} from 'features/ui/store/uiSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
|
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
const globalHotkeysSelector = createSelector(
|
const globalHotkeysSelector = createSelector(
|
||||||
@ -27,7 +28,11 @@ const globalHotkeysSelector = createSelector(
|
|||||||
|
|
||||||
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
|
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
|
||||||
|
|
||||||
export const useGlobalHotkeys = () => {
|
/**
|
||||||
|
* Logical component. Handles app-level global hotkeys.
|
||||||
|
* @returns null
|
||||||
|
*/
|
||||||
|
const GlobalHotkeys: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { shift } = useAppSelector(globalHotkeysSelector);
|
const { shift } = useAppSelector(globalHotkeysSelector);
|
||||||
|
|
||||||
@ -75,4 +80,8 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys('4', () => {
|
useHotkeys('4', () => {
|
||||||
dispatch(setActiveTab('nodes'));
|
dispatch(setActiveTab('nodes'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default memo(GlobalHotkeys);
|
65
invokeai/frontend/web/src/app/components/Toaster.ts
Normal file
65
invokeai/frontend/web/src/app/components/Toaster.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { useToast, UseToastOptions } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { toastQueueSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
export type MakeToastArg = string | UseToastOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a toast from a string or a UseToastOptions object.
|
||||||
|
* If a string is passed, the toast will have the status 'info' and will be closable with a duration of 2500ms.
|
||||||
|
*/
|
||||||
|
export const makeToast = (arg: MakeToastArg): UseToastOptions => {
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
return {
|
||||||
|
title: arg,
|
||||||
|
status: 'info',
|
||||||
|
isClosable: true,
|
||||||
|
duration: 2500,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'info', isClosable: true, duration: 2500, ...arg };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical component. Watches the toast queue and makes toasts when the queue is not empty.
|
||||||
|
* @returns null
|
||||||
|
*/
|
||||||
|
const Toaster = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const toastQueue = useAppSelector(toastQueueSelector);
|
||||||
|
const toast = useToast();
|
||||||
|
useEffect(() => {
|
||||||
|
toastQueue.forEach((t) => {
|
||||||
|
toast(t);
|
||||||
|
});
|
||||||
|
toastQueue.length > 0 && dispatch(clearToastQueue());
|
||||||
|
}, [dispatch, toast, toastQueue]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that can be used to make a toast.
|
||||||
|
* @example
|
||||||
|
* const toaster = useAppToaster();
|
||||||
|
* toaster('Hello world!');
|
||||||
|
* toaster({ title: 'Hello world!', status: 'success' });
|
||||||
|
* @returns A function that can be used to make a toast.
|
||||||
|
* @see makeToast
|
||||||
|
* @see MakeToastArg
|
||||||
|
* @see UseToastOptions
|
||||||
|
*/
|
||||||
|
export const useAppToaster = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const toaster = useCallback(
|
||||||
|
(arg: MakeToastArg) => dispatch(addToast(makeToast(arg))),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return toaster;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toaster;
|
@ -2,11 +2,11 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
|||||||
import { Image, isInvokeAIImage } from 'app/types/invokeai';
|
import { Image, isInvokeAIImage } from 'app/types/invokeai';
|
||||||
import { selectResultsById } from 'features/gallery/store/resultsSlice';
|
import { selectResultsById } from 'features/gallery/store/resultsSlice';
|
||||||
import { selectUploadsById } from 'features/gallery/store/uploadsSlice';
|
import { selectUploadsById } from 'features/gallery/store/uploadsSlice';
|
||||||
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
|
import { makeToast } from 'app/components/Toaster';
|
||||||
|
|
||||||
export const addInitialImageSelectedListener = () => {
|
export const addInitialImageSelectedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, useToast } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import useImageUploader from 'common/hooks/useImageUploader';
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
@ -16,6 +16,7 @@ import { FileRejection, useDropzone } from 'react-dropzone';
|
|||||||
import { useTranslation } from 'react-i18next';
|
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';
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
type ImageUploaderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -25,7 +26,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const toast = useToast({});
|
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 { setOpenUploader } = useImageUploader();
|
||||||
@ -37,14 +38,14 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
(acc: string, cur: { message: string }) => `${acc}\n${cur.message}`,
|
(acc: string, cur: { message: string }) => `${acc}\n${cur.message}`,
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.uploadFailed'),
|
title: t('toast.uploadFailed'),
|
||||||
description: msg,
|
description: msg,
|
||||||
status: 'error',
|
status: 'error',
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, toast]
|
[t, toaster]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileAcceptedCallback = useCallback(
|
const fileAcceptedCallback = useCallback(
|
||||||
@ -105,7 +106,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
if (imageItems.length > 1) {
|
if (imageItems.length > 1) {
|
||||||
toast({
|
toaster({
|
||||||
description: t('toast.uploadFailedMultipleImagesDesc'),
|
description: t('toast.uploadFailedMultipleImagesDesc'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
@ -116,7 +117,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
const file = imageItems[0].getAsFile();
|
const file = imageItems[0].getAsFile();
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast({
|
toaster({
|
||||||
description: t('toast.uploadFailedUnableToLoadDesc'),
|
description: t('toast.uploadFailedUnableToLoadDesc'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
@ -130,7 +131,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('paste', pasteImageListener);
|
document.removeEventListener('paste', pasteImageListener);
|
||||||
};
|
};
|
||||||
}, [t, dispatch, toast, activeTabName]);
|
}, [t, dispatch, toaster, activeTabName]);
|
||||||
|
|
||||||
const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(
|
const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(
|
||||||
activeTabName
|
activeTabName
|
||||||
|
@ -5,15 +5,8 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Flex,
|
Flex,
|
||||||
FlexProps,
|
FlexProps,
|
||||||
IconButton,
|
|
||||||
Link,
|
Link,
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItemOption,
|
|
||||||
MenuList,
|
|
||||||
MenuOptionGroup,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
@ -70,6 +63,7 @@ import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceR
|
|||||||
import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings';
|
import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings';
|
||||||
import { allParametersSet } from 'features/parameters/store/generationSlice';
|
import { allParametersSet } from 'features/parameters/store/generationSlice';
|
||||||
import DeleteImageButton from './ImageActionButtons/DeleteImageButton';
|
import DeleteImageButton from './ImageActionButtons/DeleteImageButton';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
@ -164,7 +158,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
onClose: onDeleteDialogClose,
|
onClose: onDeleteDialogClose,
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const toast = useToast();
|
const toaster = useAppToaster();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { recallPrompt, recallSeed, recallAllParameters } = useParameters();
|
const { recallPrompt, recallSeed, recallAllParameters } = useParameters();
|
||||||
@ -213,7 +207,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
const url = getImageUrl();
|
const url = getImageUrl();
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.problemCopyingImageLink'),
|
title: t('toast.problemCopyingImageLink'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
@ -224,14 +218,14 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.imageLinkCopied'),
|
title: t('toast.imageLinkCopied'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [toast, shouldTransformUrls, getUrl, t, image]);
|
}, [toaster, shouldTransformUrls, getUrl, t, image]);
|
||||||
|
|
||||||
const handlePreviewVisibility = useCallback(() => {
|
const handlePreviewVisibility = useCallback(() => {
|
||||||
dispatch(setShouldHidePreview(!shouldHidePreview));
|
dispatch(setShouldHidePreview(!shouldHidePreview));
|
||||||
@ -346,13 +340,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('unifiedCanvas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.sentToUnifiedCanvas'),
|
title: t('toast.sentToUnifiedCanvas'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
}, [image, isLightboxOpen, dispatch, activeTabName, toast, t]);
|
}, [image, isLightboxOpen, dispatch, activeTabName, toaster, t]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'i',
|
'i',
|
||||||
@ -360,7 +354,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
if (image) {
|
if (image) {
|
||||||
handleClickShowImageDetails();
|
handleClickShowImageDetails();
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.metadataLoadFailed'),
|
title: t('toast.metadataLoadFailed'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
@ -368,7 +362,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[image, shouldShowImageDetails]
|
[image, shouldShowImageDetails, toaster]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
@ -35,6 +34,7 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
|||||||
import { useParameters } from 'features/parameters/hooks/useParameters';
|
import { useParameters } from 'features/parameters/hooks/useParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import { requestedImageDeletion } from '../store/actions';
|
import { requestedImageDeletion } from '../store/actions';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
|
||||||
export const selector = createSelector(
|
export const selector = createSelector(
|
||||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||||
@ -101,7 +101,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
|
|
||||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||||
|
|
||||||
const toast = useToast();
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('unifiedCanvas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.sentToUnifiedCanvas'),
|
title: t('toast.sentToUnifiedCanvas'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
|
@ -13,10 +13,9 @@ import { nodeAdded } from '../store/nodesSlice';
|
|||||||
import { map } from 'lodash-es';
|
import { map } from 'lodash-es';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useBuildInvocation } from '../hooks/useBuildInvocation';
|
import { useBuildInvocation } from '../hooks/useBuildInvocation';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
|
||||||
const AddNodeMenu = () => {
|
const AddNodeMenu = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -27,22 +26,23 @@ const AddNodeMenu = () => {
|
|||||||
|
|
||||||
const buildInvocation = useBuildInvocation();
|
const buildInvocation = useBuildInvocation();
|
||||||
|
|
||||||
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
const addNode = useCallback(
|
const addNode = useCallback(
|
||||||
(nodeType: AnyInvocationType) => {
|
(nodeType: AnyInvocationType) => {
|
||||||
const invocation = buildInvocation(nodeType);
|
const invocation = buildInvocation(nodeType);
|
||||||
|
|
||||||
if (!invocation) {
|
if (!invocation) {
|
||||||
const toast = makeToast({
|
toaster({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
title: `Unknown Invocation type ${nodeType}`,
|
title: `Unknown Invocation type ${nodeType}`,
|
||||||
});
|
});
|
||||||
dispatch(addToast(toast));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(nodeAdded(invocation));
|
dispatch(nodeAdded(invocation));
|
||||||
},
|
},
|
||||||
[dispatch, buildInvocation]
|
[dispatch, buildInvocation, toaster]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -16,11 +16,11 @@ import {
|
|||||||
import { Tooltip } from '@chakra-ui/tooltip';
|
import { Tooltip } from '@chakra-ui/tooltip';
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import { useBuildInvocation } from 'features/nodes/hooks/useBuildInvocation';
|
import { useBuildInvocation } from 'features/nodes/hooks/useBuildInvocation';
|
||||||
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { nodeAdded } from '../../store/nodesSlice';
|
import { nodeAdded } from '../../store/nodesSlice';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { InvocationTemplate } from 'features/nodes/types/types';
|
import { InvocationTemplate } from 'features/nodes/types/types';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
|
||||||
interface NodeListItemProps {
|
interface NodeListItemProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -63,6 +63,7 @@ const NodeSearch = () => {
|
|||||||
|
|
||||||
const buildInvocation = useBuildInvocation();
|
const buildInvocation = useBuildInvocation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
const [showNodeList, setShowNodeList] = useState<boolean>(false);
|
const [showNodeList, setShowNodeList] = useState<boolean>(false);
|
||||||
@ -89,17 +90,16 @@ const NodeSearch = () => {
|
|||||||
const invocation = buildInvocation(nodeType);
|
const invocation = buildInvocation(nodeType);
|
||||||
|
|
||||||
if (!invocation) {
|
if (!invocation) {
|
||||||
const toast = makeToast({
|
toaster({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
title: `Unknown Invocation type ${nodeType}`,
|
title: `Unknown Invocation type ${nodeType}`,
|
||||||
});
|
});
|
||||||
dispatch(addToast(toast));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(nodeAdded(invocation));
|
dispatch(nodeAdded(invocation));
|
||||||
},
|
},
|
||||||
[dispatch, buildInvocation]
|
[dispatch, buildInvocation, toaster]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderNodeList = () => {
|
const renderNodeList = () => {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useToast } from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { isFinite, isString } from 'lodash-es';
|
import { isFinite, isString } from 'lodash-es';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
@ -10,10 +9,11 @@ import { NUMPY_RAND_MAX } from 'app/constants';
|
|||||||
import { initialImageSelected } from '../store/actions';
|
import { initialImageSelected } from '../store/actions';
|
||||||
import { Image } from 'app/types/invokeai';
|
import { Image } from 'app/types/invokeai';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
|
||||||
export const useParameters = () => {
|
export const useParameters = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const toast = useToast();
|
const toaster = useAppToaster();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const setBothPrompts = useSetBothPrompts();
|
const setBothPrompts = useSetBothPrompts();
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ export const useParameters = () => {
|
|||||||
const recallPrompt = useCallback(
|
const recallPrompt = useCallback(
|
||||||
(prompt: unknown) => {
|
(prompt: unknown) => {
|
||||||
if (!isString(prompt)) {
|
if (!isString(prompt)) {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.promptNotSet'),
|
title: t('toast.promptNotSet'),
|
||||||
description: t('toast.promptNotSetDesc'),
|
description: t('toast.promptNotSetDesc'),
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
@ -34,14 +34,14 @@ export const useParameters = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setBothPrompts(prompt);
|
setBothPrompts(prompt);
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.promptSet'),
|
title: t('toast.promptSet'),
|
||||||
status: 'info',
|
status: 'info',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, toast, setBothPrompts]
|
[t, toaster, setBothPrompts]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +51,7 @@ export const useParameters = () => {
|
|||||||
(seed: unknown) => {
|
(seed: unknown) => {
|
||||||
const s = Number(seed);
|
const s = Number(seed);
|
||||||
if (!isFinite(s) || (isFinite(s) && !(s >= 0 && s <= NUMPY_RAND_MAX))) {
|
if (!isFinite(s) || (isFinite(s) && !(s >= 0 && s <= NUMPY_RAND_MAX))) {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.seedNotSet'),
|
title: t('toast.seedNotSet'),
|
||||||
description: t('toast.seedNotSetDesc'),
|
description: t('toast.seedNotSetDesc'),
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
@ -62,14 +62,14 @@ export const useParameters = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setSeed(s));
|
dispatch(setSeed(s));
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.seedSet'),
|
title: t('toast.seedSet'),
|
||||||
status: 'info',
|
status: 'info',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, toast, dispatch]
|
[t, toaster, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,7 +78,7 @@ export const useParameters = () => {
|
|||||||
const recallInitialImage = useCallback(
|
const recallInitialImage = useCallback(
|
||||||
async (image: unknown) => {
|
async (image: unknown) => {
|
||||||
if (!isImageField(image)) {
|
if (!isImageField(image)) {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.initialImageNotSet'),
|
title: t('toast.initialImageNotSet'),
|
||||||
description: t('toast.initialImageNotSetDesc'),
|
description: t('toast.initialImageNotSetDesc'),
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
@ -91,14 +91,14 @@ export const useParameters = () => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
initialImageSelected({ name: image.image_name, type: image.image_type })
|
initialImageSelected({ name: image.image_name, type: image.image_type })
|
||||||
);
|
);
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.initialImageSet'),
|
title: t('toast.initialImageSet'),
|
||||||
status: 'info',
|
status: 'info',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, toast, dispatch]
|
[t, toaster, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,14 +123,14 @@ export const useParameters = () => {
|
|||||||
dispatch(setActiveTab('txt2img'));
|
dispatch(setActiveTab('txt2img'));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.parametersSet'),
|
title: t('toast.parametersSet'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
isClosable: true,
|
isClosable: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toaster({
|
||||||
title: t('toast.parametersNotSet'),
|
title: t('toast.parametersNotSet'),
|
||||||
description: t('toast.parametersNotSetDesc'),
|
description: t('toast.parametersNotSetDesc'),
|
||||||
status: 'error',
|
status: 'error',
|
||||||
@ -139,7 +139,7 @@ export const useParameters = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[t, toast, dispatch]
|
[t, toaster, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import { useToast, UseToastOptions } from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { toastQueueSelector } from 'features/system/store/systemSelectors';
|
|
||||||
import { clearToastQueue } from 'features/system/store/systemSlice';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export type MakeToastArg = string | UseToastOptions;
|
|
||||||
|
|
||||||
export const makeToast = (arg: MakeToastArg): UseToastOptions => {
|
|
||||||
if (typeof arg === 'string') {
|
|
||||||
return {
|
|
||||||
title: arg,
|
|
||||||
status: 'info',
|
|
||||||
isClosable: true,
|
|
||||||
duration: 2500,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 'info', isClosable: true, duration: 2500, ...arg };
|
|
||||||
};
|
|
||||||
|
|
||||||
const useToastWatcher = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const toastQueue = useAppSelector(toastQueueSelector);
|
|
||||||
const toast = useToast();
|
|
||||||
useEffect(() => {
|
|
||||||
toastQueue.forEach((t) => {
|
|
||||||
toast(t);
|
|
||||||
});
|
|
||||||
toastQueue.length > 0 && dispatch(clearToastQueue());
|
|
||||||
}, [dispatch, toast, toastQueue]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useToastWatcher;
|
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from 'services/events/actions';
|
} from 'services/events/actions';
|
||||||
|
|
||||||
import { ProgressImage } from 'services/events/types';
|
import { ProgressImage } from 'services/events/types';
|
||||||
import { makeToast } from '../hooks/useToastWatcher';
|
import { makeToast } from '../../../app/components/Toaster';
|
||||||
import { sessionCanceled, sessionInvoked } from 'services/thunks/session';
|
import { sessionCanceled, sessionInvoked } from 'services/thunks/session';
|
||||||
import { receivedModels } from 'services/thunks/model';
|
import { receivedModels } from 'services/thunks/model';
|
||||||
import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
|
import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
} from 'services/thunks/gallery';
|
} from 'services/thunks/gallery';
|
||||||
import { receivedModels } from 'services/thunks/model';
|
import { receivedModels } from 'services/thunks/model';
|
||||||
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
||||||
import { makeToast } from '../../../features/system/hooks/useToastWatcher';
|
import { makeToast } from '../../../app/components/Toaster';
|
||||||
import { addToast } from '../../../features/system/store/systemSlice';
|
import { addToast } from '../../../features/system/store/systemSlice';
|
||||||
|
|
||||||
type SetEventListenersArg = {
|
type SetEventListenersArg = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user