Skip to content

Commit 715db8f

Browse files
Mary HippMary Hipp
Mary Hipp
authored and
Mary Hipp
committed
update recent workflows UI
1 parent 0dd9d0e commit 715db8f

File tree

8 files changed

+67
-100
lines changed

8 files changed

+67
-100
lines changed

Diff for: invokeai/frontend/web/public/locales/en.json

+1
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,7 @@
17261726
"loadWorkflow": "$t(common.load) Workflow",
17271727
"autoLayout": "Auto Layout",
17281728
"edit": "Edit",
1729+
"view": "View",
17291730
"download": "Download",
17301731
"copyShareLink": "Copy Share Link",
17311732
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",

Diff for: invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/SaveWorkflow.tsx

-32
This file was deleted.

Diff for: invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ViewWorkflow.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ export const ViewWorkflow = ({ workflowId }: { workflowId: string }) => {
1818
);
1919

2020
return (
21-
<Tooltip label={t('workflows.edit')} closeOnScroll>
21+
<Tooltip label={t('workflows.view')} closeOnScroll>
2222
<IconButton
2323
size="sm"
2424
variant="ghost"
25-
aria-label={t('workflows.edit')}
25+
aria-label={t('workflows.view')}
2626
onClick={handleClickLoad}
2727
icon={<PiEyeBold />}
2828
/>

Diff for: invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx

+28-45
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { $workflowCategories } from 'app/store/nanostores/workflowCategories';
55
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
66
import { WORKFLOW_TAGS, type WorkflowTag } from 'features/nodes/store/types';
77
import {
8+
selectWorkflowIsRecent,
89
selectWorkflowLibrarySelectedTags,
910
selectWorkflowSelectedCategories,
11+
workflowIsRecentChanged,
1012
workflowSelectedCategoriesChanged,
1113
workflowSelectedTagsRese,
1214
workflowSelectedTagToggled,
@@ -18,7 +20,7 @@ import { memo, useCallback, useMemo } from 'react';
1820
import { useTranslation } from 'react-i18next';
1921
import { PiArrowCounterClockwiseBold, PiUsersBold } from 'react-icons/pi';
2022
import { useDispatch } from 'react-redux';
21-
import { useGetCountsQuery, useListWorkflowsQuery } from 'services/api/endpoints/workflows';
23+
import { useGetCountsQuery } from 'services/api/endpoints/workflows';
2224
import type { S } from 'services/api/types';
2325

2426
export const WorkflowLibrarySideNav = () => {
@@ -27,21 +29,31 @@ export const WorkflowLibrarySideNav = () => {
2729
const categories = useAppSelector(selectWorkflowSelectedCategories);
2830
const categoryOptions = useStore($workflowCategories);
2931
const selectedTags = useAppSelector(selectWorkflowLibrarySelectedTags);
32+
const isRecent = useAppSelector(selectWorkflowIsRecent);
3033

3134
const selectYourWorkflows = useCallback(() => {
3235
dispatch(workflowSelectedCategoriesChanged(categoryOptions.includes('project') ? ['user', 'project'] : ['user']));
36+
dispatch(workflowIsRecentChanged(undefined));
3337
}, [categoryOptions, dispatch]);
3438

3539
const selectPrivateWorkflows = useCallback(() => {
3640
dispatch(workflowSelectedCategoriesChanged(['user']));
41+
dispatch(workflowIsRecentChanged(undefined));
3742
}, [dispatch]);
3843

3944
const selectSharedWorkflows = useCallback(() => {
4045
dispatch(workflowSelectedCategoriesChanged(['project']));
46+
dispatch(workflowIsRecentChanged(undefined));
4147
}, [dispatch]);
4248

4349
const selectDefaultWorkflows = useCallback(() => {
4450
dispatch(workflowSelectedCategoriesChanged(['default']));
51+
dispatch(workflowIsRecentChanged(undefined));
52+
}, [dispatch]);
53+
54+
const selectRecentWorkflows = useCallback(() => {
55+
dispatch(workflowIsRecentChanged(true));
56+
dispatch(workflowSelectedCategoriesChanged(['default', 'user', 'project']));
4557
}, [dispatch]);
4658

4759
const resetTags = useCallback(() => {
@@ -50,33 +62,34 @@ export const WorkflowLibrarySideNav = () => {
5062

5163
const isYourWorkflowsSelected = useMemo(() => {
5264
if (categoryOptions.includes('project')) {
53-
return categories.includes('user') && categories.includes('project');
65+
return categories.includes('user') && categories.includes('project') && isRecent === undefined;
5466
} else {
55-
return categories.includes('user');
67+
return categories.includes('user') && isRecent === undefined;
5668
}
57-
}, [categoryOptions, categories]);
69+
}, [categoryOptions, categories, isRecent]);
5870

5971
const isPrivateWorkflowsExclusivelySelected = useMemo(() => {
60-
return categories.length === 1 && categories.includes('user');
61-
}, [categories]);
72+
return categories.length === 1 && categories.includes('user') && isRecent === undefined;
73+
}, [categories, isRecent]);
6274

6375
const isSharedWorkflowsExclusivelySelected = useMemo(() => {
64-
return categories.length === 1 && categories.includes('project');
65-
}, [categories]);
76+
return categories.length === 1 && categories.includes('project') && isRecent === undefined;
77+
}, [categories, isRecent]);
6678

6779
const isDefaultWorkflowsExclusivelySelected = useMemo(() => {
68-
return categories.length === 1 && categories.includes('default');
69-
}, [categories]);
80+
return categories.length === 1 && categories.includes('default') && isRecent === undefined;
81+
}, [categories, isRecent]);
82+
83+
const isRecentWorkflowsSelected = useMemo(() => {
84+
return categories.length === 3 && !!isRecent;
85+
}, [categories, isRecent]);
7086

