feat(ui): make toast/hotkey into logical components

This commit is contained in:
psychedelicious 2023-05-15 13:53:41 +10:00
parent 0221ca8f49
commit 5e4457445f
13 changed files with 181 additions and 147 deletions

View File

@ -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,6 +72,7 @@ const App = ({
}, [isApplicationReady, setIsReady]); }, [isApplicationReady, setIsReady]);
return ( return (
<>
<Grid w="100vw" h="100vh" position="relative" overflow="hidden"> <Grid w="100vw" h="100vh" position="relative" overflow="hidden">
{isLightboxEnabled && <Lightbox />} {isLightboxEnabled && <Lightbox />}
<ImageUploader> <ImageUploader>
@ -136,6 +132,9 @@ const App = ({
<FloatingGalleryButton /> <FloatingGalleryButton />
</Portal> </Portal>
</Grid> </Grid>
<Toaster />
<GlobalHotkeys />
</>
); );
}; };

View File

@ -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);

View 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;

View File

@ -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({

View File

@ -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

View File

@ -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(() => {

View File

@ -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,

View File

@ -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 (

View File

@ -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 = () => {

View File

@ -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 {

View File

@ -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;

View File

@ -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';

View File

@ -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 = {