feat(ui): copy layer to clipboard

This commit is contained in:
Mary Hipp
2024-09-17 15:12:23 -04:00
committed by psychedelicious
parent bc683f955d
commit 8cd78f7f34
7 changed files with 118 additions and 4 deletions

View File

@@ -1081,6 +1081,7 @@
"importFailed": "Import Failed",
"importSuccessful": "Import Successful",
"invalidUpload": "Invalid Upload",
"layerCopiedToClipboard": "Layer Copied to Clipboard",
"loadedWithWarnings": "Workflow Loaded with Warnings",
"maskSavedAssets": "Mask Saved to Assets",
"maskSentControlnetAssets": "Mask Sent to ControlNet & Assets",
@@ -1101,6 +1102,7 @@
"problemCopyingCanvas": "Problem Copying Canvas",
"problemCopyingCanvasDesc": "Unable to export base layer",
"problemCopyingImage": "Unable to Copy Image",
"problemCopyingLayer": "Unable to Copy Layer",
"problemDownloadingImage": "Unable to Download Image",
"problemDownloadingCanvas": "Problem Downloading Canvas",
"problemDownloadingCanvasDesc": "Unable to export base layer",
@@ -1669,6 +1671,7 @@
"sendToGallery": "Send To Gallery",
"sendToGalleryDesc": "Pressing Invoke generates and saves a unique image to your gallery.",
"sendToCanvas": "Send To Canvas",
"copyToClipboard": "Copy to Clipboard",
"sendToCanvasDesc": "Pressing Invoke stages your work in progress on the canvas.",
"viewProgressInViewer": "View progress and outputs in the <Btn>Image Viewer</Btn>.",
"viewProgressOnCanvas": "View progress and stage outputs on the <Btn>Canvas</Btn>.",

View File

@@ -1,5 +1,6 @@
import { MenuGroup } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityMenuItemsCopy } from 'features/controlLayers/components/common/CanvasEntityMenuItemsCopy';
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
@@ -20,6 +21,7 @@ const CanvasContextMenuSelectedEntityMenuItemsContent = memo(() => {
<MenuGroup title={title}>
{isFilterableEntityIdentifier(entityIdentifier) && <CanvasEntityMenuItemsFilter />}
{isTransformableEntityIdentifier(entityIdentifier) && <CanvasEntityMenuItemsTransform />}
<CanvasEntityMenuItemsCopy />
<CanvasEntityMenuItemsDelete />
</MenuGroup>
);

View File

@@ -0,0 +1,34 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useCopyLayerToClipboard } from 'features/controlLayers/hooks/useCopyLayerToClipboard';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyBold } from 'react-icons/pi';
export const CanvasEntityCopyToClipboard = memo(() => {
const { t } = useTranslation();
const entityIdentifier = useEntityIdentifierContext();
const adapter = useEntityAdapterSafe(entityIdentifier);
const isBusy = useCanvasIsBusy();
const copyLayerToClipboard = useCopyLayerToClipboard();
const onClick = useCallback(() => {
copyLayerToClipboard(adapter);
}, [copyLayerToClipboard, adapter]);
return (
<IconButton
size="sm"
aria-label={t('unifiedCanvas.copyToClipboard')}
tooltip={t('unifiedCanvas.copyToClipboard')}
variant="link"
alignSelf="stretch"
icon={<PiCopyBold />}
onClick={onClick}
isDisabled={isBusy}
/>
);
});
CanvasEntityCopyToClipboard.displayName = 'CanvasEntityCopyToClipboard';

View File

@@ -6,11 +6,14 @@ import { CanvasEntityIsLockedToggle } from 'features/controlLayers/components/co
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { memo } from 'react';
import { CanvasEntityCopyToClipboard } from './CanvasEntityCopyToClipboard';
export const CanvasEntityHeaderCommonActions = memo(() => {
const entityIdentifier = useEntityIdentifierContext();
return (
<Flex alignSelf="stretch">
<CanvasEntityCopyToClipboard />
<CanvasEntityIsBookmarkedForQuickSwitchToggle />
{entityIdentifier.type !== 'reference_image' && <CanvasEntityIsLockedToggle />}
<CanvasEntityEnabledToggle />

View File

@@ -0,0 +1,28 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useCopyLayerToClipboard } from 'features/controlLayers/hooks/useCopyLayerToClipboard';
import { useIsEntityInteractable } from 'features/controlLayers/hooks/useEntityIsInteractable';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyBold } from 'react-icons/pi';
export const CanvasEntityMenuItemsCopy = memo(() => {
const { t } = useTranslation();
const entityIdentifier = useEntityIdentifierContext();
const adapter = useEntityAdapterSafe(entityIdentifier);
const isInteractable = useIsEntityInteractable(entityIdentifier);
const copyLayerToClipboard = useCopyLayerToClipboard();
const onClick = useCallback(() => {
copyLayerToClipboard(adapter);
}, [copyLayerToClipboard, adapter]);
return (
<MenuItem onClick={onClick} icon={<PiCopyBold />} isDisabled={!isInteractable}>
{t('controlLayers.copyToClipboard')}
</MenuItem>
);
});
CanvasEntityMenuItemsCopy.displayName = 'CanvasEntityMenuItemsCopy';

View File

@@ -0,0 +1,44 @@
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
import type { CanvasEntityAdapterInpaintMask } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterInpaintMask';
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
import type { CanvasEntityAdapterRegionalGuidance } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRegionalGuidance';
import { canvasToBlob } from 'features/controlLayers/konva/util';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const useCopyLayerToClipboard = () => {
const { t } = useTranslation();
const copyLayerToCipboard = useCallback(
async (
adapter:
| CanvasEntityAdapterRasterLayer
| CanvasEntityAdapterControlLayer
| CanvasEntityAdapterInpaintMask
| CanvasEntityAdapterRegionalGuidance
| null
) => {
if (!adapter) {
return;
}
try {
const canvas = adapter.getCanvas();
const blob = await canvasToBlob(canvas);
copyBlobToClipboard(blob);
toast({
status: 'info',
title: t('toast.layerCopiedToClipboard'),
});
} catch (error) {
toast({
status: 'error',
title: t('toast.problemCopyingLayer'),
});
}
},
[t]
);
return copyLayerToCipboard;
};

View File

@@ -299,11 +299,11 @@ export type EntityIdentifierPayload<
U extends CanvasEntityType = CanvasEntityType,
> = T extends void
? {
entityIdentifier: CanvasEntityIdentifier<U>;
}
entityIdentifier: CanvasEntityIdentifier<U>;
}
: {
entityIdentifier: CanvasEntityIdentifier<U>;
} & T;
entityIdentifier: CanvasEntityIdentifier<U>;
} & T;
export type EntityMovedPayload = EntityIdentifierPayload<{ position: Coordinate }>;
export type EntityBrushLineAddedPayload = EntityIdentifierPayload<{ brushLine: CanvasBrushLineState }>;