enable copying svg cell output (#203843)

* find the correct svg element and write it to the clipboard

* add context to allow context menu

* simplify selection

* better logs for error states
This commit is contained in:
Aaron Munger 2024-01-31 06:18:37 -08:00 committed by GitHub
parent 6aad7f2ec9
commit 7a56623b82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 12 deletions

View file

@ -113,7 +113,7 @@
"webview/context": [
{
"command": "notebook.cellOutput.copy",
"when": "webviewId == 'notebook.output' && webviewSection == 'image'"
"when": "webviewId == 'notebook.output'"
}
]
}

View file

@ -38,7 +38,11 @@ function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable
if (alt) {
image.alt = alt;
}
image.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', outputId: outputInfo.id, 'preventDefaultContextMenuItems': true }));
image.setAttribute('data-vscode-context', JSON.stringify({
webviewSection: 'image',
outputId: outputInfo.id,
'preventDefaultContextMenuItems': true
}));
const display = document.createElement('div');
display.classList.add('display');
display.appendChild(image);
@ -78,7 +82,7 @@ function getAltText(outputInfo: OutputItem) {
return undefined;
}
function injectTitleForSvg(outputInfo: OutputItem, element: HTMLElement) {
function fixUpSvgElement(outputInfo: OutputItem, element: HTMLElement) {
if (outputInfo.mime.indexOf('svg') > -1) {
const svgElement = element.querySelector('svg');
const altText = getAltText(outputInfo);
@ -87,6 +91,16 @@ function injectTitleForSvg(outputInfo: OutputItem, element: HTMLElement) {
title.innerText = altText;
svgElement.prepend(title);
}
if (svgElement) {
svgElement.classList.add('output-image');
svgElement.setAttribute('data-vscode-context', JSON.stringify({
webviewSection: 'image',
outputId: outputInfo.id,
'preventDefaultContextMenuItems': true
}));
}
}
}
@ -96,7 +110,7 @@ async function renderHTML(outputInfo: OutputItem, container: HTMLElement, signal
const htmlContent = outputInfo.text();
const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent;
element.innerHTML = trustedHtml as string;
injectTitleForSvg(outputInfo, element);
fixUpSvgElement(outputInfo, element);
for (const hook of hooks) {
element = (await hook.postRender(outputInfo, element, signal)) ?? element;

View file

@ -1394,21 +1394,34 @@ async function webviewPreloads(ctx: PreloadContext) {
}
try {
const image = $window.document.getElementById(outputId)?.querySelector('img')
?? $window.document.getElementById(altOutputId)?.querySelector('img');
const outputElement = $window.document.getElementById(outputId)
?? $window.document.getElementById(altOutputId);
let image = outputElement?.querySelector('img');
if (!image) {
const svgImage = outputElement?.querySelector('svg.output-image');
if (svgImage) {
image = new Image();
image.src = 'data:image/svg+xml,' + encodeURIComponent(svgImage.outerHTML);
}
}
if (image) {
const imageToCopy = image;
await navigator.clipboard.write([new ClipboardItem({
'image/png': new Promise((resolve) => {
const canvas = document.createElement('canvas');
if (canvas !== null) {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const context = canvas.getContext('2d');
context?.drawImage(image, 0, 0);
}
canvas.width = imageToCopy.naturalWidth;
canvas.height = imageToCopy.naturalHeight;
const context = canvas.getContext('2d');
context!.drawImage(imageToCopy, 0, 0);
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
console.error('No blob data to write to clipboard');
}
canvas.remove();
}, 'image/png');