diff options
author | Kristóf Marussy <kristof@marussy.com> | 2024-02-23 19:49:30 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2024-02-23 19:49:30 +0100 |
commit | 4944ca0f5ab9adfc17a45fadc778c7b78568a038 (patch) | |
tree | acabe2d42592a422b6032078e3114c0da6b7a2fa /subprojects | |
parent | fix(frontend): top button styling (diff) | |
download | refinery-4944ca0f5ab9adfc17a45fadc778c7b78568a038.tar.gz refinery-4944ca0f5ab9adfc17a45fadc778c7b78568a038.tar.zst refinery-4944ca0f5ab9adfc17a45fadc778c7b78568a038.zip |
refactor(web): use filesystem access API when available
Diffstat (limited to 'subprojects')
-rw-r--r-- | subprojects/frontend/src/graph/exportDiagram.tsx | 35 | ||||
-rw-r--r-- | subprojects/frontend/src/utils/fileIO.ts | 63 | ||||
-rw-r--r-- | subprojects/frontend/types/filesystemAccess.d.ts | 23 |
3 files changed, 92 insertions, 29 deletions
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'; | |||
18 | import type { Theme } from '@mui/material/styles'; | 18 | import type { Theme } from '@mui/material/styles'; |
19 | 19 | ||
20 | import { darkTheme, lightTheme } from '../theme/ThemeProvider'; | 20 | import { darkTheme, lightTheme } from '../theme/ThemeProvider'; |
21 | import { copyBlob, saveBlob } from '../utils/fileIO'; | ||
21 | 22 | ||
22 | import type ExportSettingsStore from './ExportSettingsStore'; | 23 | import type ExportSettingsStore from './ExportSettingsStore'; |
23 | import type GraphStore from './GraphStore'; | 24 | import type GraphStore from './GraphStore'; |
@@ -25,7 +26,9 @@ import { createGraphTheme } from './GraphTheme'; | |||
25 | import { SVG_NS } from './postProcessSVG'; | 26 | import { SVG_NS } from './postProcessSVG'; |
26 | 27 | ||
27 | const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'; | 28 | const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'; |
29 | const PNG_CONTENT_TYPE = 'image/png'; | ||
28 | const SVG_CONTENT_TYPE = 'image/svg+xml'; | 30 | const SVG_CONTENT_TYPE = 'image/svg+xml'; |
31 | const EXPORT_ID = 'export-image'; | ||
29 | 32 | ||
30 | const ICONS: Map<string, Element> = new Map(); | 33 | const ICONS: Map<string, Element> = new Map(); |
31 | 34 | ||
@@ -213,32 +216,6 @@ function serializeSVG(svgDocument: XMLDocument): Blob { | |||
213 | }); | 216 | }); |
214 | } | 217 | } |
215 | 218 | ||
216 | function downloadBlob(blob: Blob, name: string): void { | ||
217 | const link = document.createElement('a'); | ||
218 | const url = window.URL.createObjectURL(blob); | ||
219 | try { | ||
220 | link.href = url; | ||
221 | link.download = name; | ||
222 | link.style.display = 'none'; | ||
223 | document.body.appendChild(link); | ||
224 | link.click(); | ||
225 | } finally { | ||
226 | window.URL.revokeObjectURL(url); | ||
227 | document.body.removeChild(link); | ||
228 | } | ||
229 | } | ||
230 | |||
231 | async function copyBlob(blob: Blob): Promise<void> { | ||
232 | const { clipboard } = navigator; | ||
233 | if ('write' in clipboard) { | ||
234 | await clipboard.write([ | ||
235 | new ClipboardItem({ | ||
236 | [blob.type]: blob, | ||
237 | }), | ||
238 | ]); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | async function serializePNG( | 219 | async function serializePNG( |
243 | serializedSVG: Blob, | 220 | serializedSVG: Blob, |
244 | svg: SVGSVGElement, | 221 | svg: SVGSVGElement, |
@@ -302,7 +279,7 @@ async function serializePNG( | |||
302 | } else { | 279 | } else { |
303 | resolve(exportedBlob); | 280 | resolve(exportedBlob); |
304 | } | 281 | } |
305 | }, 'image/png'); | 282 | }, PNG_CONTENT_TYPE); |
306 | }); | 283 | }); |
307 | } | 284 | } |
308 | 285 | ||
@@ -353,11 +330,11 @@ export default async function exportDiagram( | |||
353 | if (mode === 'copy') { | 330 | if (mode === 'copy') { |
354 | await copyBlob(png); | 331 | await copyBlob(png); |
355 | } else { | 332 | } else { |
356 | downloadBlob(png, 'graph.png'); | 333 | await saveBlob(png, 'graph.png', PNG_CONTENT_TYPE, EXPORT_ID); |
357 | } | 334 | } |
358 | } else if (mode === 'copy') { | 335 | } else if (mode === 'copy') { |
359 | await copyBlob(serializedSVG); | 336 | await copyBlob(serializedSVG); |
360 | } else { | 337 | } else { |
361 | downloadBlob(serializedSVG, 'graph.svg'); | 338 | await saveBlob(serializedSVG, 'graph.svg', SVG_CONTENT_TYPE, EXPORT_ID); |
362 | } | 339 | } |
363 | } | 340 | } |
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 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | export async function saveBlob( | ||
8 | blob: Blob, | ||
9 | name: string, | ||
10 | mimeType: string, | ||
11 | id?: string, | ||
12 | ): Promise<void> { | ||
13 | if ('showSaveFilePicker' in window) { | ||
14 | const options: FilePickerOptions = { | ||
15 | suggestedName: name, | ||
16 | }; | ||
17 | if (id !== undefined) { | ||
18 | options.id = id; | ||
19 | } | ||
20 | const extensionIndex = name.lastIndexOf('.'); | ||
21 | if (extensionIndex >= 0) { | ||
22 | options.types = [ | ||
23 | { | ||
24 | description: `${name.substring(extensionIndex + 1)} files`, | ||
25 | accept: { | ||
26 | [mimeType]: [name.substring(extensionIndex)], | ||
27 | }, | ||
28 | }, | ||
29 | ]; | ||
30 | } | ||
31 | const handle = await window.showSaveFilePicker(options); | ||
32 | const writable = await handle.createWritable(); | ||
33 | try { | ||
34 | await writable.write(blob); | ||
35 | } finally { | ||
36 | await writable.close(); | ||
37 | } | ||
38 | return; | ||
39 | } | ||
40 | const link = document.createElement('a'); | ||
41 | const url = window.URL.createObjectURL(blob); | ||
42 | try { | ||
43 | link.href = url; | ||
44 | link.download = name; | ||
45 | link.style.display = 'none'; | ||
46 | document.body.appendChild(link); | ||
47 | link.click(); | ||
48 | } finally { | ||
49 | window.URL.revokeObjectURL(url); | ||
50 | document.body.removeChild(link); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | export async function copyBlob(blob: Blob): Promise<void> { | ||
55 | const { clipboard } = navigator; | ||
56 | if ('write' in clipboard) { | ||
57 | await clipboard.write([ | ||
58 | new ClipboardItem({ | ||
59 | [blob.type]: blob, | ||
60 | }), | ||
61 | ]); | ||
62 | } | ||
63 | } | ||
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 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | interface FilePickerOptions { | ||
8 | suggestedName?: string; | ||
9 | id?: string; | ||
10 | types?: { | ||
11 | description?: string; | ||
12 | accept: Record<string, string[]>; | ||
13 | }[]; | ||
14 | } | ||
15 | |||
16 | interface Window { | ||
17 | showOpenFilePicker?: ( | ||
18 | options?: FilePickerOptions, | ||
19 | ) => Promise<FileSystemFileHandle>; | ||
20 | showSaveFilePicker?: ( | ||
21 | options?: FilePickerOptions, | ||
22 | ) => Promise<FileSystemFileHandle>; | ||
23 | } | ||