fix: Add copy to clipboard option for capture (#5720)

* Add copy to clipboard option for capture

* PR comments

* Added toast feedback and updated labels

---------

Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
This commit is contained in:
Bill Wallace 2026-02-18 19:03:17 -05:00 committed by GitHub
parent c85ca0e177
commit 53c67f3612
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 73 additions and 5 deletions

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { ImageModal, FooterAction } from '@ohif/ui-next';
import { ImageModal, FooterAction, toast } from '@ohif/ui-next';
import { useTranslation } from 'react-i18next';
const MAX_TEXTURE_SIZE = 10000;
const DEFAULT_FILENAME = 'image';
@ -16,6 +16,7 @@ interface ViewportDownloadFormNewProps {
onEnableViewport: (element: HTMLElement) => void;
onDisableViewport: () => void;
onDownload: (filename: string, fileType: string) => void;
onCopyToClipboard: () => void;
warningState: { enabled: boolean; value: string };
}
@ -32,6 +33,7 @@ function ViewportDownloadFormNew({
onEnableViewport,
onDisableViewport,
onDownload,
onCopyToClipboard,
}: ViewportDownloadFormNewProps) {
const [viewportElement, setViewportElement] = useState<HTMLElement | null>(null);
const [showWarningMessage, setShowWarningMessage] = useState(true);
@ -138,13 +140,27 @@ function ViewportDownloadFormNew({
<FooterAction.Secondary onClick={onClose}>
{t('Common:Cancel')}
</FooterAction.Secondary>
<FooterAction.Secondary
onClick={async () => {
try {
await onCopyToClipboard();
toast.success(t('Image copied to clipboard'));
onClose();
} catch (error) {
toast.error(t('Failed to copy image to clipboard'));
console.error('Failed to copy to clipboard:', error);
}
}}
>
{t('Copy to Clipboard')}
</FooterAction.Secondary>
<FooterAction.Primary
onClick={() => {
onDownload(filename || DEFAULT_FILENAME, fileType);
onClose();
}}
>
{t('Common:Save')}
{t('Save Image')}
</FooterAction.Primary>
</FooterAction.Right>
</FooterAction>

View File

@ -118,16 +118,20 @@ const CornerstoneViewportDownloadForm = ({
const downloadViewport = renderingEngine.getViewport(VIEWPORT_ID);
try {
const properties = viewport.getProperties();
if (downloadViewport instanceof StackViewport) {
const imageId = viewport.getCurrentImageId();
const properties = viewport.getProperties();
await downloadViewport.setStack([imageId]);
downloadViewport.setProperties(properties);
} else if (downloadViewport instanceof BaseVolumeViewport) {
const volumeIds = viewport.getAllVolumeIds();
downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
await downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
}
downloadViewport.setProperties(properties);
const viewRef = viewport.getViewReference();
downloadViewport.setViewReference(viewRef);
downloadViewport.render();
if (segmentationRepresentations?.length) {
segmentationRepresentations.forEach(segRepresentation => {
@ -230,6 +234,48 @@ const CornerstoneViewportDownloadForm = ({
downloadUrl(canvas.toDataURL(`image/${fileType}`, 1.0), { filename });
};
const handleCopyToClipboard = async () => {
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${VIEWPORT_ID}"]`
);
if (!divForDownloadViewport) {
console.debug('No viewport found for copy');
return;
}
try {
const canvas = await html2canvas(divForDownloadViewport as HTMLElement);
// Clipboard API only supports PNG format in most browsers
const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob(
blob => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Failed to create blob from canvas'));
}
},
'image/png',
1.0
);
});
// Copy to clipboard using the Clipboard API
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob,
}),
]);
console.log('Image copied to clipboard successfully');
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
throw error;
}
};
const ViewportDownloadFormNew = customizationService.getCustomization(
'ohif.captureViewportModal'
);
@ -247,6 +293,7 @@ const CornerstoneViewportDownloadForm = ({
onEnableViewport={handleEnableViewport}
onDisableViewport={handleDisableViewport}
onDownload={handleDownload}
onCopyToClipboard={handleCopyToClipboard}
warningState={warningState}
/>
);

View File

@ -1,9 +1,13 @@
{
"Copy to Clipboard": "Copy Image",
"File name": "File name",
"Image size": "Image size",
"Image size in pixels": "Image size in pixels",
"Include annotations": "Include annotations",
"Include warning message": "Include warning message",
"Width": "Width",
"Height": "Height"
"Height": "Height",
"Save Image": "Save Image",
"Image copied to clipboard": "Image copied to clipboard",
"Failed to copy image to clipboard": "Failed to copy image to clipboard"
}

View File

@ -1,5 +1,6 @@
{
"Capture": "Capture",
"Copy to Clipboard": "Copier dans le presse-papiers",
"Cancel": "$t(Common:Cancel)",
"Download": "$t(Common:Download)",
"Image size": "Taille de l'image",