diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 96e832aa45..a7868e4cd2 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1691,6 +1691,7 @@ "searchPlaceholder": "Search by name, description or tags", "filterByTags": "Filter by Tags", "yourWorkflows": "Your Workflows", + "recentlyOpened": "Recently Opened", "private": "Private", "shared": "Shared", "browseWorkflows": "Browse Workflows", diff --git a/invokeai/frontend/web/src/app/store/nanostores/workflowCategories.ts b/invokeai/frontend/web/src/app/store/nanostores/workflowCategories.ts index e0d6107129..3b673cfab3 100644 --- a/invokeai/frontend/web/src/app/store/nanostores/workflowCategories.ts +++ b/invokeai/frontend/web/src/app/store/nanostores/workflowCategories.ts @@ -1,4 +1,4 @@ import type { WorkflowCategory } from 'features/nodes/types/workflow'; import { atom } from 'nanostores'; -export const $workflowCategories = atom(['user', 'default']); +export const $workflowCategories = atom(['user', 'default', 'project']); diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx index 7652c85dcd..1614853605 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx @@ -1,5 +1,5 @@ import type { ButtonProps, CheckboxProps } from '@invoke-ai/ui-library'; -import { Button, Checkbox, Collapse, Flex, Spacer, Text } from '@invoke-ai/ui-library'; +import { Button, Checkbox, Collapse, Flex, Icon, Spacer, Text } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { $workflowCategories } from 'app/store/nanostores/workflowCategories'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -11,12 +11,14 @@ import { workflowSelectedTagsRese, workflowSelectedTagToggled, } from 'features/nodes/store/workflowSlice'; +import { useLoadWorkflow } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog'; import { UploadWorkflowButton } from 'features/workflowLibrary/components/UploadWorkflowButton'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiUsersBold } from 'react-icons/pi'; import { useDispatch } from 'react-redux'; -import { useGetCountsQuery } from 'services/api/endpoints/workflows'; +import { useGetCountsQuery, useListWorkflowsQuery } from 'services/api/endpoints/workflows'; +import type { S } from 'services/api/types'; export const WorkflowLibrarySideNav = () => { const { t } = useTranslation(); @@ -66,8 +68,16 @@ export const WorkflowLibrarySideNav = () => { }, [categories]); return ( - - + + + + {t('workflows.recentlyOpened')} + + + + + + {t('workflows.yourWorkflows')} @@ -98,7 +108,7 @@ export const WorkflowLibrarySideNav = () => { )} - + {t('workflows.browseWorkflows')} @@ -136,6 +146,60 @@ export const WorkflowLibrarySideNav = () => { ); }; +const recentWorkflowsQueryArg = { + page: 0, + per_page: 5, + order_by: 'opened_at', + direction: 'DESC', +} satisfies Parameters[0]; + +const RecentWorkflows = memo(() => { + const { t } = useTranslation(); + const { data, isLoading } = useListWorkflowsQuery(recentWorkflowsQueryArg); + + if (isLoading) { + return {t('common.loading')}; + } + + if (!data) { + return {t('workflows.noRecentWorkflows')}; + } + + return ( + <> + {data.items.map((workflow) => { + return ; + })} + + ); +}); +RecentWorkflows.displayName = 'RecentWorkflows'; + +const RecentWorkflowButton = memo(({ workflow }: { workflow: S['WorkflowRecordListItemWithThumbnailDTO'] }) => { + const loadWorkflow = useLoadWorkflow(); + const load = useCallback(() => { + loadWorkflow.loadWithDialog(workflow.workflow_id, 'view'); + }, [loadWorkflow, workflow.workflow_id]); + + return ( + + + {workflow.name} + + {workflow.category === 'project' && } + + ); +}); +RecentWorkflowButton.displayName = 'RecentWorkflowButton'; + const CategoryButton = memo(({ isSelected, ...rest }: ButtonProps & { isSelected: boolean }) => { return (