feat(ui): add option to copy share link for workflows if projectURL is defined (commercial)

This commit is contained in:
Mary Hipp
2024-10-09 10:19:35 -04:00
committed by psychedelicious
parent 8544ba3798
commit 3c46522595
4 changed files with 143 additions and 22 deletions

View File

@@ -90,6 +90,7 @@
"batch": "Batch Manager",
"beta": "Beta",
"cancel": "Cancel",
"close": "Close",
"copy": "Copy",
"copyError": "$t(gallery.copy) Error",
"on": "On",
@@ -1125,6 +1126,7 @@
"canceled": "Processing Canceled",
"connected": "Connected to Server",
"imageCopied": "Image Copied",
"linkCopied": "Link Copied",
"unableToLoadImage": "Unable to Load Image",
"unableToLoadImageMetadata": "Unable to Load Image Metadata",
"unableToLoadStylePreset": "Unable to Load Style Preset",
@@ -1559,7 +1561,12 @@
"loadFromGraph": "Load Workflow from Graph",
"convertGraph": "Convert Graph",
"loadWorkflow": "$t(common.load) Workflow",
"autoLayout": "Auto Layout"
"autoLayout": "Auto Layout",
"edit": "Edit",
"download": "Download",
"copyShareLink": "Copy Share Link",
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",
"delete": "Delete"
},
"controlLayers": {
"regional": "Regional",

View File

@@ -0,0 +1,64 @@
import {
Button,
Flex,
Heading,
IconButton,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $projectUrl } from 'app/store/nanostores/projectId';
import { useCopyWorkflowLinkModal } from 'features/nodes/hooks/useCopyWorkflowLinkModal';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyBold } from 'react-icons/pi';
export const CopyWorkflowLinkModal = ({ workflowId, workflowName }: { workflowId: string; workflowName: string }) => {
const projectUrl = useStore($projectUrl);
const { t } = useTranslation();
const workflowLink = useMemo(() => {
return `${projectUrl}/studio?selectedWorkflowId=${workflowId}`;
}, [projectUrl, workflowId]);
const { isOpen, onClose } = useCopyWorkflowLinkModal();
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(workflowLink);
toast({
status: 'success',
title: t('toast.linkCopied'),
});
onClose();
}, [workflowLink, t, onClose]);
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered size="lg" useInert={false}>
<ModalContent>
<ModalHeader>
<Flex flexDir="column" gap={2}>
<Heading fontSize="xl">{t('workflows.copyShareLinkForWorkflow')}</Heading>
<Text fontSize="md">{workflowName}</Text>
</Flex>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex layerStyle="third" p={5} borderRadius="base">
<Text fontWeight="semibold">{workflowLink}</Text>
<IconButton aria-label="Copy Link" icon={<PiCopyBold />} onClick={handleCopy} />
</Flex>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>{t('common.close')}</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

View File

@@ -8,9 +8,12 @@ import {
Tooltip,
useDisclosure,
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { EMPTY_OBJECT } from 'app/store/constants';
import { $projectUrl } from 'app/store/nanostores/projectId';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import dateFormat, { masks } from 'dateformat';
import { useCopyWorkflowLinkModal } from 'features/nodes/hooks/useCopyWorkflowLinkModal';
import { $isWorkflowListMenuIsOpen } from 'features/nodes/store/workflowListMenu';
import { selectWorkflowId, workflowModeChanged } from 'features/nodes/store/workflowSlice';
import { useDeleteLibraryWorkflow } from 'features/workflowLibrary/hooks/useDeleteLibraryWorkflow';
@@ -19,15 +22,17 @@ import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/use
import type { MouseEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDownloadSimpleBold, PiPencilBold, PiTrashBold } from 'react-icons/pi';
import { PiDownloadSimpleBold, PiPencilBold, PiShareFatBold, PiTrashBold } from 'react-icons/pi';
import type { WorkflowRecordListItemDTO } from 'services/api/types';
import { CopyWorkflowLinkModal } from './CopyWorkflowLinkModal';
import { WorkflowListItemTooltip } from './WorkflowListItemTooltip';
export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListItemDTO }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { t } = useTranslation();
const dispatch = useAppDispatch();
const projectUrl = useStore($projectUrl);
const [isHovered, setIsHovered] = useState(false);
@@ -42,6 +47,8 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
const workflowId = useAppSelector(selectWorkflowId);
const downloadWorkflow = useDownloadWorkflow();
const { onOpen: onOpenCopyWorkflowLinkModal } = useCopyWorkflowLinkModal();
const { deleteWorkflow, deleteWorkflowResult } = useDeleteLibraryWorkflow(EMPTY_OBJECT);
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow({
onSuccess: () => $isWorkflowListMenuIsOpen.set(false),
@@ -75,6 +82,14 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
[onOpen]
);
const handleClickShare = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
onOpenCopyWorkflowLinkModal();
},
[onOpenCopyWorkflowLinkModal]
);
return (
<>
<Flex
@@ -118,31 +133,48 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
<Spacer />
<Flex alignItems="center" gap={1} opacity={isHovered ? 1 : 0}>
<IconButton
size="sm"
variant="ghost"
aria-label="Edit"
onClick={handleClickEdit}
isLoading={deleteWorkflowResult.isLoading}
icon={<PiPencilBold />}
/>
<IconButton
size="sm"
variant="ghost"
aria-label="Download"
onClick={downloadWorkflow}
icon={<PiDownloadSimpleBold />}
/>
{workflow.category !== 'default' && (
<Tooltip label={t('workflows.edit')}>
<IconButton
size="sm"
variant="ghost"
aria-label={t('stylePresets.deleteTemplate')}
onClick={handleClickDelete}
aria-label={t('workflows.edit')}
onClick={handleClickEdit}
isLoading={deleteWorkflowResult.isLoading}
colorScheme="error"
icon={<PiTrashBold />}
icon={<PiPencilBold />}
/>
</Tooltip>
<Tooltip label={t('workflows.download')}>
<IconButton
size="sm"
variant="ghost"
aria-label={t('workflows.download')}
onClick={downloadWorkflow}
icon={<PiDownloadSimpleBold />}
/>
</Tooltip>
{!!projectUrl && workflow.workflow_id && (
<Tooltip label={t('workflows.copyShareLink')}>
<IconButton
size="sm"
variant="ghost"
aria-label={t('workflows.copyShareLink')}
onClick={handleClickShare}
icon={<PiShareFatBold />}
/>
</Tooltip>
)}
{workflow.category !== 'default' && (
<Tooltip label={t('workflows.delete')}>
<IconButton
size="sm"
variant="ghost"
aria-label={t('workflows.delete')}
onClick={handleClickDelete}
isLoading={deleteWorkflowResult.isLoading}
colorScheme="error"
icon={<PiTrashBold />}
/>
</Tooltip>
)}
</Flex>
</Flex>
@@ -157,6 +189,7 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
>
<p>{t('workflows.deleteWorkflow2')}</p>
</ConfirmationAlertDialog>
<CopyWorkflowLinkModal workflowId={workflow.workflow_id} workflowName={workflow.name} />
</>
);
};

View File

@@ -0,0 +1,17 @@
import { useStore } from '@nanostores/react';
import { atom } from 'nanostores';
import { useCallback } from 'react';
const $isOpen = atom<boolean>(false);
export const useCopyWorkflowLinkModal = () => {
const isOpen = useStore($isOpen);
const onOpen = useCallback(() => {
$isOpen.set(true);
}, []);
const onClose = useCallback(() => {
$isOpen.set(false);
}, []);
return { isOpen, onOpen, onClose };
};