Adds useToastWatcher hook

- Dispatch an `addToast` action with standard Chakra toast options object to add a toast to the toastQueue
- The hook is called in App.tsx and just useEffect's w/ toastQueue as dependency to create the toasts
- So now you can add toasts anywhere you have access to `dispatch`, which includes middleware and thunks
- Adds first usage of this for the save image buttons in canvas
This commit is contained in:
psychedelicious 2022-11-18 20:07:34 +11:00 committed by blessedcoolant
parent c69573e65d
commit 04cb2d39cb
8 changed files with 62 additions and 32 deletions

View File

@ -15,6 +15,7 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { SystemState } from 'features/system/systemSlice'; import { SystemState } from 'features/system/systemSlice';
import _ from 'lodash'; import _ from 'lodash';
import { Model } from './invokeai'; import { Model } from './invokeai';
import useToastWatcher from 'features/system/hooks/useToastWatcher';
keepGUIAlive(); keepGUIAlive();
@ -50,18 +51,13 @@ const appSelector = createSelector(
const shouldShowGalleryButton = const shouldShowGalleryButton =
!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && !(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) &&
['txt2img', 'img2img', 'unifiedCanvas'].includes( ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
activeTabName
);
const shouldShowOptionsPanelButton = const shouldShowOptionsPanelButton =
!( !(
shouldShowOptionsPanel || shouldShowOptionsPanel ||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel) (shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
) && ) && ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
['txt2img', 'img2img', 'unifiedCanvas'].includes(
activeTabName
);
return { return {
modelStatusText, modelStatusText,
@ -80,6 +76,8 @@ const App = () => {
const { shouldShowGalleryButton, shouldShowOptionsPanelButton } = const { shouldShowGalleryButton, shouldShowOptionsPanelButton } =
useAppSelector(appSelector); useAppSelector(appSelector);
useToastWatcher();
return ( return (
<div className="App"> <div className="App">
<ImageUploader> <ImageUploader>

View File

@ -23,7 +23,6 @@ import {
clearIntermediateImage, clearIntermediateImage,
GalleryState, GalleryState,
removeImage, removeImage,
setCurrentImage,
setIntermediateImage, setIntermediateImage,
} from 'features/gallery/gallerySlice'; } from 'features/gallery/gallerySlice';

View File

@ -43,8 +43,6 @@ export const socketioMiddleware = () => {
onGalleryImages, onGalleryImages,
onProcessingCanceled, onProcessingCanceled,
onImageDeleted, onImageDeleted,
// onImageUploaded,
// onMaskImageUploaded,
onSystemConfig, onSystemConfig,
onModelChanged, onModelChanged,
onModelChangeFailed, onModelChangeFailed,
@ -58,8 +56,6 @@ export const socketioMiddleware = () => {
emitRequestImages, emitRequestImages,
emitRequestNewImages, emitRequestNewImages,
emitCancelProcessing, emitCancelProcessing,
// emitUploadImage,
// emitUploadMaskImage,
emitRequestSystemConfig, emitRequestSystemConfig,
emitRequestModelChange, emitRequestModelChange,
} = makeSocketIOEmitters(store, socketio); } = makeSocketIOEmitters(store, socketio);
@ -104,14 +100,6 @@ export const socketioMiddleware = () => {
onImageDeleted(data); onImageDeleted(data);
}); });
// socketio.on('imageUploaded', (data: InvokeAI.ImageUploadResponse) => {
// onImageUploaded(data);
// });
// socketio.on('maskImageUploaded', (data: InvokeAI.ImageUrlResponse) => {
// onMaskImageUploaded(data);
// });
socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => { socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => {
onSystemConfig(data); onSystemConfig(data);
}); });
@ -166,16 +154,6 @@ export const socketioMiddleware = () => {
break; break;
} }
// case 'socketio/uploadImage': {
// emitUploadImage(action.payload);
// break;
// }
// case 'socketio/uploadMaskImage': {
// emitUploadMaskImage(action.payload);
// break;
// }
case 'socketio/requestSystemConfig': { case 'socketio/requestSystemConfig': {
emitRequestSystemConfig(); emitRequestSystemConfig();
break; break;

View File

@ -6,6 +6,7 @@ import layerToDataURL from './layerToDataURL';
import downloadFile from './downloadFile'; import downloadFile from './downloadFile';
import copyImage from './copyImage'; import copyImage from './copyImage';
import { getCanvasBaseLayer } from './konvaInstanceProvider'; import { getCanvasBaseLayer } from './konvaInstanceProvider';
import { addToast } from 'features/system/systemSlice';
export const mergeAndUploadCanvas = createAsyncThunk( export const mergeAndUploadCanvas = createAsyncThunk(
'canvas/mergeAndUploadCanvas', 'canvas/mergeAndUploadCanvas',
@ -21,7 +22,7 @@ export const mergeAndUploadCanvas = createAsyncThunk(
const { saveToGallery, downloadAfterSaving, cropVisible, copyAfterSaving } = const { saveToGallery, downloadAfterSaving, cropVisible, copyAfterSaving } =
args; args;
const { getState } = thunkAPI; const { getState, dispatch } = thunkAPI;
const state = getState() as RootState; const state = getState() as RootState;
@ -60,11 +61,27 @@ export const mergeAndUploadCanvas = createAsyncThunk(
if (downloadAfterSaving) { if (downloadAfterSaving) {
downloadFile(url); downloadFile(url);
dispatch(
addToast({
title: 'Image Download Started',
status: 'success',
duration: 2500,
isClosable: true,
})
);
return; return;
} }
if (copyAfterSaving) { if (copyAfterSaving) {
copyImage(url, width, height); copyImage(url, width, height);
dispatch(
addToast({
title: 'Image Copied',
status: 'success',
duration: 2500,
isClosable: true,
})
);
return; return;
} }

View File

@ -27,3 +27,6 @@ export const mayGenerateMultipleImagesSelector = createSelector(
}, },
} }
); );
export const optionsSelector = (state: RootState): OptionsState =>
state.options;

View File

@ -0,0 +1,19 @@
import { useToast } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store';
import { useEffect } from 'react';
import { toastQueueSelector } from '../systemSelectors';
import { clearToastQueue } from '../systemSlice';
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

@ -0,0 +1,6 @@
import { RootState } from 'app/store';
import { SystemState } from './systemSlice';
export const systemSelector = (state: RootState): SystemState => state.system;
export const toastQueueSelector = (state: RootState) => state.system.toastQueue;

View File

@ -1,6 +1,6 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { ExpandedIndex } from '@chakra-ui/react'; import { ExpandedIndex, UseToastOptions } from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
export type LogLevel = 'info' | 'warning' | 'error'; export type LogLevel = 'info' | 'warning' | 'error';
@ -45,6 +45,7 @@ export interface SystemState
isCancelable: boolean; isCancelable: boolean;
saveIntermediatesInterval: number; saveIntermediatesInterval: number;
enableImageDebugging: boolean; enableImageDebugging: boolean;
toastQueue: UseToastOptions[];
} }
const initialSystemState: SystemState = { const initialSystemState: SystemState = {
@ -76,6 +77,7 @@ const initialSystemState: SystemState = {
isCancelable: true, isCancelable: true,
saveIntermediatesInterval: 5, saveIntermediatesInterval: 5,
enableImageDebugging: false, enableImageDebugging: false,
toastQueue: [],
}; };
export const systemSlice = createSlice({ export const systemSlice = createSlice({
@ -206,6 +208,12 @@ export const systemSlice = createSlice({
setEnableImageDebugging: (state, action: PayloadAction<boolean>) => { setEnableImageDebugging: (state, action: PayloadAction<boolean>) => {
state.enableImageDebugging = action.payload; state.enableImageDebugging = action.payload;
}, },
addToast: (state, action: PayloadAction<UseToastOptions>) => {
state.toastQueue.push(action.payload);
},
clearToastQueue: (state) => {
state.toastQueue = [];
},
}, },
}); });
@ -231,6 +239,8 @@ export const {
setSaveIntermediatesInterval, setSaveIntermediatesInterval,
setEnableImageDebugging, setEnableImageDebugging,
generationRequested, generationRequested,
addToast,
clearToastQueue,
} = systemSlice.actions; } = systemSlice.actions;
export default systemSlice.reducer; export default systemSlice.reducer;