feat(mermaid): display an error when PNG export could not occur

This commit is contained in:
Elian Doran 2025-03-31 18:36:57 +03:00
parent f3b866fa7b
commit 3cdbc76fff
No known key found for this signature in database
3 changed files with 56 additions and 33 deletions

View File

@ -650,47 +650,58 @@ function triggerDownload(fileName: string, dataUrl: string) {
* *
* @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it. * @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
* @param svgContent the content of the SVG file download. * @param svgContent the content of the SVG file download.
* @returns `true` if the operation succeeded (width/height present), or `false` if the download was not triggered. * @returns a promise which resolves if the operation was successful, or rejects if it failed (permissions issue or some other issue).
*/ */
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) { function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
const mime = "image/svg+xml"; return new Promise<void>((resolve, reject) => {
const mime = "image/svg+xml";
// First, we need to determine the width and the height from the input SVG. // First, we need to determine the width and the height from the input SVG.
const svgDocument = (new DOMParser()).parseFromString(svgContent, mime); const svgDocument = (new DOMParser()).parseFromString(svgContent, mime);
const width = svgDocument.documentElement?.getAttribute("width"); const width = svgDocument.documentElement?.getAttribute("width");
const height = svgDocument.documentElement?.getAttribute("height"); const height = svgDocument.documentElement?.getAttribute("height");
if (!width || !height) { if (!width || !height) {
return false; reject();
} return;
}
// Convert the image to a blob. // Convert the image to a blob.
const svgBlob = new Blob([ svgContent ], { const svgBlob = new Blob([ svgContent ], {
type: mime type: mime
}) })
// Create an image element and load the SVG. // Create an image element and load the SVG.
const imageEl = new Image(); const imageEl = new Image();
imageEl.width = parseFloat(width); imageEl.width = parseFloat(width);
imageEl.height = parseFloat(height); imageEl.height = parseFloat(height);
imageEl.src = URL.createObjectURL(svgBlob); imageEl.onload = () => {
imageEl.onload = () => { try {
// Draw the image with a canvas. // Draw the image with a canvas.
const canvasEl = document.createElement("canvas"); const canvasEl = document.createElement("canvas");
canvasEl.width = imageEl.width; canvasEl.width = imageEl.width;
canvasEl.height = imageEl.height; canvasEl.height = imageEl.height;
document.body.appendChild(canvasEl); document.body.appendChild(canvasEl);
const ctx = canvasEl.getContext("2d"); const ctx = canvasEl.getContext("2d");
ctx?.drawImage(imageEl, 0, 0); if (!ctx) {
URL.revokeObjectURL(imageEl.src); reject();
}
const imgUri = canvasEl.toDataURL("image/png") ctx?.drawImage(imageEl, 0, 0);
triggerDownload(`${nameWithoutExtension}.png`, imgUri); URL.revokeObjectURL(imageEl.src);
document.body.removeChild(canvasEl);
};
return true; const imgUri = canvasEl.toDataURL("image/png")
triggerDownload(`${nameWithoutExtension}.png`, imgUri);
document.body.removeChild(canvasEl);
resolve();
} catch (e) {
console.warn(e);
reject();
}
};
imageEl.src = URL.createObjectURL(svgBlob);
});
} }
/** /**

View File

@ -2,7 +2,9 @@ import type { EventData } from "../../components/app_context.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import toast from "../../services/toast.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import ws from "../../services/ws.js";
import OnClickButtonWidget from "../buttons/onclick_button.js"; import OnClickButtonWidget from "../buttons/onclick_button.js";
import AbstractSplitTypeWidget from "./abstract_split_type_widget.js"; import AbstractSplitTypeWidget from "./abstract_split_type_widget.js";
@ -218,11 +220,18 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
} }
async exportPngEvent({ ntxId }: EventData<"exportPng">) { async exportPngEvent({ ntxId }: EventData<"exportPng">) {
console.log("Export to PNG", this.noteContext?.noteId, ntxId, this.svg);
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) { if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
console.log("Return");
return; return;
} }
utils.downloadSvgAsPng(this.note.title, this.svg); try {
await utils.downloadSvgAsPng(this.note.title, this.svg);
} catch (e) {
console.warn(e);
toast.showError(t("svg.export_to_png"));
}
} }
} }

View File

@ -1746,5 +1746,8 @@
}, },
"png_export_button": { "png_export_button": {
"button_title": "Export diagram as PNG" "button_title": "Export diagram as PNG"
},
"svg": {
"export_to_png": "The diagram could not be exported to PNG."
} }
} }