Skip to content

Commit 3c46522

Browse files
Mary Hipppsychedelicious
authored andcommitted
feat(ui): add option to copy share link for workflows if projectURL is defined (commercial)
1 parent 8544ba3 commit 3c46522

File tree

4 files changed

+143
-22
lines changed

4 files changed

+143
-22
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"batch": "Batch Manager",
9191
"beta": "Beta",
9292
"cancel": "Cancel",
93+
"close": "Close",
9394
"copy": "Copy",
9495
"copyError": "$t(gallery.copy) Error",
9596
"on": "On",
@@ -1125,6 +1126,7 @@
11251126
"canceled": "Processing Canceled",
11261127
"connected": "Connected to Server",
11271128
"imageCopied": "Image Copied",
1129+
"linkCopied": "Link Copied",
11281130
"unableToLoadImage": "Unable to Load Image",
11291131
"unableToLoadImageMetadata": "Unable to Load Image Metadata",
11301132
"unableToLoadStylePreset": "Unable to Load Style Preset",
@@ -1559,7 +1561,12 @@
15591561
"loadFromGraph": "Load Workflow from Graph",
15601562
"convertGraph": "Convert Graph",
15611563
"loadWorkflow": "$t(common.load) Workflow",
1562-
"autoLayout": "Auto Layout"
1564+
"autoLayout": "Auto Layout",
1565+
"edit": "Edit",
1566+
"download": "Download",
1567+
"copyShareLink": "Copy Share Link",
1568+
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",
1569+
"delete": "Delete"
15631570
},
15641571
"controlLayers": {
15651572
"regional": "Regional",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {
2+
Button,
3+
Flex,
4+
Heading,
5+
IconButton,
6+
Modal,
7+
ModalBody,
8+
ModalCloseButton,
9+
ModalContent,
10+
ModalFooter,
11+
ModalHeader,
12+
Text,
13+
} from '@invoke-ai/ui-library';
14+
import { useStore } from '@nanostores/react';
15+
import { $projectUrl } from 'app/store/nanostores/projectId';
16+
import { useCopyWorkflowLinkModal } from 'features/nodes/hooks/useCopyWorkflowLinkModal';
17+
import { toast } from 'features/toast/toast';
18+
import { useCallback, useMemo } from 'react';
19+
import { useTranslation } from 'react-i18next';
20+
import { PiCopyBold } from 'react-icons/pi';
21+
22+
export const CopyWorkflowLinkModal = ({ workflowId, workflowName }: { workflowId: string; workflowName: string }) => {
23+
const projectUrl = useStore($projectUrl);
24+
const { t } = useTranslation();
25+
26+
const workflowLink = useMemo(() => {
27+
return `${projectUrl}/studio?selectedWorkflowId=${workflowId}`;
28+
}, [projectUrl, workflowId]);
29+
30+
const { isOpen, onClose } = useCopyWorkflowLinkModal();
31+
32+
const handleCopy = useCallback(() => {
33+
navigator.clipboard.writeText(workflowLink);
34+
toast({
35+
status: 'success',
36+
title: t('toast.linkCopied'),
37+
});
38+
onClose();
39+
}, [workflowLink, t, onClose]);
40+
41+
return (
42+
<Modal isOpen={isOpen} onClose={onClose} isCentered size="lg" useInert={false}>
43+
<ModalContent>
44+
<ModalHeader>
45+
<Flex flexDir="column" gap={2}>
46+
<Heading fontSize="xl">{t('workflows.copyShareLinkForWorkflow')}</Heading>
47+
<Text fontSize="md">{workflowName}</Text>
48+
</Flex>
49+
</ModalHeader>
50+
<ModalCloseButton />
51+
<ModalBody>
52+
<Flex layerStyle="third" p={5} borderRadius="base">
53+
<Text fontWeight="semibold">{workflowLink}</Text>
54+
<IconButton aria-label="Copy Link" icon={<PiCopyBold />} onClick={handleCopy} />
55+
</Flex>
56+
</ModalBody>
57+
58+
<ModalFooter>
59+
<Button onClick={onClose}>{t('common.close')}</Button>
60+
</ModalFooter>
61+
</ModalContent>
62+
</Modal>
63+
);
64+
};

invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/WorkflowListItem.tsx

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import {
88
Tooltip,
99
useDisclosure,
1010
} from '@invoke-ai/ui-library';
11+
import { useStore } from '@nanostores/react';
1112
import { EMPTY_OBJECT } from 'app/store/constants';
13+
import { $projectUrl } from 'app/store/nanostores/projectId';
1214
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
1315
import dateFormat, { masks } from 'dateformat';
16+
import { useCopyWorkflowLinkModal } from 'features/nodes/hooks/useCopyWorkflowLinkModal';
1417
import { $isWorkflowListMenuIsOpen } from 'features/nodes/store/workflowListMenu';
1518
import { selectWorkflowId, workflowModeChanged } from 'features/nodes/store/workflowSlice';
1619
import { useDeleteLibraryWorkflow } from 'features/workflowLibrary/hooks/useDeleteLibraryWorkflow';
@@ -19,15 +22,17 @@ import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/use
1922
import type { MouseEvent } from 'react';
2023
import { useCallback, useMemo, useState } from 'react';
2124
import { useTranslation } from 'react-i18next';
22-
import { PiDownloadSimpleBold, PiPencilBold, PiTrashBold } from 'react-icons/pi';
25+
import { PiDownloadSimpleBold, PiPencilBold, PiShareFatBold, PiTrashBold } from 'react-icons/pi';
2326
import type { WorkflowRecordListItemDTO } from 'services/api/types';
2427

28+
import { CopyWorkflowLinkModal } from './CopyWorkflowLinkModal';
2529
import { WorkflowListItemTooltip } from './WorkflowListItemTooltip';
2630

2731
export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListItemDTO }) => {
2832
const { isOpen, onOpen, onClose } = useDisclosure();
2933
const { t } = useTranslation();
3034
const dispatch = useAppDispatch();
35+
const projectUrl = useStore($projectUrl);
3136

3237
const [isHovered, setIsHovered] = useState(false);
3338

@@ -42,6 +47,8 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
4247
const workflowId = useAppSelector(selectWorkflowId);
4348
const downloadWorkflow = useDownloadWorkflow();
4449

50+
const { onOpen: onOpenCopyWorkflowLinkModal } = useCopyWorkflowLinkModal();
51+
4552
const { deleteWorkflow, deleteWorkflowResult } = useDeleteLibraryWorkflow(EMPTY_OBJECT);
4653
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow({
4754
onSuccess: () => $isWorkflowListMenuIsOpen.set(false),
@@ -75,6 +82,14 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
7582
[onOpen]
7683
);
7784

85+
const handleClickShare = useCallback(
86+
(e: MouseEvent<HTMLButtonElement>) => {
87+
e.stopPropagation();
88+
onOpenCopyWorkflowLinkModal();
89+
},
90+
[onOpenCopyWorkflowLinkModal]
91+
);
92+
7893
return (
7994
<>
8095
<Flex
@@ -118,31 +133,48 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
118133
<Spacer />
119134

120135
<Flex alignItems="center" gap={1} opacity={isHovered ? 1 : 0}>
121-
<IconButton
122-
size="sm"
123-
variant="ghost"
124-
aria-label="Edit"
125-
onClick={handleClickEdit}
126-
isLoading={deleteWorkflowResult.isLoading}
127-
icon={<PiPencilBold />}
128-
/>
129-
<IconButton
130-
size="sm"
131-
variant="ghost"
132-
aria-label="Download"
133-
onClick={downloadWorkflow}
134-
icon={<PiDownloadSimpleBold />}
135-
/>
136-
{workflow.category !== 'default' && (
136+
<Tooltip label={t('workflows.edit')}>
137137
<IconButton
138138
size="sm"
139139
variant="ghost"
140-
aria-label={t('stylePresets.deleteTemplate')}
141-
onClick={handleClickDelete}
140+
aria-label={t('workflows.edit')}
141+
onClick={handleClickEdit}
142142
isLoading={deleteWorkflowResult.isLoading}
143-
colorScheme="error"
144-
icon={<PiTrashBold />}
143+
icon={<PiPencilBold />}
145144
/>
145+
</Tooltip>
146+
<Tooltip label={t('workflows.download')}>
147+
<IconButton
148+
size="sm"
149+
variant="ghost"
150+
aria-label={t('workflows.download')}
151+
onClick={downloadWorkflow}
152+
icon={<PiDownloadSimpleBold />}
153+
/>
154+
</Tooltip>
155+
{!!projectUrl && workflow.workflow_id && (
156+
<Tooltip label={t('workflows.copyShareLink')}>
157+
<IconButton
158+
size="sm"
159+
variant="ghost"
160+
aria-label={t('workflows.copyShareLink')}
161+
onClick={handleClickShare}
162+
icon={<PiShareFatBold />}
163+
/>
164+
</Tooltip>
165+
)}
166+
{workflow.category !== 'default' && (
167+
<Tooltip label={t('workflows.delete')}>
168+
<IconButton
169+
size="sm"
170+
variant="ghost"
171+
aria-label={t('workflows.delete')}
172+
onClick={handleClickDelete}
173+
isLoading={deleteWorkflowResult.isLoading}
174+
colorScheme="error"
175+
icon={<PiTrashBold />}
176+
/>
177+
</Tooltip>
146178
)}
147179
</Flex>
148180
</Flex>
@@ -157,6 +189,7 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
157189
>
158190
<p>{t('workflows.deleteWorkflow2')}</p>
159191
</ConfirmationAlertDialog>
192+
<CopyWorkflowLinkModal workflowId={workflow.workflow_id} workflowName={workflow.name} />
160193
</>
161194
);
162195
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useStore } from '@nanostores/react';
2+
import { atom } from 'nanostores';
3+
import { useCallback } from 'react';
4+
5+
const $isOpen = atom<boolean>(false);
6+
7+
export const useCopyWorkflowLinkModal = () => {
8+
const isOpen = useStore($isOpen);
9+
const onOpen = useCallback(() => {
10+
$isOpen.set(true);
11+
}, []);
12+
const onClose = useCallback(() => {
13+
$isOpen.set(false);
14+
}, []);
15+
16+
return { isOpen, onOpen, onClose };
17+
};

0 commit comments

Comments
 (0)