From 89da976949b673f65591c2210e7612224ec5b902 Mon Sep 17 00:00:00 2001 From: Mary Hipp Rogers Date: Thu, 25 Jan 2024 11:43:47 -0500 Subject: [PATCH] workflow library updates (#5568) * dont show duplicate toasts if workflow actions fail due to auth * dynamic order by options based on projectId * add endpointName to authtoast to makeit unique per endpoint * lint * update toast logic to check based on endpoint name w type safety * fix save as endpoit name * lint * fix type --------- Co-authored-by: Mary Hipp --- .../components/WorkflowLibraryList.tsx | 20 ++++++++++----- .../hooks/useDeleteLibraryWorkflow.ts | 23 ++++++++++++----- .../hooks/useGetAndLoadLibraryWorkflow.ts | 23 ++++++++++++----- .../workflowLibrary/hooks/useSaveWorkflow.ts | 24 +++++++++++++----- .../hooks/useSaveWorkflowAs.ts | 25 +++++++++++++------ .../src/services/api/authToastMiddleware.ts | 4 ++- 6 files changed, 87 insertions(+), 32 deletions(-) diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx index 3ff519f672..9e8adb6503 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx @@ -61,13 +61,21 @@ const WorkflowLibraryList = () => { const [category, setCategory] = useState('user'); const [page, setPage] = useState(0); const [query, setQuery] = useState(''); - const [order_by, setOrderBy] = useState('opened_at'); + const projectId = useStore($projectId); + const orderByOptions = useMemo(() => { + return projectId + ? ORDER_BY_OPTIONS.filter((option) => option.value !== 'opened_at') + : ORDER_BY_OPTIONS; + }, [projectId]); + + const [order_by, setOrderBy] = useState( + orderByOptions[0]?.value as WorkflowRecordOrderBy + ); const [direction, setDirection] = useState('ASC'); const [debouncedQuery] = useDebounce(query, 500); - const projectId = useStore($projectId); const queryArg = useMemo[0]>(() => { - if (category === 'user') { + if (category !== 'default') { return { page, per_page: PER_PAGE, @@ -101,8 +109,8 @@ const WorkflowLibraryList = () => { [order_by] ); const valueOrderBy = useMemo( - () => ORDER_BY_OPTIONS.find((o) => o.value === order_by), - [order_by] + () => orderByOptions.find((o) => o.value === order_by), + [order_by, orderByOptions] ); const onChangeDirection = useCallback( @@ -186,7 +194,7 @@ const WorkflowLibraryList = () => { {t('common.orderBy')} diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts index bf518fe409..ae541c8157 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts @@ -1,7 +1,11 @@ +import { useToast } from '@invoke-ai/ui'; import { useAppToaster } from 'app/components/Toaster'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDeleteWorkflowMutation } from 'services/api/endpoints/workflows'; +import { + useDeleteWorkflowMutation, + workflowsApi, +} from 'services/api/endpoints/workflows'; type UseDeleteLibraryWorkflowOptions = { onSuccess?: () => void; @@ -22,6 +26,7 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onError, }) => { const toaster = useAppToaster(); + const toast = useToast(); const { t } = useTranslation(); const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation(); @@ -34,14 +39,20 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ }); onSuccess && onSuccess(); } catch { - toaster({ - title: t('toast.problemDeletingWorkflow'), - status: 'error', - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.deleteWorkflow.name}` + ) + ) { + toaster({ + title: t('toast.problemDeletingWorkflow'), + status: 'error', + }); + } onError && onError(); } }, - [_deleteWorkflow, toaster, t, onSuccess, onError] + [_deleteWorkflow, toaster, t, onSuccess, onError, toast] ); return { deleteWorkflow, deleteWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts index 27de12789a..85a0a6c757 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts @@ -1,9 +1,13 @@ +import { useToast } from '@invoke-ai/ui'; import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import { workflowLoadRequested } from 'features/nodes/store/actions'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLazyGetWorkflowQuery } from 'services/api/endpoints/workflows'; +import { + useLazyGetWorkflowQuery, + workflowsApi, +} from 'services/api/endpoints/workflows'; type UseGetAndLoadLibraryWorkflowOptions = { onSuccess?: () => void; @@ -25,6 +29,7 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ }) => { const dispatch = useAppDispatch(); const toaster = useAppToaster(); + const toast = useToast(); const { t } = useTranslation(); const [_getAndLoadWorkflow, getAndLoadWorkflowResult] = useLazyGetWorkflowQuery(); @@ -38,14 +43,20 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ // No toast - the listener for this action does that after the workflow is loaded onSuccess && onSuccess(); } catch { - toaster({ - title: t('toast.problemRetrievingWorkflow'), - status: 'error', - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.getWorkflow.name}` + ) + ) { + toaster({ + title: t('toast.problemRetrievingWorkflow'), + status: 'error', + }); + } onError && onError(); } }, - [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError] + [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError, toast] ); return { getAndLoadWorkflow, getAndLoadWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts index 6417084312..85589c6694 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts @@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'; import { useCreateWorkflowMutation, useUpdateWorkflowMutation, + workflowsApi, } from 'services/api/endpoints/workflows'; import type { O } from 'ts-toolbelt'; @@ -60,12 +61,23 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => { isClosable: true, }); } catch (e) { - toast.update(toastRef.current, { - title: t('workflows.problemSavingWorkflow'), - status: 'error', - duration: 1000, - isClosable: true, - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}` + ) && + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.updateWorkflow.name}` + ) + ) { + toast.update(toastRef.current, { + title: t('workflows.problemSavingWorkflow'), + status: 'error', + duration: 1000, + isClosable: true, + }); + } else { + toast.close(toastRef.current); + } } }, [updateWorkflow, dispatch, toast, t, createWorkflow]); return { diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts index bd0b091346..cb827f2195 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts @@ -9,7 +9,10 @@ import { } from 'features/nodes/store/workflowSlice'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useCreateWorkflowMutation } from 'services/api/endpoints/workflows'; +import { + useCreateWorkflowMutation, + workflowsApi, +} from 'services/api/endpoints/workflows'; type SaveWorkflowAsArg = { name: string; @@ -59,12 +62,20 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => { }); } catch (e) { onError && onError(); - toast.update(toastRef.current, { - title: t('workflows.problemSavingWorkflow'), - status: 'error', - duration: 1000, - isClosable: true, - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}` + ) + ) { + toast.update(toastRef.current, { + title: t('workflows.problemSavingWorkflow'), + status: 'error', + duration: 1000, + isClosable: true, + }); + } else { + toast.close(toastRef.current); + } } }, [toast, createWorkflow, dispatch, t] diff --git a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts index 366f4d06c4..f1e5862c57 100644 --- a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts +++ b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts @@ -27,7 +27,8 @@ export const authToastMiddleware: Middleware = if (isRejectedWithValue(action)) { try { const parsed = zRejectedForbiddenAction.parse(action); - if (parsed.meta?.arg?.endpointName === 'getImageDTO') { + const endpointName = parsed.meta?.arg?.endpointName; + if (endpointName === 'getImageDTO') { // do not show toast if problem is image access return; } @@ -39,6 +40,7 @@ export const authToastMiddleware: Middleware = : undefined; dispatch( addToast({ + id: `auth-error-toast-${endpointName}`, title: t('common.somethingWentWrong'), status: 'error', description: customMessage,