7187
return (
7288
<Flex h="full" minH={0} overflow="hidden" flexDir="column" w={64} gap={1}>
7389
<Flex flexDir="column" w="full" pb={2}>
74-
<Text px={3} py={2} fontSize="md" fontWeight="semibold">
90+
<CategoryButton isSelected={isRecentWorkflowsSelected} onClick={selectRecentWorkflows}>
7591
{t('workflows.recentlyOpened')}
76-
</Text>
77-
<Flex flexDir="column" gap={2} pl={4}>
78-
<RecentWorkflows />
79-
</Flex>
92+
</CategoryButton>
8093
</Flex>
8194
<Flex flexDir="column" w="full" pb={2}>
8295
<CategoryButton isSelected={isYourWorkflowsSelected} onClick={selectYourWorkflows}>
@@ -148,36 +161,6 @@ export const WorkflowLibrarySideNav = () => {
148161
);
149162
};
150163

151-
const recentWorkflowsQueryArg = {
152-
page: 0,
153-
per_page: 5,
154-
order_by: 'opened_at',
155-
direction: 'DESC',
156-
is_recent: true,
157-
} satisfies Parameters<typeof useListWorkflowsQuery>[0];
158-
159-
const RecentWorkflows = memo(() => {
160-
const { t } = useTranslation();
161-
const { data, isLoading } = useListWorkflowsQuery(recentWorkflowsQueryArg);
162-
163-
if (isLoading) {
164-
return <Text variant="subtext">{t('common.loading')}</Text>;
165-
}
166-
167-
if (!data) {
168-
return <Text variant="subtext">{t('workflows.noRecentWorkflows')}</Text>;
169-
}
170-
171-
return (
172-
<>
173-
{data.items.map((workflow) => {
174-
return <RecentWorkflowButton key={workflow.workflow_id} workflow={workflow} />;
175-
})}
176-
</>
177-
);
178-
});
179-
RecentWorkflows.displayName = 'RecentWorkflows';
180-
181164
const RecentWorkflowButton = memo(({ workflow }: { workflow: S['WorkflowRecordListItemWithThumbnailDTO'] }) => {
182165
const loadWorkflow = useLoadWorkflow();
183166
const load = useCallback(() => {

Diff for: invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { EMPTY_ARRAY } from 'app/store/constants';
33
import { useAppSelector } from 'app/store/storeHooks';
44
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
55
import {
6+
selectWorkflowIsRecent,
67
selectWorkflowLibrarySelectedTags,
78
selectWorkflowOrderBy,
89
selectWorkflowOrderDirection,
@@ -26,6 +27,7 @@ const useInfiniteQueryAry = () => {
2627
const direction = useAppSelector(selectWorkflowOrderDirection);
2728
const query = useAppSelector(selectWorkflowSearchTerm);
2829
const tags = useAppSelector(selectWorkflowLibrarySelectedTags);
30+
const isRecent = useAppSelector(selectWorkflowIsRecent);
2931
const [debouncedQuery] = useDebounce(query, 500);
3032

3133
const queryArg = useMemo(() => {
@@ -37,8 +39,9 @@ const useInfiniteQueryAry = () => {
3739
categories,
3840
query: debouncedQuery,
3941
tags: categories.length === 1 && categories.includes('default') ? tags : [],
42+
is_recent: isRecent,
4043
} satisfies Parameters<typeof useListWorkflowsQuery>[0];
41-
}, [orderBy, direction, categories, debouncedQuery, tags]);
44+
}, [orderBy, direction, categories, debouncedQuery, tags, isRecent]);
4245

4346
return queryArg;
4447
};

Diff for: invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx

+25-19
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types'
1313
import { DeleteWorkflow } from './WorkflowLibraryListItemActions/DeleteWorkflow';
1414
import { DownloadWorkflow } from './WorkflowLibraryListItemActions/DownloadWorkflow';
1515
import { EditWorkflow } from './WorkflowLibraryListItemActions/EditWorkflow';
16-
import { SaveWorkflow } from './WorkflowLibraryListItemActions/SaveWorkflow';
1716
import { ViewWorkflow } from './WorkflowLibraryListItemActions/ViewWorkflow';
1817

1918
const IMAGE_THUMBNAIL_SIZE = '80px';
@@ -81,19 +80,32 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
8180
minWidth={IMAGE_THUMBNAIL_SIZE}
8281
borderRadius="base"
8382
/>
84-
<Flex flexDir="column" gap={1} justifyContent="flex-start">
85-
<Flex gap={4} alignItems="center">
86-
<Text noOfLines={2}>{workflow.name}</Text>
83+
<Flex flexDir="column" gap={1} justifyContent="space-between">
84+
<Flex flexDir="column" gap={1}>
85+
<Flex gap={4} alignItems="center">
86+
<Text noOfLines={2}>{workflow.name}</Text>
8787

88-
{isActive && (
89-
<Badge color="invokeBlue.400" borderColor="invokeBlue.700" borderWidth={1} bg="transparent" flexShrink={0}>
90-
{t('workflows.opened')}
91-
</Badge>
92-
)}
88+
{isActive && (
89+
<Badge
90+
color="invokeBlue.400"
91+
borderColor="invokeBlue.700"
92+
borderWidth={1}
93+
bg="transparent"
94+
flexShrink={0}
95+
>
96+
{t('workflows.opened')}
97+
</Badge>
98+
)}
99+
</Flex>
100+
<Text variant="subtext" fontSize="xs" noOfLines={2}>
101+
{workflow.description}
102+
</Text>
93103
</Flex>
94-
<Text variant="subtext" fontSize="xs" noOfLines={2}>
95-
{workflow.description}
96-
</Text>
104+
{workflow.opened_at && (
105+
<Text variant="subtext" fontSize="xs" noOfLines={2} justifySelf="flex-end">
106+
{t('workflows.opened')}: {new Date(workflow.opened_at).toLocaleString()}
107+
</Text>
108+
)}
97109
</Flex>
98110

99111
<Spacer />
@@ -114,13 +126,7 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
114126
right={0}
115127
bottom={0}
116128
>
117-
{workflow.category === 'default' && (
118-
<>
119-
{/* 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" */}
120-
<ViewWorkflow workflowId={workflow.workflow_id} />
121-
<SaveWorkflow workflowId={workflow.workflow_id} />
122-
</>
123-
)}
129+
{workflow.category === 'default' && <ViewWorkflow workflowId={workflow.workflow_id} />}
124130
{workflow.category !== 'default' && (
125131
<>
126132
<EditWorkflow workflowId={workflow.workflow_id} />

Diff for: invokeai/frontend/web/src/features/nodes/store/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ export type WorkflowsState = Omit<WorkflowV3, 'nodes' | 'edges'> & {
3939
searchTerm: string;
4040
orderBy?: WorkflowRecordOrderBy;
4141
orderDirection: SQLiteDirection;
42+
isRecent?: boolean;
4243
formFieldInitialValues: Record<string, StatefulFieldValue>;
4344
};

Diff for: invokeai/frontend/web/src/features/nodes/store/workflowSlice.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const initialWorkflowState: WorkflowState = {
8484
mode: 'view',
8585
formFieldInitialValues: {},
8686
searchTerm: '',
87-
orderBy: 'opened_at', // initial value is decided in component
87+
orderBy: 'opened_at',
8888
orderDirection: 'DESC',
8989
selectedTags: [],
9090
selectedCategories: ['user'],
@@ -120,6 +120,9 @@ export const workflowSlice = createSlice({
120120
state.meta.category = action.payload;
121121
}
122122
},
123+
workflowIsRecentChanged: (state, action: PayloadAction<boolean | undefined>) => {
124+
state.isRecent = action.payload;
125+
},
123126
workflowDescriptionChanged: (state, action: PayloadAction<string>) => {
124127
state.description = action.payload;
125128
state.isTouched = true;
@@ -317,6 +320,7 @@ export const {
317320
workflowSearchTermChanged,
318321
workflowOrderByChanged,
319322
workflowOrderDirectionChanged,
323+
workflowIsRecentChanged,
320324
workflowSelectedCategoriesChanged,
321325
workflowSelectedTagToggled,
322326
workflowSelectedTagsRese,
@@ -385,6 +389,7 @@ export const selectWorkflowIsTouched = createWorkflowSelector((workflow) => work
385389
export const selectWorkflowSearchTerm = createWorkflowSelector((workflow) => workflow.searchTerm);
386390
export const selectWorkflowOrderBy = createWorkflowSelector((workflow) => workflow.orderBy);
387391
export const selectWorkflowOrderDirection = createWorkflowSelector((workflow) => workflow.orderDirection);
392+
export const selectWorkflowIsRecent = createWorkflowSelector((workflow) => workflow.isRecent);
388393
export const selectWorkflowSelectedCategories = createWorkflowSelector((workflow) => workflow.selectedCategories);
389394
export const selectWorkflowDescription = createWorkflowSelector((workflow) => workflow.description);
390395
export const selectWorkflowLibrarySelectedTags = createWorkflowSelector((workflow) => workflow.selectedTags);

0 commit comments

Comments
 (0)