From 04cb2d39cbdeda02014ef7bebc1f5916cdfa4b6f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 18 Nov 2022 20:07:34 +1100 Subject: [PATCH] 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 --- frontend/src/app/App.tsx | 12 +++++----- frontend/src/app/socketio/listeners.ts | 1 - frontend/src/app/socketio/middleware.ts | 22 ------------------- .../canvas/util/mergeAndUploadCanvas.ts | 19 +++++++++++++++- .../src/features/options/optionsSelectors.ts | 3 +++ .../features/system/hooks/useToastWatcher.ts | 19 ++++++++++++++++ .../src/features/system/systemSelectors.ts | 6 +++++ frontend/src/features/system/systemSlice.ts | 12 +++++++++- 8 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 frontend/src/features/system/hooks/useToastWatcher.ts create mode 100644 frontend/src/features/system/systemSelectors.ts diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index c8b0c117a9..b1340b7536 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -15,6 +15,7 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors'; import { SystemState } from 'features/system/systemSlice'; import _ from 'lodash'; import { Model } from './invokeai'; +import useToastWatcher from 'features/system/hooks/useToastWatcher'; keepGUIAlive(); @@ -50,18 +51,13 @@ const appSelector = createSelector( const shouldShowGalleryButton = !(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && - ['txt2img', 'img2img', 'unifiedCanvas'].includes( - activeTabName - ); + ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName); const shouldShowOptionsPanelButton = !( shouldShowOptionsPanel || (shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel) - ) && - ['txt2img', 'img2img', 'unifiedCanvas'].includes( - activeTabName - ); + ) && ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName); return { modelStatusText, @@ -80,6 +76,8 @@ const App = () => { const { shouldShowGalleryButton, shouldShowOptionsPanelButton } = useAppSelector(appSelector); + useToastWatcher(); + return (
diff --git a/frontend/src/app/socketio/listeners.ts b/frontend/src/app/socketio/listeners.ts index 720f18d722..d10071db95 100644 --- a/frontend/src/app/socketio/listeners.ts +++ b/frontend/src/app/socketio/listeners.ts @@ -23,7 +23,6 @@ import { clearIntermediateImage, GalleryState, removeImage, - setCurrentImage, setIntermediateImage, } from 'features/gallery/gallerySlice'; diff --git a/frontend/src/app/socketio/middleware.ts b/frontend/src/app/socketio/middleware.ts index ac17884128..6beecca64e 100644 --- a/frontend/src/app/socketio/middleware.ts +++ b/frontend/src/app/socketio/middleware.ts @@ -43,8 +43,6 @@ export const socketioMiddleware = () => { onGalleryImages, onProcessingCanceled, onImageDeleted, - // onImageUploaded, - // onMaskImageUploaded, onSystemConfig, onModelChanged, onModelChangeFailed, @@ -58,8 +56,6 @@ export const socketioMiddleware = () => { emitRequestImages, emitRequestNewImages, emitCancelProcessing, - // emitUploadImage, - // emitUploadMaskImage, emitRequestSystemConfig, emitRequestModelChange, } = makeSocketIOEmitters(store, socketio); @@ -104,14 +100,6 @@ export const socketioMiddleware = () => { onImageDeleted(data); }); - // socketio.on('imageUploaded', (data: InvokeAI.ImageUploadResponse) => { - // onImageUploaded(data); - // }); - - // socketio.on('maskImageUploaded', (data: InvokeAI.ImageUrlResponse) => { - // onMaskImageUploaded(data); - // }); - socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => { onSystemConfig(data); }); @@ -166,16 +154,6 @@ export const socketioMiddleware = () => { break; } - // case 'socketio/uploadImage': { - // emitUploadImage(action.payload); - // break; - // } - - // case 'socketio/uploadMaskImage': { - // emitUploadMaskImage(action.payload); - // break; - // } - case 'socketio/requestSystemConfig': { emitRequestSystemConfig(); break; diff --git a/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts b/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts index 1dd1387b44..1c1e171fc3 100644 --- a/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts +++ b/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts @@ -6,6 +6,7 @@ import layerToDataURL from './layerToDataURL'; import downloadFile from './downloadFile'; import copyImage from './copyImage'; import { getCanvasBaseLayer } from './konvaInstanceProvider'; +import { addToast } from 'features/system/systemSlice'; export const mergeAndUploadCanvas = createAsyncThunk( 'canvas/mergeAndUploadCanvas', @@ -21,7 +22,7 @@ export const mergeAndUploadCanvas = createAsyncThunk( const { saveToGallery, downloadAfterSaving, cropVisible, copyAfterSaving } = args; - const { getState } = thunkAPI; + const { getState, dispatch } = thunkAPI; const state = getState() as RootState; @@ -60,11 +61,27 @@ export const mergeAndUploadCanvas = createAsyncThunk( if (downloadAfterSaving) { downloadFile(url); + dispatch( + addToast({ + title: 'Image Download Started', + status: 'success', + duration: 2500, + isClosable: true, + }) + ); return; } if (copyAfterSaving) { copyImage(url, width, height); + dispatch( + addToast({ + title: 'Image Copied', + status: 'success', + duration: 2500, + isClosable: true, + }) + ); return; } diff --git a/frontend/src/features/options/optionsSelectors.ts b/frontend/src/features/options/optionsSelectors.ts index c02512e34f..3d513f08d3 100644 --- a/frontend/src/features/options/optionsSelectors.ts +++ b/frontend/src/features/options/optionsSelectors.ts @@ -27,3 +27,6 @@ export const mayGenerateMultipleImagesSelector = createSelector( }, } ); + +export const optionsSelector = (state: RootState): OptionsState => + state.options; diff --git a/frontend/src/features/system/hooks/useToastWatcher.ts b/frontend/src/features/system/hooks/useToastWatcher.ts new file mode 100644 index 0000000000..5aeca3289e --- /dev/null +++ b/frontend/src/features/system/hooks/useToastWatcher.ts @@ -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; diff --git a/frontend/src/features/system/systemSelectors.ts b/frontend/src/features/system/systemSelectors.ts new file mode 100644 index 0000000000..9c1322cf92 --- /dev/null +++ b/frontend/src/features/system/systemSelectors.ts @@ -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; diff --git a/frontend/src/features/system/systemSlice.ts b/frontend/src/features/system/systemSlice.ts index 1dff43ce17..76a6e6d7b3 100644 --- a/frontend/src/features/system/systemSlice.ts +++ b/frontend/src/features/system/systemSlice.ts @@ -1,6 +1,6 @@ import { createSlice } 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'; export type LogLevel = 'info' | 'warning' | 'error'; @@ -45,6 +45,7 @@ export interface SystemState isCancelable: boolean; saveIntermediatesInterval: number; enableImageDebugging: boolean; + toastQueue: UseToastOptions[]; } const initialSystemState: SystemState = { @@ -76,6 +77,7 @@ const initialSystemState: SystemState = { isCancelable: true, saveIntermediatesInterval: 5, enableImageDebugging: false, + toastQueue: [], }; export const systemSlice = createSlice({ @@ -206,6 +208,12 @@ export const systemSlice = createSlice({ setEnableImageDebugging: (state, action: PayloadAction) => { state.enableImageDebugging = action.payload; }, + addToast: (state, action: PayloadAction) => { + state.toastQueue.push(action.payload); + }, + clearToastQueue: (state) => { + state.toastQueue = []; + }, }, }); @@ -231,6 +239,8 @@ export const { setSaveIntermediatesInterval, setEnableImageDebugging, generationRequested, + addToast, + clearToastQueue, } = systemSlice.actions; export default systemSlice.reducer;