Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1985,7 +1985,7 @@
"loadWorkflow": "$t(common.load) Workflow",
"autoLayout": "Auto Layout",
"edit": "Edit",
"view": "View",
"view": "Load",
"download": "Download",
"copyShareLink": "Copy Share Link",
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const DeleteWorkflow = ({ workflowId }: { workflowId: string }) => {
<Tooltip label={t('workflows.delete')} closeOnScroll>
<IconButton
size="sm"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.delete')}
onClick={handleClickDelete}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const DownloadWorkflow = ({ workflowId }: { workflowId: string }) => {
<Tooltip label={t('workflows.download')} closeOnScroll>
<IconButton
size="sm"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.download')}
onClick={handleClickDownload}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const EditWorkflow = ({ workflowId }: { workflowId: string }) => {
<Tooltip label={t('workflows.edit')} closeOnScroll>
<IconButton
size="sm"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.edit')}
onClick={handleClickEdit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/L
import type { MouseEvent } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiEyeBold } from 'react-icons/pi';
import { PiCheckBold } from 'react-icons/pi';

export const ViewWorkflow = ({ workflowId }: { workflowId: string }) => {
export const LoadWorkflow = ({ workflowId }: { workflowId: string }) => {
const dispatch = useAppDispatch();
const loadWorkflowWithDialog = useLoadWorkflowWithDialog();
const { t } = useTranslation();
Expand All @@ -30,11 +30,11 @@ export const ViewWorkflow = ({ workflowId }: { workflowId: string }) => {
<Tooltip label={t('workflows.view')} closeOnScroll>
<IconButton
size="sm"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.view')}
onClick={handleClickLoad}
icon={<PiEyeBold />}
icon={<PiCheckBold />}
/>
</Tooltip>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const LockedWorkflowIcon = memo(() => {
<IconButton
size="sm"
cursor="not-allowed"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.builder.publishedWorkflowsLocked')}
icon={<PiLockBold />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ShareWorkflowButton = memo(({ workflow }: { workflow: WorkflowRecor
<Tooltip label={t('workflows.copyShareLink')} closeOnScroll>
<IconButton
size="sm"
variant="link"
variant="ghost"
alignSelf="stretch"
aria-label={t('workflows.copyShareLink')}
onClick={handleClickShare}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const WorkflowLibraryModal = memo(() => {
maxW="calc(100% - var(--invoke-sizes-40))"
h="calc(100% - var(--invoke-sizes-40))"
maxH="calc(100% - var(--invoke-sizes-40))"
bg="base.850"
>
<ModalHeader>{t('workflows.workflowLibrary')}</ModalHeader>
<ModalCloseButton />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ButtonProps, CheckboxProps } from '@invoke-ai/ui-library';
import type { ButtonProps, CheckboxProps, SystemStyleObject } from '@invoke-ai/ui-library';
import {
Box,
Button,
Expand Down Expand Up @@ -43,13 +43,13 @@ export const WorkflowLibrarySideNav = () => {
const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);

return (
<Flex h="full" minH={0} overflow="hidden" flexDir="column" w={64} gap={0}>
<Flex flexDir="column" w="full" pb={2} gap={2}>
<Flex h="full" minH={0} overflow="hidden" flexDir="column" w={64}>
<Flex flexDir="column" w="full">
<WorkflowLibraryViewButton view="recent">{t('workflows.recentlyOpened')}</WorkflowLibraryViewButton>
<WorkflowLibraryViewButton view="yours">{t('workflows.yourWorkflows')}</WorkflowLibraryViewButton>
{categoryOptions.includes('project') && (
<Collapse in={view === 'yours' || view === 'shared' || view === 'private'}>
<Flex flexDir="column" gap={2} pl={4} pt={2}>
<Flex flexDir="column" pl={4}>
<WorkflowLibraryViewButton size="sm" view="private">
{t('workflows.private')}
</WorkflowLibraryViewButton>
Expand All @@ -64,7 +64,7 @@ export const WorkflowLibrarySideNav = () => {
<WorkflowLibraryViewButton view="published">{t('workflows.published')}</WorkflowLibraryViewButton>
)}
</Flex>
<Flex h="full" minH={0} overflow="hidden" flexDir="column">
<Flex w="full" h="full" minH={0} flexDir="column">
<BrowseWorkflowsButton />
<DefaultsViewCheckboxesCollapsible />
</Flex>
Expand All @@ -87,18 +87,17 @@ const BrowseWorkflowsButton = memo(() => {
if (view === 'defaults' && selectedTags.length > 0) {
return (
<ButtonGroup>
<WorkflowLibraryViewButton view="defaults" w="auto">
{t('workflows.browseWorkflows')}
</WorkflowLibraryViewButton>
<WorkflowLibraryViewButton view="defaults">{t('workflows.browseWorkflows')}</WorkflowLibraryViewButton>
<Tooltip label={t('workflows.deselectAll')}>
<IconButton
onClick={resetTags}
size="md"
aria-label={t('workflows.deselectAll')}
icon={<PiArrowCounterClockwiseBold size={12} />}
variant="ghost"
bg="base.700"
bg="base.750"
color="base.50"
flexShrink={0}
/>
</Tooltip>
</ButtonGroup>
Expand Down Expand Up @@ -193,17 +192,34 @@ const WorkflowLibraryViewButton = memo(({ view, ...rest }: ButtonProps & { view:
dispatch(workflowLibraryViewChanged(view));
}, [dispatch, view]);

const workflowLibraryButtonSx: SystemStyleObject = {
position: 'relative',
_after: {
content: '""',
position: 'absolute',
insetY: 2,
left: 1,
w: 0.5,
rounded: 'sm',
bg: selectedView === view ? 'invokeBlue.300' : 'transparent',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using a pseudo-element here, how about a border? Regardless I would have the blue active state be full height and flush. Here are some screenshot of the different states - the smaller elements when team features are enabled looks better with the blue line flush IMO

Screenshot 2025-09-17 at 9 30 31 AM Screenshot 2025-09-17 at 9 30 20 AM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did it this way to be more consistent with the model manager active state, also using a full height border would taint the border radius, no?

},
...(selectedView === view
? {
bg: 'base.750',
color: 'base.50',
}
: {}),
};

return (
<Button
variant="ghost"
justifyContent="flex-start"
size="md"
flexShrink={0}
w="full"
onClick={onClick}
sx={workflowLibraryButtonSx}
{...rest}
bg={selectedView === view ? 'base.700' : undefined}
color={selectedView === view ? 'base.50' : undefined}
/>
);
});
Expand All @@ -222,7 +238,7 @@ const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory })
<Text fontWeight="semibold" color="base.300" flexShrink={0}>
{t(tagCategory.categoryTKey)}
</Text>
<Flex flexDir="column" gap={2} pl={4}>
<Flex flexDir="column">
{tagCategory.tags.map((tag) => (
<TagCheckbox key={tag.label} tag={tag} />
))}
Expand All @@ -247,18 +263,31 @@ const TagCheckbox = memo(({ tag, ...rest }: CheckboxProps & { tag: { label: stri
return null;
}

const tagCheckbox: SystemStyleObject = {
py: 1,
px: 2,
transition: 'background-color 0.1s',
rounded: 'base',
_hover: {
bg: 'base.750',
},
_active: {
bg: 'transparent',
},
flexShrink: 0,
};

return (
<Flex alignItems="center" gap={2}>
<Checkbox isChecked={isChecked} onChange={onChange} {...rest} flexShrink={0} />
<Text>{`${tag.label} (${count})`}</Text>
<Checkbox isChecked={isChecked} onChange={onChange} sx={tagCheckbox} {...rest}>
<Text>{`${t(tag.label)} (${count})`}</Text>
{tag.recommended && (
<Tooltip label={t('workflows.recommended')}>
<Box as="span" lineHeight={0}>
<Icon as={PiStarFill} boxSize={4} fill="invokeYellow.500" />
</Box>
</Tooltip>
)}
</Flex>
</Checkbox>
);
});
TagCheckbox.displayName = 'TagCheckbox';
Original file line number Diff line number Diff line change
Expand Up @@ -168,25 +168,6 @@ const WorkflowListContent = memo(
fetchNextPage();
}, [hasNextPage, isFetching, fetchNextPage]);

// // TODO(psyche): this causes an infinite loop, the scrollIntoView triggers the onScroll which triggers the
// // fetchNextPage which triggers the scrollIntoView again...
// useEffect(() => {
// const el = ref.current;
// if (!el) {
// return;
// }

// const observer = new MutationObserver(() => {
// el.querySelector(':scope > :last-child')?.scrollIntoView({ behavior: 'smooth' });
// });

// observer.observe(el, { childList: true });

// return () => {
// observer.disconnect();
// };
// }, []);

return (
<Flex flexDir="column" gap={4} flex={1} minH={0}>
<Grid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,26 @@ import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types'
import { DeleteWorkflow } from './WorkflowLibraryListItemActions/DeleteWorkflow';
import { DownloadWorkflow } from './WorkflowLibraryListItemActions/DownloadWorkflow';
import { EditWorkflow } from './WorkflowLibraryListItemActions/EditWorkflow';
import { ViewWorkflow } from './WorkflowLibraryListItemActions/ViewWorkflow';
import { LoadWorkflow } from './WorkflowLibraryListItemActions/LoadWorkflow';

const IMAGE_THUMBNAIL_SIZE = '108px';
const IMAGE_THUMBNAIL_SIZE = '92px';
const FALLBACK_ICON_SIZE = '32px';

const WORKFLOW_ACTION_BUTTONS_CN = 'workflow-action-buttons';

const sx: SystemStyleObject = {
const workflowListItemSx: SystemStyleObject = {
position: 'relative',
cursor: 'pointer',
role: 'button',
bg: 'base.850',
rounded: 'base',
w: 'full',
alignItems: 'stretch',
gap: 2,
borderWidth: 1,
borderColor: 'base.750',
_hover: {
bg: 'base.700',
bg: 'base.800',
[`& .${WORKFLOW_ACTION_BUTTONS_CN}`]: {
display: 'flex',
},
Expand Down Expand Up @@ -52,17 +62,7 @@ export const WorkflowListItem = memo(({ workflow }: { workflow: WorkflowRecordLi
}, [dispatch, loadWorkflowWithDialog, workflow.workflow_id]);

return (
<Flex
position="relative"
role="button"
onClick={handleClickLoad}
bg="base.750"
borderRadius="base"
w="full"
alignItems="stretch"
sx={sx}
gap={2}
>
<Flex onClick={handleClickLoad} sx={workflowListItemSx}>
<Flex p={2} pr={0}>
<Image
src={workflow.thumbnail_url ?? undefined}
Expand All @@ -74,7 +74,7 @@ export const WorkflowListItem = memo(({ workflow }: { workflow: WorkflowRecordLi
width={IMAGE_THUMBNAIL_SIZE}
minHeight={IMAGE_THUMBNAIL_SIZE}
minWidth={IMAGE_THUMBNAIL_SIZE}
borderRadius="base"
borderRadius="sm"
/>
</Flex>
<Flex flexDir="column" gap={1} justifyContent="space-between" w="full">
Expand Down Expand Up @@ -121,19 +121,19 @@ export const WorkflowListItem = memo(({ workflow }: { workflow: WorkflowRecordLi
)}
</Flex>
</Flex>
<Text variant="subtext" fontSize="xs" noOfLines={3}>
<Text variant="subtext" fontSize="sm" noOfLines={3}>
{workflow.description}
</Text>
</Flex>
<Flex className={WORKFLOW_ACTION_BUTTONS_CN} alignItems="center" display="none" h={8}>
<Flex className={WORKFLOW_ACTION_BUTTONS_CN} alignItems="center" display="none" pr={1} py={1}>
{workflow.opened_at && (
<Text variant="subtext" fontSize="xs" noOfLines={1} justifySelf="flex-end" pb={0.5}>
{t('workflows.opened')} {new Date(workflow.opened_at).toLocaleString()}
</Text>
)}
<Spacer />
{workflow.category === 'default' && !workflow.is_published && (
<ViewWorkflow workflowId={workflow.workflow_id} />
<LoadWorkflow workflowId={workflow.workflow_id} />
)}
{workflow.category !== 'default' && !workflow.is_published && (
<>
Expand All @@ -157,7 +157,7 @@ const UserThumbnailFallback = memo(() => {
height={IMAGE_THUMBNAIL_SIZE}
minWidth={IMAGE_THUMBNAIL_SIZE}
bg="base.600"
borderRadius="base"
borderRadius="sm"
alignItems="center"
justifyContent="center"
opacity={0.3}
Expand All @@ -174,7 +174,7 @@ const DefaultThumbnailFallback = memo(() => {
height={IMAGE_THUMBNAIL_SIZE}
minWidth={IMAGE_THUMBNAIL_SIZE}
bg="base.600"
borderRadius="base"
borderRadius="sm"
alignItems="center"
justifyContent="center"
opacity={0.3}
Expand Down
Loading