mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): add option to copy share link for workflows if projectURL is defined (commercial)
This commit is contained in:
committed by
psychedelicious
parent
8544ba3798
commit
3c46522595
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
Reference in New Issue
Block a user