mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
update recent workflows UI
This commit is contained in:
committed by
psychedelicious
parent
df305c0b99
commit
afd894fd04
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 />}
|
||||
/>
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user