From 4944ca0f5ab9adfc17a45fadc778c7b78568a038 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 23 Feb 2024 19:49:30 +0100 Subject: refactor(web): use filesystem access API when available --- subprojects/frontend/src/graph/exportDiagram.tsx | 35 +++---------- subprojects/frontend/src/utils/fileIO.ts | 63 ++++++++++++++++++++++++ subprojects/frontend/types/filesystemAccess.d.ts | 23 +++++++++ 3 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 subprojects/frontend/src/utils/fileIO.ts create mode 100644 subprojects/frontend/types/filesystemAccess.d.ts diff --git a/subprojects/frontend/src/graph/exportDiagram.tsx b/subprojects/frontend/src/graph/exportDiagram.tsx index 46c7f199..3ba278f9 100644 --- a/subprojects/frontend/src/graph/exportDiagram.tsx +++ b/subprojects/frontend/src/graph/exportDiagram.tsx @@ -18,6 +18,7 @@ import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; import type { Theme } from '@mui/material/styles'; import { darkTheme, lightTheme } from '../theme/ThemeProvider'; +import { copyBlob, saveBlob } from '../utils/fileIO'; import type ExportSettingsStore from './ExportSettingsStore'; import type GraphStore from './GraphStore'; @@ -25,7 +26,9 @@ import { createGraphTheme } from './GraphTheme'; import { SVG_NS } from './postProcessSVG'; const PROLOG = ''; +const PNG_CONTENT_TYPE = 'image/png'; const SVG_CONTENT_TYPE = 'image/svg+xml'; +const EXPORT_ID = 'export-image'; const ICONS: Map = new Map(); @@ -213,32 +216,6 @@ function serializeSVG(svgDocument: XMLDocument): Blob { }); } -function downloadBlob(blob: Blob, name: string): void { - const link = document.createElement('a'); - const url = window.URL.createObjectURL(blob); - try { - link.href = url; - link.download = name; - link.style.display = 'none'; - document.body.appendChild(link); - link.click(); - } finally { - window.URL.revokeObjectURL(url); - document.body.removeChild(link); - } -} - -async function copyBlob(blob: Blob): Promise { - const { clipboard } = navigator; - if ('write' in clipboard) { - await clipboard.write([ - new ClipboardItem({ - [blob.type]: blob, - }), - ]); - } -} - async function serializePNG( serializedSVG: Blob, svg: SVGSVGElement, @@ -302,7 +279,7 @@ async function serializePNG( } else { resolve(exportedBlob); } - }, 'image/png'); + }, PNG_CONTENT_TYPE); }); } @@ -353,11 +330,11 @@ export default async function exportDiagram( if (mode === 'copy') { await copyBlob(png); } else { - downloadBlob(png, 'graph.png'); + await saveBlob(png, 'graph.png', PNG_CONTENT_TYPE, EXPORT_ID); } } else if (mode === 'copy') { await copyBlob(serializedSVG); } else { - downloadBlob(serializedSVG, 'graph.svg'); + await saveBlob(serializedSVG, 'graph.svg', SVG_CONTENT_TYPE, EXPORT_ID); } } diff --git a/subprojects/frontend/src/utils/fileIO.ts b/subprojects/frontend/src/utils/fileIO.ts new file mode 100644 index 00000000..abcc43eb --- /dev/null +++ b/subprojects/frontend/src/utils/fileIO.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export async function saveBlob( + blob: Blob, + name: string, + mimeType: string, + id?: string, +): Promise { + if ('showSaveFilePicker' in window) { + const options: FilePickerOptions = { + suggestedName: name, + }; + if (id !== undefined) { + options.id = id; + } + const extensionIndex = name.lastIndexOf('.'); + if (extensionIndex >= 0) { + options.types = [ + { + description: `${name.substring(extensionIndex + 1)} files`, + accept: { + [mimeType]: [name.substring(extensionIndex)], + }, + }, + ]; + } + const handle = await window.showSaveFilePicker(options); + const writable = await handle.createWritable(); + try { + await writable.write(blob); + } finally { + await writable.close(); + } + return; + } + const link = document.createElement('a'); + const url = window.URL.createObjectURL(blob); + try { + link.href = url; + link.download = name; + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + } finally { + window.URL.revokeObjectURL(url); + document.body.removeChild(link); + } +} + +export async function copyBlob(blob: Blob): Promise { + const { clipboard } = navigator; + if ('write' in clipboard) { + await clipboard.write([ + new ClipboardItem({ + [blob.type]: blob, + }), + ]); + } +} diff --git a/subprojects/frontend/types/filesystemAccess.d.ts b/subprojects/frontend/types/filesystemAccess.d.ts new file mode 100644 index 00000000..000cd2a5 --- /dev/null +++ b/subprojects/frontend/types/filesystemAccess.d.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +interface FilePickerOptions { + suggestedName?: string; + id?: string; + types?: { + description?: string; + accept: Record; + }[]; +} + +interface Window { + showOpenFilePicker?: ( + options?: FilePickerOptions, + ) => Promise; + showSaveFilePicker?: ( + options?: FilePickerOptions, + ) => Promise; +} -- cgit v1.2.3-54-g00ecf