update recent workflows UI

This commit is contained in:
Mary Hipp
2025-03-11 12:55:35 -04:00
committed by psychedelicious
parent df305c0b99
commit afd894fd04
7 changed files with 74 additions and 86 deletions

View File

@@ -1,32 +0,0 @@
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
import { useLoadWorkflow } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
import type { MouseEvent } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiFloppyDiskBold } from 'react-icons/pi';
// needs to clone and save workflow to account without taking over editor
export const SaveWorkflow = ({ workflowId }: { workflowId: string }) => {
const loadWorkflow = useLoadWorkflow();
const { t } = useTranslation();
const handleClickSave = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
loadWorkflow.loadWithDialog(workflowId, 'view');
},
[loadWorkflow, workflowId]
);
return (
<Tooltip label={t('workflows.edit')} closeOnScroll>
<IconButton
size="sm"
variant="ghost"
aria-label={t('workflows.edit')}
onClick={handleClickSave}
icon={<PiFloppyDiskBold />}
/>
</Tooltip>
);
};

View File

@@ -18,11 +18,11 @@ export const ViewWorkflow = ({ workflowId }: { workflowId: string }) => {
);
return (
<Tooltip label={t('workflows.edit')} closeOnScroll>
<Tooltip label={t('workflows.view')} closeOnScroll>
<IconButton
size="sm"
variant="ghost"
aria-label={t('workflows.edit')}
aria-label={t('workflows.view')}
onClick={handleClickLoad}
icon={<PiEyeBold />}
/>

View File

@@ -8,8 +8,10 @@ import {
$workflowLibraryTagCategoriesOptions,
$workflowLibraryTagOptions,
selectWorkflowLibraryCategories,
selectWorkflowLibraryShowOpenedWorkflowsOnly,
selectWorkflowLibraryTags,
workflowLibraryCategoriesChanged,
workflowLibraryShowOpenedWorkflowsOnlyChanged,
workflowLibraryTagsReset,
workflowLibraryTagToggled,
} from 'features/nodes/store/workflowLibrarySlice';
@@ -24,27 +26,36 @@ import { useGetCountsByTagQuery, useListWorkflowsQuery } from 'services/api/endp
import type { S } from 'services/api/types';
export const WorkflowLibrarySideNav = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const categories = useAppSelector(selectWorkflowLibraryCategories);
const categoryOptions = useStore($workflowLibraryCategoriesOptions);
const tags = useAppSelector(selectWorkflowLibraryTags);
const showOpenedWorkflowsOnly = useAppSelector(selectWorkflowLibraryShowOpenedWorkflowsOnly);
const tagCategoryOptions = useStore($workflowLibraryTagCategoriesOptions);
const selectYourWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(categoryOptions.includes('project') ? ['user', 'project'] : ['user']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [categoryOptions, dispatch]);
const selectPrivateWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['user']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectSharedWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['project']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectDefaultWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['default']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectRecentWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['default', 'user', 'project']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(true));
}, [dispatch]);
const resetTags = useCallback(() => {
@@ -53,38 +64,35 @@ export const WorkflowLibrarySideNav = () => {
const isYourWorkflowsSelected = useMemo(() => {
if (categoryOptions.includes('project')) {
return categories.includes('user') && categories.includes('project');
return categories.includes('user') && categories.includes('project') && !showOpenedWorkflowsOnly;
} else {
return categories.includes('user');
return categories.includes('user') && !showOpenedWorkflowsOnly;
}
}, [categoryOptions, categories]);
}, [categoryOptions, categories, showOpenedWorkflowsOnly]);
const isPrivateWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('user');
}, [categories]);
return categories.length === 1 && categories.includes('user') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isSharedWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('project');
}, [categories]);
return categories.length === 1 && categories.includes('project') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isDefaultWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('default');
}, [categories]);
return categories.length === 1 && categories.includes('default') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isRecentWorkflowsSelected = useMemo(() => {
return categories.length === 3 && showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
return (
<Flex h="full" minH={0} overflow="hidden" flexDir="column" w={64} gap={1}>
<Flex flexDir="column" w="full" pb={2}>
<Text px={3} py={2} fontSize="md" fontWeight="semibold">
{t('workflows.recentlyOpened')}
</Text>
<Flex flexDir="column" gap={2} pl={4}>
<RecentWorkflows />
</Flex>
<CategoryButton isSelected={isRecentWorkflowsSelected} onClick={selectRecentWorkflows}></CategoryButton>
</Flex>
<Flex flexDir="column" w="full" pb={2}>
<CategoryButton isSelected={isYourWorkflowsSelected} onClick={selectYourWorkflows}>
{t('workflows.yourWorkflows')}
</CategoryButton>
<CategoryButton isSelected={isYourWorkflowsSelected} onClick={selectYourWorkflows}></CategoryButton>
{categoryOptions.includes('project') && (
<Collapse
in={
@@ -96,16 +104,13 @@ export const WorkflowLibrarySideNav = () => {
size="sm"
onClick={selectPrivateWorkflows}
isSelected={isPrivateWorkflowsExclusivelySelected}
>
{t('workflows.private')}
</CategoryButton>
></CategoryButton>
<CategoryButton
size="sm"
rightIcon={<PiUsersBold />}
onClick={selectSharedWorkflows}
isSelected={isSharedWorkflowsExclusivelySelected}
>
{t('workflows.shared')}
<Spacer />
</CategoryButton>
</Flex>
@@ -113,9 +118,10 @@ export const WorkflowLibrarySideNav = () => {
)}
</Flex>
<Flex h="full" minH={0} overflow="hidden" flexDir="column">
<CategoryButton isSelected={isDefaultWorkflowsExclusivelySelected} onClick={selectDefaultWorkflows}>
{t('workflows.browseWorkflows')}
</CategoryButton>
<CategoryButton
isSelected={isDefaultWorkflowsExclusivelySelected}
onClick={selectDefaultWorkflows}
></CategoryButton>
<Collapse in={isDefaultWorkflowsExclusivelySelected}>
<Flex flexDir="column" gap={2} pl={4} py={2} overflow="hidden" h="100%" minH={0}>
<Button
@@ -129,9 +135,7 @@ export const WorkflowLibrarySideNav = () => {
flexShrink={0}
leftIcon={<PiArrowCounterClockwiseBold />}
h={8}
>
{t('workflows.deselectAll')}
</Button>
></Button>
<Flex flexDir="column" gap={2} overflow="auto">
{tagCategoryOptions.map((tagCategory) => (
<TagCategory
@@ -163,11 +167,11 @@ const RecentWorkflows = memo(() => {
const { t } = useTranslation();
const { data, isLoading } = useListWorkflowsQuery(recentWorkflowsQueryArg);
if (isLoading) {
if (isLoading || !data) {
return <Text variant="subtext">{t('common.loading')}</Text>;
}
if (!data) {
if (data.items.length === 0) {
return <Text variant="subtext">{t('workflows.noRecentWorkflows')}</Text>;
}
@@ -212,7 +216,6 @@ const useCountForTagCategory = (tagCategory: WorkflowTagCategory) => {
() =>
({
tags: allTags,
categories: ['default'], // We only allow filtering by tag for default workflows
}) satisfies Parameters<typeof useGetCountsByTagQuery>[0],
[allTags]
);

View File

@@ -8,6 +8,7 @@ import {
selectWorkflowLibraryHasSearchTerm,
selectWorkflowLibraryOrderBy,
selectWorkflowLibrarySearchTerm,
selectWorkflowLibraryShowOpenedWorkflowsOnly,
selectWorkflowLibraryTags,
} from 'features/nodes/store/workflowLibrarySlice';
import { memo, useCallback, useMemo, useRef } from 'react';
@@ -26,6 +27,7 @@ const useInfiniteQueryAry = () => {
const direction = useAppSelector(selectWorkflowLibraryDirection);
const query = useAppSelector(selectWorkflowLibrarySearchTerm);
const tags = useAppSelector(selectWorkflowLibraryTags);
const showOpenedWorkflowsOnly = useAppSelector(selectWorkflowLibraryShowOpenedWorkflowsOnly);
const [debouncedQuery] = useDebounce(query, 500);
const queryArg = useMemo(() => {
@@ -37,8 +39,9 @@ const useInfiniteQueryAry = () => {
categories,
query: debouncedQuery,
tags: categories.length === 1 && categories.includes('default') ? tags : [],
is_recent: showOpenedWorkflowsOnly,
} satisfies Parameters<typeof useListWorkflowsInfiniteInfiniteQuery>[0];
}, [orderBy, direction, categories, debouncedQuery, tags]);
}, [orderBy, direction, categories, debouncedQuery, tags, showOpenedWorkflowsOnly]);
return queryArg;
};

View File

@@ -13,7 +13,6 @@ import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types'
import { DeleteWorkflow } from './WorkflowLibraryListItemActions/DeleteWorkflow';
import { DownloadWorkflow } from './WorkflowLibraryListItemActions/DownloadWorkflow';
import { EditWorkflow } from './WorkflowLibraryListItemActions/EditWorkflow';
import { SaveWorkflow } from './WorkflowLibraryListItemActions/SaveWorkflow';
import { ViewWorkflow } from './WorkflowLibraryListItemActions/ViewWorkflow';
const IMAGE_THUMBNAIL_SIZE = '80px';
@@ -81,19 +80,32 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
minWidth={IMAGE_THUMBNAIL_SIZE}
borderRadius="base"
/>
<Flex flexDir="column" gap={1} justifyContent="flex-start">
<Flex gap={4} alignItems="center">
<Text noOfLines={2}>{workflow.name}</Text>
<Flex flexDir="column" gap={1} justifyContent="space-between">
<Flex flexDir="column" gap={1}>
<Flex gap={4} alignItems="center">
<Text noOfLines={2}>{workflow.name}</Text>
{isActive && (
<Badge color="invokeBlue.400" borderColor="invokeBlue.700" borderWidth={1} bg="transparent" flexShrink={0}>
{t('workflows.opened')}
</Badge>
)}
{isActive && (
<Badge
color="invokeBlue.400"
borderColor="invokeBlue.700"
borderWidth={1}
bg="transparent"
flexShrink={0}
>
{t('workflows.opened')}
</Badge>
)}
</Flex>
<Text variant="subtext" fontSize="xs" noOfLines={2}>
{workflow.description}
</Text>
</Flex>
<Text variant="subtext" fontSize="xs" noOfLines={2}>
{workflow.description}
</Text>
{workflow.opened_at && (
<Text variant="subtext" fontSize="xs" noOfLines={2} justifySelf="flex-end">
{t('workflows.opened')}: {new Date(workflow.opened_at).toLocaleString()}
</Text>
)}
</Flex>
<Spacer />
@@ -114,13 +126,7 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
right={0}
bottom={0}
>
{workflow.category === 'default' && (
<>
{/* need to consider what is useful here and which icons show that. idea is to "try it out"/"view" or "clone for your own changes" */}
<ViewWorkflow workflowId={workflow.workflow_id} />
<SaveWorkflow workflowId={workflow.workflow_id} />
</>
)}
{workflow.category === 'default' && <ViewWorkflow workflowId={workflow.workflow_id} />}
{workflow.category !== 'default' && (
<>
<EditWorkflow workflowId={workflow.workflow_id} />

View File

@@ -11,6 +11,7 @@ type WorkflowLibraryState = {
direction: SQLiteDirection;
tags: string[];
categories: WorkflowCategory[];
showOpenedWorkflowsOnly: boolean;
};
const initialWorkflowLibraryState: WorkflowLibraryState = {
@@ -19,6 +20,7 @@ const initialWorkflowLibraryState: WorkflowLibraryState = {
direction: 'DESC',
tags: [],
categories: ['user'],
showOpenedWorkflowsOnly: false,
};
export const workflowLibrarySlice = createSlice({
@@ -38,6 +40,9 @@ export const workflowLibrarySlice = createSlice({
state.categories = action.payload;
state.searchTerm = '';
},
workflowLibraryShowOpenedWorkflowsOnlyChanged: (state, action: PayloadAction<boolean>) => {
state.showOpenedWorkflowsOnly = action.payload;
},
workflowLibraryTagToggled: (state, action: PayloadAction<string>) => {
const tag = action.payload;
const tags = state.tags;
@@ -58,6 +63,7 @@ export const {
workflowLibraryOrderByChanged,
workflowLibraryDirectionChanged,
workflowLibraryCategoriesChanged,
workflowLibraryShowOpenedWorkflowsOnlyChanged,
workflowLibraryTagToggled,
workflowLibraryTagsReset,
} = workflowLibrarySlice.actions;
@@ -82,6 +88,7 @@ export const selectWorkflowLibraryOrderBy = createWorkflowLibrarySelector(({ ord
export const selectWorkflowLibraryDirection = createWorkflowLibrarySelector(({ direction }) => direction);
export const selectWorkflowLibraryTags = createWorkflowLibrarySelector(({ tags }) => tags);
export const selectWorkflowLibraryCategories = createWorkflowLibrarySelector(({ categories }) => categories);
export const selectWorkflowLibraryShowOpenedWorkflowsOnly = createWorkflowLibrarySelector(({ showOpenedWorkflowsOnly }) => showOpenedWorkflowsOnly);
export const DEFAULT_WORKFLOW_LIBRARY_CATEGORIES = ['user', 'default'] satisfies WorkflowCategory[];
export const $workflowLibraryCategoriesOptions = atom<WorkflowCategory[]>(DEFAULT_WORKFLOW_LIBRARY_CATEGORIES);