From 6301e58a2eee4b9918394b0e3c4514de6f4d4f87 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 12 Feb 2024 12:53:36 -0500 Subject: [PATCH] move upload button into workflow library modal --- .../components/UploadWorkflowButton.tsx | 59 +++++++++++++++++++ .../components/WorkflowLibraryList.tsx | 20 +++++-- .../hooks/useLoadWorkflowFromFile.tsx | 6 +- 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 invokeai/frontend/web/src/features/workflowLibrary/components/UploadWorkflowButton.tsx diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/UploadWorkflowButton.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/UploadWorkflowButton.tsx new file mode 100644 index 0000000000..3d518476f1 --- /dev/null +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/UploadWorkflowButton.tsx @@ -0,0 +1,59 @@ +import { Button, IconButton } from '@invoke-ai/ui-library'; +import { useLoadWorkflowFromFile } from 'features/workflowLibrary/hooks/useLoadWorkflowFromFile'; +import { memo, useCallback, useRef } from 'react'; +import { useDropzone } from 'react-dropzone'; +import { useTranslation } from 'react-i18next'; +import { PiUploadSimpleBold } from 'react-icons/pi'; + +interface Props { + full?: boolean; + onSuccess?: () => void; +} + +const UploadWorkflowMenuItem = ({ full, onSuccess }: Props) => { + const { t } = useTranslation(); + const resetRef = useRef<() => void>(null); + const loadWorkflowFromFile = useLoadWorkflowFromFile({ resetRef, onSuccess }); + + const onDropAccepted = useCallback( + (files: File[]) => { + if (!files[0]) { + return; + } + loadWorkflowFromFile(files[0]); + }, + [loadWorkflowFromFile] + ); + + const { getRootProps } = useDropzone({ + accept: { 'application/json': ['.json'] }, + onDropAccepted, + noDrag: true, + multiple: false, + }); + return ( + <> + {full ? ( + + ) : ( + } + {...getRootProps()} + pointerEvents="auto" + /> + )} + + ); +}; + +export default memo(UploadWorkflowMenuItem); diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx index c653870af1..726773c525 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx @@ -1,5 +1,6 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; import { + Box, Button, ButtonGroup, Combobox, @@ -20,6 +21,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon import type { WorkflowCategory } from 'features/nodes/types/workflow'; import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem'; import WorkflowLibraryPagination from 'features/workflowLibrary/components/WorkflowLibraryPagination'; +import { useWorkflowLibraryModalContext } from 'features/workflowLibrary/context/useWorkflowLibraryModalContext'; import type { ChangeEvent, KeyboardEvent } from 'react'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -29,6 +31,8 @@ import type { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types' import { useDebounce } from 'use-debounce'; import { z } from 'zod'; +import UploadWorkflowButton from './UploadWorkflowButton'; + const PER_PAGE = 10; const zOrderBy = z.enum(['opened_at', 'created_at', 'updated_at', 'name']); @@ -55,6 +59,7 @@ const WorkflowLibraryList = () => { const [selectedCategory, setSelectedCategory] = useState('user'); const [page, setPage] = useState(0); const [query, setQuery] = useState(''); + const { onClose } = useWorkflowLibraryModalContext(); const projectId = useStore($projectId); const orderByOptions = useMemo(() => { @@ -221,11 +226,16 @@ const WorkflowLibraryList = () => { )} - {data && ( - - - - )} + + + + + + + {data && } + + + ); }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx index 0b284ee7db..8610fa87e0 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx @@ -10,11 +10,12 @@ import { useTranslation } from 'react-i18next'; type useLoadWorkflowFromFileOptions = { resetRef: RefObject<() => void>; + onSuccess?: () => void; }; type UseLoadWorkflowFromFile = (options: useLoadWorkflowFromFileOptions) => (file: File | null) => void; -export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef }) => { +export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef, onSuccess }) => { const dispatch = useAppDispatch(); const logger = useLogger('nodes'); const { t } = useTranslation(); @@ -31,6 +32,7 @@ export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef }) = const parsedJSON = JSON.parse(String(rawJSON)); dispatch(workflowLoadRequested({ workflow: parsedJSON, asCopy: true })); dispatch(workflowLoadedFromFile()); + onSuccess && onSuccess(); } catch (e) { // There was a problem reading the file logger.error(t('nodes.unableToLoadWorkflow')); @@ -51,7 +53,7 @@ export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef }) = // Reset the file picker internal state so that the same file can be loaded again resetRef.current?.(); }, - [dispatch, logger, resetRef, t] + [dispatch, logger, resetRef, t, onSuccess] ); return loadWorkflowFromFile;