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;