diff options
author | Kristóf Marussy <marussy@mit.bme.hu> | 2024-02-24 20:41:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-24 20:41:53 +0100 |
commit | 833e2f58afaafbea3e6fbf2cb3d9aa53a641fc84 (patch) | |
tree | 3b3e495b5718ae9a8833e1b5f9fa34377a11de8f | |
parent | Merge pull request #53 from kris7t/imports (diff) | |
parent | chore(deps); bump dependencies (diff) | |
download | refinery-833e2f58afaafbea3e6fbf2cb3d9aa53a641fc84.tar.gz refinery-833e2f58afaafbea3e6fbf2cb3d9aa53a641fc84.tar.zst refinery-833e2f58afaafbea3e6fbf2cb3d9aa53a641fc84.zip |
Merge pull request #55 from kris7t/svg-export
Frontend: file management and svg export
38 files changed, 2155 insertions, 597 deletions
diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 00000000..6fe84ee2 --- /dev/null +++ b/LICENSES/OFL-1.1.txt | |||
@@ -0,0 +1,43 @@ | |||
1 | SIL OPEN FONT LICENSE | ||
2 | |||
3 | Version 1.1 - 26 February 2007 | ||
4 | |||
5 | PREAMBLE | ||
6 | |||
7 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. | ||
8 | |||
9 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. | ||
10 | |||
11 | DEFINITIONS | ||
12 | |||
13 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. | ||
14 | |||
15 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). | ||
16 | |||
17 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). | ||
18 | |||
19 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. | ||
20 | |||
21 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. | ||
22 | |||
23 | PERMISSION & CONDITIONS | ||
24 | |||
25 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: | ||
26 | |||
27 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. | ||
28 | |||
29 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. | ||
30 | |||
31 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. | ||
32 | |||
33 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. | ||
34 | |||
35 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. | ||
36 | |||
37 | TERMINATION | ||
38 | |||
39 | This license becomes null and void if any of the above conditions are not met. | ||
40 | |||
41 | DISCLAIMER | ||
42 | |||
43 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. | ||
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index df12d2f1..0ed6470f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml | |||
@@ -8,9 +8,9 @@ jetty = "12.0.6" | |||
8 | jmh = "1.37" | 8 | jmh = "1.37" |
9 | junit = "5.10.2" | 9 | junit = "5.10.2" |
10 | mockito = "5.10.0" | 10 | mockito = "5.10.0" |
11 | mwe2 = "2.17.0.M3" | 11 | mwe2 = "2.17.0" |
12 | slf4j = "2.0.12" | 12 | slf4j = "2.0.12" |
13 | xtext = "2.34.0.M1" | 13 | xtext = "2.34.0.M2" |
14 | 14 | ||
15 | [libraries] | 15 | [libraries] |
16 | eclipseCollections = { group = "org.eclipse.collections", name = "eclipse-collections", version.ref = "eclipseCollections" } | 16 | eclipseCollections = { group = "org.eclipse.collections", name = "eclipse-collections", version.ref = "eclipseCollections" } |
@@ -37,7 +37,7 @@ junit-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", ver | |||
37 | junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } | 37 | junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } |
38 | mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } | 38 | mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } |
39 | mockito-junit = { group = "org.mockito", name = "mockito-junit-jupiter", version.ref = "mockito" } | 39 | mockito-junit = { group = "org.mockito", name = "mockito-junit-jupiter", version.ref = "mockito" } |
40 | mwe-utils = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe.utils", version = "1.11.0.M3" } | 40 | mwe-utils = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe.utils", version = "1.11.0" } |
41 | mwe2-launch = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.launch", version.ref = "mwe2" } | 41 | mwe2-launch = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.launch", version.ref = "mwe2" } |
42 | mwe2-lib = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.lib", version.ref = "mwe2" } | 42 | mwe2-lib = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.lib", version.ref = "mwe2" } |
43 | ortools = { group = "com.google.ortools", name = "ortools-java", version = "9.8.3296" } | 43 | ortools = { group = "com.google.ortools", name = "ortools-java", version = "9.8.3296" } |
diff --git a/package.json b/package.json index 7822bcc1..f7b8bd83 100644 --- a/package.json +++ b/package.json | |||
@@ -25,7 +25,7 @@ | |||
25 | }, | 25 | }, |
26 | "packageManager": "yarn@4.1.0", | 26 | "packageManager": "yarn@4.1.0", |
27 | "devDependencies": { | 27 | "devDependencies": { |
28 | "eslint": "^8.56.0", | 28 | "eslint": "^8.57.0", |
29 | "typescript": "5.3.3" | 29 | "typescript": "5.3.3" |
30 | }, | 30 | }, |
31 | "resolutions": { | 31 | "resolutions": { |
diff --git a/subprojects/frontend/config/graphvizUMDVitePlugin.ts b/subprojects/frontend/config/graphvizUMDVitePlugin.ts index 0c3c9aa0..8f3511bc 100644 --- a/subprojects/frontend/config/graphvizUMDVitePlugin.ts +++ b/subprojects/frontend/config/graphvizUMDVitePlugin.ts | |||
@@ -35,7 +35,6 @@ export default function graphvizUMDVitePlugin(): PluginOption { | |||
35 | if (resolvedPath === undefined) { | 35 | if (resolvedPath === undefined) { |
36 | return; | 36 | return; |
37 | } | 37 | } |
38 | console.log(resolvedPath); | ||
39 | if (command === 'serve') { | 38 | if (command === 'serve') { |
40 | url = `/@fs/${resolvedPath}`; | 39 | url = `/@fs/${resolvedPath}`; |
41 | } else { | 40 | } else { |
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index 7d5e19d1..73bb463d 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "//": [ | 2 | "//": [ |
3 | "SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>", | 3 | "SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>", |
4 | "", | 4 | "", |
5 | "SPDX-License-Identifier: EPL-2.0" | 5 | "SPDX-License-Identifier: EPL-2.0" |
6 | ], | 6 | ], |
@@ -33,21 +33,25 @@ | |||
33 | "@codemirror/language": "^6.10.1", | 33 | "@codemirror/language": "^6.10.1", |
34 | "@codemirror/lint": "^6.5.0", | 34 | "@codemirror/lint": "^6.5.0", |
35 | "@codemirror/search": "^6.5.6", | 35 | "@codemirror/search": "^6.5.6", |
36 | "@codemirror/state": "^6.4.0", | 36 | "@codemirror/state": "^6.4.1", |
37 | "@codemirror/view": "^6.24.0", | 37 | "@codemirror/view": "^6.24.1", |
38 | "@emotion/cache": "^11.11.0", | ||
38 | "@emotion/react": "^11.11.3", | 39 | "@emotion/react": "^11.11.3", |
40 | "@emotion/serialize": "^1.1.3", | ||
39 | "@emotion/styled": "^11.11.0", | 41 | "@emotion/styled": "^11.11.0", |
42 | "@emotion/utils": "^1.2.1", | ||
40 | "@fontsource-variable/jetbrains-mono": "^5.0.19", | 43 | "@fontsource-variable/jetbrains-mono": "^5.0.19", |
41 | "@fontsource-variable/open-sans": "^5.0.25", | 44 | "@fontsource-variable/open-sans": "^5.0.25", |
45 | "@fontsource/open-sans": "^5.0.24", | ||
42 | "@hpcc-js/wasm": "^2.16.0", | 46 | "@hpcc-js/wasm": "^2.16.0", |
43 | "@lezer/common": "^1.2.1", | 47 | "@lezer/common": "^1.2.1", |
44 | "@lezer/highlight": "^1.2.0", | 48 | "@lezer/highlight": "^1.2.0", |
45 | "@lezer/lr": "^1.4.0", | 49 | "@lezer/lr": "^1.4.0", |
46 | "@material-icons/svg": "^1.0.33", | 50 | "@material-icons/svg": "^1.0.33", |
47 | "@mui/icons-material": "^5.15.10", | 51 | "@mui/icons-material": "^5.15.11", |
48 | "@mui/material": "^5.15.10", | 52 | "@mui/material": "^5.15.11", |
49 | "@mui/system": "^5.15.9", | 53 | "@mui/system": "^5.15.11", |
50 | "@mui/x-data-grid": "^6.19.4", | 54 | "@mui/x-data-grid": "^6.19.5", |
51 | "ansi-styles": "^6.2.1", | 55 | "ansi-styles": "^6.2.1", |
52 | "csstype": "^3.1.3", | 56 | "csstype": "^3.1.3", |
53 | "d3": "^7.8.5", | 57 | "d3": "^7.8.5", |
@@ -55,17 +59,19 @@ | |||
55 | "d3-selection": "^3.0.0", | 59 | "d3-selection": "^3.0.0", |
56 | "d3-zoom": "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch", | 60 | "d3-zoom": "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch", |
57 | "escape-string-regexp": "^5.0.0", | 61 | "escape-string-regexp": "^5.0.0", |
62 | "jspdf": "^2.5.1", | ||
58 | "lodash-es": "^4.17.21", | 63 | "lodash-es": "^4.17.21", |
59 | "loglevel": "^1.9.1", | 64 | "loglevel": "^1.9.1", |
60 | "loglevel-plugin-prefix": "^0.8.4", | 65 | "loglevel-plugin-prefix": "^0.8.4", |
61 | "mobx": "^6.12.0", | 66 | "mobx": "^6.12.0", |
62 | "mobx-react-lite": "^4.0.5", | 67 | "mobx-react-lite": "^4.0.5", |
63 | "ms": "^2.1.3", | 68 | "ms": "^2.1.3", |
64 | "nanoid": "^5.0.5", | 69 | "nanoid": "^5.0.6", |
65 | "notistack": "^3.0.1", | 70 | "notistack": "^3.0.1", |
66 | "react": "^18.2.0", | 71 | "react": "^18.2.0", |
67 | "react-dom": "^18.2.0", | 72 | "react-dom": "^18.2.0", |
68 | "react-resize-detector": "^10.0.1", | 73 | "react-resize-detector": "^10.0.1", |
74 | "svg2pdf.js": "^2.2.3", | ||
69 | "xstate": "^4.38.3", | 75 | "xstate": "^4.38.3", |
70 | "zod": "^3.22.4" | 76 | "zod": "^3.22.4" |
71 | }, | 77 | }, |
@@ -75,21 +81,22 @@ | |||
75 | "@types/d3-graphviz": "^2.6.10", | 81 | "@types/d3-graphviz": "^2.6.10", |
76 | "@types/d3-selection": "^3.0.10", | 82 | "@types/d3-selection": "^3.0.10", |
77 | "@types/d3-zoom": "^3.0.8", | 83 | "@types/d3-zoom": "^3.0.8", |
78 | "@types/eslint": "^8.56.2", | 84 | "@types/eslint": "^8.56.3", |
79 | "@types/html-minifier-terser": "^7.0.2", | 85 | "@types/html-minifier-terser": "^7.0.2", |
86 | "@types/jspdf": "^2.0.0", | ||
80 | "@types/lodash-es": "^4.17.12", | 87 | "@types/lodash-es": "^4.17.12", |
81 | "@types/micromatch": "^4.0.6", | 88 | "@types/micromatch": "^4.0.6", |
82 | "@types/ms": "^0.7.34", | 89 | "@types/ms": "^0.7.34", |
83 | "@types/node": "^20.11.19", | 90 | "@types/node": "^20.11.20", |
84 | "@types/pnpapi": "^0.0.5", | 91 | "@types/pnpapi": "^0.0.5", |
85 | "@types/react": "^18.2.56", | 92 | "@types/react": "^18.2.58", |
86 | "@types/react-dom": "^18.2.19", | 93 | "@types/react-dom": "^18.2.19", |
87 | "@typescript-eslint/eslint-plugin": "^6.21.0", | 94 | "@typescript-eslint/eslint-plugin": "^6.21.0", |
88 | "@typescript-eslint/parser": "^6.21.0", | 95 | "@typescript-eslint/parser": "^6.21.0", |
89 | "@vitejs/plugin-react-swc": "^3.6.0", | 96 | "@vitejs/plugin-react-swc": "^3.6.0", |
90 | "@xstate/cli": "^0.5.17", | 97 | "@xstate/cli": "^0.5.17", |
91 | "cross-env": "^7.0.3", | 98 | "cross-env": "^7.0.3", |
92 | "eslint": "^8.56.0", | 99 | "eslint": "^8.57.0", |
93 | "eslint-config-airbnb": "^19.0.4", | 100 | "eslint-config-airbnb": "^19.0.4", |
94 | "eslint-config-airbnb-typescript": "^17.1.0", | 101 | "eslint-config-airbnb-typescript": "^17.1.0", |
95 | "eslint-config-prettier": "^9.1.0", | 102 | "eslint-config-prettier": "^9.1.0", |
@@ -105,7 +112,7 @@ | |||
105 | "pnpapi": "^0.0.0", | 112 | "pnpapi": "^0.0.0", |
106 | "prettier": "^3.2.5", | 113 | "prettier": "^3.2.5", |
107 | "typescript": "5.3.3", | 114 | "typescript": "5.3.3", |
108 | "vite": "^5.1.3", | 115 | "vite": "^5.1.4", |
109 | "vite-plugin-pwa": "^0.19.0", | 116 | "vite-plugin-pwa": "^0.19.0", |
110 | "workbox-window": "^7.0.0" | 117 | "workbox-window": "^7.0.0" |
111 | } | 118 | } |
diff --git a/subprojects/frontend/src/PaneButtons.tsx b/subprojects/frontend/src/PaneButtons.tsx index 7e884ab0..7c759c36 100644 --- a/subprojects/frontend/src/PaneButtons.tsx +++ b/subprojects/frontend/src/PaneButtons.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -27,6 +27,9 @@ const PaneButtonGroup = styled(ToggleButtonGroup, { | |||
27 | '.MuiToggleButton-root': { | 27 | '.MuiToggleButton-root': { |
28 | fontSize: '1rem', | 28 | fontSize: '1rem', |
29 | lineHeight: '1.5', | 29 | lineHeight: '1.5', |
30 | // Must remove margin along with the border to avoid the button | ||
31 | // moving around (into the space of the missing border) when selected. | ||
32 | margin: '0', | ||
30 | border: 'none', | 33 | border: 'none', |
31 | ...(hideLabel ? {} : { paddingBlock: 6 }), | 34 | ...(hideLabel ? {} : { paddingBlock: 6 }), |
32 | '&::before': { | 35 | '&::before': { |
diff --git a/subprojects/frontend/src/RootStore.ts b/subprojects/frontend/src/RootStore.ts index e277c808..c029f746 100644 --- a/subprojects/frontend/src/RootStore.ts +++ b/subprojects/frontend/src/RootStore.ts | |||
@@ -5,10 +5,16 @@ | |||
5 | */ | 5 | */ |
6 | 6 | ||
7 | import { getLogger } from 'loglevel'; | 7 | import { getLogger } from 'loglevel'; |
8 | import { makeAutoObservable, runInAction } from 'mobx'; | 8 | import { |
9 | IReactionDisposer, | ||
10 | autorun, | ||
11 | makeAutoObservable, | ||
12 | runInAction, | ||
13 | } from 'mobx'; | ||
9 | 14 | ||
10 | import PWAStore from './PWAStore'; | 15 | import PWAStore from './PWAStore'; |
11 | import type EditorStore from './editor/EditorStore'; | 16 | import type EditorStore from './editor/EditorStore'; |
17 | import ExportSettingsScotre from './graph/export/ExportSettingsStore'; | ||
12 | import Compressor from './persistence/Compressor'; | 18 | import Compressor from './persistence/Compressor'; |
13 | import ThemeStore from './theme/ThemeStore'; | 19 | import ThemeStore from './theme/ThemeStore'; |
14 | 20 | ||
@@ -29,16 +35,26 @@ export default class RootStore { | |||
29 | 35 | ||
30 | readonly themeStore: ThemeStore; | 36 | readonly themeStore: ThemeStore; |
31 | 37 | ||
38 | readonly exportSettingsStore: ExportSettingsScotre; | ||
39 | |||
32 | disposed = false; | 40 | disposed = false; |
33 | 41 | ||
42 | private titleReaction: IReactionDisposer | undefined; | ||
43 | |||
34 | constructor() { | 44 | constructor() { |
35 | this.pwaStore = new PWAStore(); | 45 | this.pwaStore = new PWAStore(); |
36 | this.themeStore = new ThemeStore(); | 46 | this.themeStore = new ThemeStore(); |
37 | makeAutoObservable<RootStore, 'compressor' | 'editorStoreClass'>(this, { | 47 | this.exportSettingsStore = new ExportSettingsScotre(); |
48 | makeAutoObservable< | ||
49 | RootStore, | ||
50 | 'compressor' | 'editorStoreClass' | 'titleReaction' | ||
51 | >(this, { | ||
38 | compressor: false, | 52 | compressor: false, |
39 | editorStoreClass: false, | 53 | editorStoreClass: false, |
40 | pwaStore: false, | 54 | pwaStore: false, |
41 | themeStore: false, | 55 | themeStore: false, |
56 | exportSettingsStore: false, | ||
57 | titleReaction: false, | ||
42 | }); | 58 | }); |
43 | (async () => { | 59 | (async () => { |
44 | const { default: EditorStore } = await import('./editor/EditorStore'); | 60 | const { default: EditorStore } = await import('./editor/EditorStore'); |
@@ -61,11 +77,21 @@ export default class RootStore { | |||
61 | this.initialValue = initialValue; | 77 | this.initialValue = initialValue; |
62 | if (this.editorStoreClass !== undefined) { | 78 | if (this.editorStoreClass !== undefined) { |
63 | const EditorStore = this.editorStoreClass; | 79 | const EditorStore = this.editorStoreClass; |
64 | this.editorStore = new EditorStore( | 80 | const editorStore = new EditorStore( |
65 | this.initialValue, | 81 | this.initialValue, |
66 | this.pwaStore, | 82 | this.pwaStore, |
67 | (text) => this.compressor.compress(text), | 83 | (text) => this.compressor.compress(text), |
68 | ); | 84 | ); |
85 | this.editorStore = editorStore; | ||
86 | this.titleReaction?.(); | ||
87 | this.titleReaction = autorun(() => { | ||
88 | const { simpleName, unsavedChanges } = editorStore; | ||
89 | if (simpleName === undefined) { | ||
90 | document.title = 'Refinery'; | ||
91 | } else { | ||
92 | document.title = `${unsavedChanges ? '\u25cf ' : ''}${simpleName} - Refinery`; | ||
93 | } | ||
94 | }); | ||
69 | } | 95 | } |
70 | } | 96 | } |
71 | 97 | ||
@@ -73,6 +99,7 @@ export default class RootStore { | |||
73 | if (this.disposed) { | 99 | if (this.disposed) { |
74 | return; | 100 | return; |
75 | } | 101 | } |
102 | this.titleReaction?.(); | ||
76 | this.editorStore?.dispose(); | 103 | this.editorStore?.dispose(); |
77 | this.compressor.dispose(); | 104 | this.compressor.dispose(); |
78 | this.disposed = true; | 105 | this.disposed = true; |
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index 867a24a0..738052c7 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx | |||
@@ -55,6 +55,27 @@ function useWindowControlsOverlayVisible(): boolean { | |||
55 | return windowControlsOverlayVisible; | 55 | return windowControlsOverlayVisible; |
56 | } | 56 | } |
57 | 57 | ||
58 | function RefineryIcon({ size }: { size: number }): JSX.Element { | ||
59 | const theme = useTheme(); | ||
60 | return ( | ||
61 | <svg | ||
62 | xmlns="http://www.w3.org/2000/svg" | ||
63 | width={size} | ||
64 | height={size} | ||
65 | viewBox="0 0 512 515" | ||
66 | > | ||
67 | <path | ||
68 | d="M447.98 179.335c-139.95-9.583-301.272-50.91-384-147.336v46.117C98.45 129.623 209.442 178.137 294.243 199.1c-84.796 20.963-195.791 69.476-230.265 120.985v46.117c82.73-96.422 244.053-137.752 384.002-147.334z" | ||
69 | fill={theme.palette.text.primary} | ||
70 | /> | ||
71 | <path | ||
72 | d="M447.98 296.729c-113.755 4.192-287.485 40.727-384 136.557v46.716c95.14-103.612 279.898-137.754 384-143.745z" | ||
73 | fill={theme.palette.primary.main} | ||
74 | /> | ||
75 | </svg> | ||
76 | ); | ||
77 | } | ||
78 | |||
58 | const DevModeBadge = styled('div')(({ theme }) => ({ | 79 | const DevModeBadge = styled('div')(({ theme }) => ({ |
59 | ...theme.typography.button, | 80 | ...theme.typography.button, |
60 | display: 'inline-block', | 81 | display: 'inline-block', |
@@ -64,6 +85,16 @@ const DevModeBadge = styled('div')(({ theme }) => ({ | |||
64 | borderRadius: theme.shape.borderRadius, | 85 | borderRadius: theme.shape.borderRadius, |
65 | })); | 86 | })); |
66 | 87 | ||
88 | const FileName = styled('span', { | ||
89 | shouldForwardProp: (prop) => prop !== 'unsavedChanges', | ||
90 | })<{ unsavedChanges: boolean }>(({ theme, unsavedChanges }) => ({ | ||
91 | marginLeft: theme.spacing(1), | ||
92 | fontWeight: theme.typography.fontWeightLight, | ||
93 | fontSize: '1.25rem', | ||
94 | lineHeight: '1.6rem', | ||
95 | fontStyle: unsavedChanges ? 'italic' : 'normal', | ||
96 | })); | ||
97 | |||
67 | export default observer(function TopBar(): JSX.Element { | 98 | export default observer(function TopBar(): JSX.Element { |
68 | const { editorStore, themeStore } = useRootStore(); | 99 | const { editorStore, themeStore } = useRootStore(); |
69 | const overlayVisible = useWindowControlsOverlayVisible(); | 100 | const overlayVisible = useWindowControlsOverlayVisible(); |
@@ -101,9 +132,15 @@ export default observer(function TopBar(): JSX.Element { | |||
101 | py: 0.5, | 132 | py: 0.5, |
102 | }} | 133 | }} |
103 | > | 134 | > |
104 | <Typography variant="h6" component="h1"> | 135 | <RefineryIcon size={24} /> |
136 | <Typography variant="h6" component="h1" pl={1}> | ||
105 | Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>} | 137 | Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>} |
106 | </Typography> | 138 | </Typography> |
139 | {large && editorStore?.simpleName !== undefined && ( | ||
140 | <FileName unsavedChanges={editorStore.unsavedChanges}> | ||
141 | {editorStore.simpleName} | ||
142 | </FileName> | ||
143 | )} | ||
107 | <Stack direction="row" alignItems="center" flexGrow={1} marginLeft={1}> | 144 | <Stack direction="row" alignItems="center" flexGrow={1} marginLeft={1}> |
108 | {medium && !large && ( | 145 | {medium && !large && ( |
109 | <PaneButtons themeStore={themeStore} hideLabel /> | 146 | <PaneButtons themeStore={themeStore} hideLabel /> |
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx index f4513909..4afba607 100644 --- a/subprojects/frontend/src/editor/EditorButtons.tsx +++ b/subprojects/frontend/src/editor/EditorButtons.tsx | |||
@@ -7,11 +7,14 @@ | |||
7 | import type { Diagnostic } from '@codemirror/lint'; | 7 | import type { Diagnostic } from '@codemirror/lint'; |
8 | import CancelIcon from '@mui/icons-material/Cancel'; | 8 | import CancelIcon from '@mui/icons-material/Cancel'; |
9 | import CheckIcon from '@mui/icons-material/Check'; | 9 | import CheckIcon from '@mui/icons-material/Check'; |
10 | import FileOpenIcon from '@mui/icons-material/FileOpen'; | ||
10 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; | 11 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; |
11 | import FormatPaintIcon from '@mui/icons-material/FormatPaint'; | 12 | import FormatPaintIcon from '@mui/icons-material/FormatPaint'; |
12 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | 13 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; |
13 | import LooksIcon from '@mui/icons-material/Looks'; | 14 | import LooksIcon from '@mui/icons-material/Looks'; |
14 | import RedoIcon from '@mui/icons-material/Redo'; | 15 | import RedoIcon from '@mui/icons-material/Redo'; |
16 | import SaveIcon from '@mui/icons-material/Save'; | ||
17 | import SaveAsIcon from '@mui/icons-material/SaveAs'; | ||
15 | import SearchIcon from '@mui/icons-material/Search'; | 18 | import SearchIcon from '@mui/icons-material/Search'; |
16 | import UndoIcon from '@mui/icons-material/Undo'; | 19 | import UndoIcon from '@mui/icons-material/Undo'; |
17 | import WarningIcon from '@mui/icons-material/Warning'; | 20 | import WarningIcon from '@mui/icons-material/Warning'; |
@@ -47,10 +50,37 @@ export default observer(function EditorButtons({ | |||
47 | return ( | 50 | return ( |
48 | <Stack direction="row" flexGrow={1}> | 51 | <Stack direction="row" flexGrow={1}> |
49 | <IconButton | 52 | <IconButton |
53 | disabled={editorStore === undefined} | ||
54 | onClick={() => editorStore?.openFile()} | ||
55 | aria-label="Open" | ||
56 | color="inherit" | ||
57 | > | ||
58 | <FileOpenIcon fontSize="small" /> | ||
59 | </IconButton> | ||
60 | <IconButton | ||
61 | disabled={editorStore === undefined || !editorStore.unsavedChanges} | ||
62 | onClick={() => editorStore?.saveFile()} | ||
63 | aria-label="Save" | ||
64 | color="inherit" | ||
65 | > | ||
66 | <SaveIcon fontSize="small" /> | ||
67 | </IconButton> | ||
68 | {'showSaveFilePicker' in window && ( | ||
69 | <IconButton | ||
70 | disabled={editorStore === undefined} | ||
71 | onClick={() => editorStore?.saveFileAs()} | ||
72 | aria-label="Save as" | ||
73 | color="inherit" | ||
74 | > | ||
75 | <SaveAsIcon fontSize="small" /> | ||
76 | </IconButton> | ||
77 | )} | ||
78 | <IconButton | ||
50 | disabled={editorStore === undefined || !editorStore.canUndo} | 79 | disabled={editorStore === undefined || !editorStore.canUndo} |
51 | onClick={() => editorStore?.undo()} | 80 | onClick={() => editorStore?.undo()} |
52 | aria-label="Undo" | 81 | aria-label="Undo" |
53 | color="inherit" | 82 | color="inherit" |
83 | sx={{ ml: 1 }} | ||
54 | > | 84 | > |
55 | <UndoIcon fontSize="small" /> | 85 | <UndoIcon fontSize="small" /> |
56 | </IconButton> | 86 | </IconButton> |
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts index 5e7d05e1..33bca382 100644 --- a/subprojects/frontend/src/editor/EditorStore.ts +++ b/subprojects/frontend/src/editor/EditorStore.ts | |||
@@ -27,6 +27,13 @@ import { nanoid } from 'nanoid'; | |||
27 | 27 | ||
28 | import type PWAStore from '../PWAStore'; | 28 | import type PWAStore from '../PWAStore'; |
29 | import GraphStore from '../graph/GraphStore'; | 29 | import GraphStore from '../graph/GraphStore'; |
30 | import { | ||
31 | type OpenResult, | ||
32 | type OpenTextFileResult, | ||
33 | openTextFile, | ||
34 | saveTextFile, | ||
35 | saveBlob, | ||
36 | } from '../utils/fileIO'; | ||
30 | import getLogger from '../utils/getLogger'; | 37 | import getLogger from '../utils/getLogger'; |
31 | import type XtextClient from '../xtext/XtextClient'; | 38 | import type XtextClient from '../xtext/XtextClient'; |
32 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; | 39 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; |
@@ -35,7 +42,10 @@ import EditorErrors from './EditorErrors'; | |||
35 | import GeneratedModelStore from './GeneratedModelStore'; | 42 | import GeneratedModelStore from './GeneratedModelStore'; |
36 | import LintPanelStore from './LintPanelStore'; | 43 | import LintPanelStore from './LintPanelStore'; |
37 | import SearchPanelStore from './SearchPanelStore'; | 44 | import SearchPanelStore from './SearchPanelStore'; |
38 | import createEditorState from './createEditorState'; | 45 | import createEditorState, { |
46 | createHistoryExtension, | ||
47 | historyCompartment, | ||
48 | } from './createEditorState'; | ||
39 | import { countDiagnostics } from './exposeDiagnostics'; | 49 | import { countDiagnostics } from './exposeDiagnostics'; |
40 | import { type IOccurrence, setOccurrences } from './findOccurrences'; | 50 | import { type IOccurrence, setOccurrences } from './findOccurrences'; |
41 | import { | 51 | import { |
@@ -45,6 +55,25 @@ import { | |||
45 | 55 | ||
46 | const log = getLogger('editor.EditorStore'); | 56 | const log = getLogger('editor.EditorStore'); |
47 | 57 | ||
58 | const REFINERY_CONTENT_TYPE = 'text/x-refinery'; | ||
59 | |||
60 | const FILE_PICKER_OPTIONS: FilePickerOptions = { | ||
61 | id: 'problem', | ||
62 | types: [ | ||
63 | { | ||
64 | description: 'Refinery files', | ||
65 | accept: { | ||
66 | [REFINERY_CONTENT_TYPE]: [ | ||
67 | '.problem', | ||
68 | '.PROBLEM', | ||
69 | '.refinery', | ||
70 | '.REFINERY', | ||
71 | ], | ||
72 | }, | ||
73 | }, | ||
74 | ], | ||
75 | }; | ||
76 | |||
48 | export default class EditorStore { | 77 | export default class EditorStore { |
49 | readonly id: string; | 78 | readonly id: string; |
50 | 79 | ||
@@ -76,6 +105,12 @@ export default class EditorStore { | |||
76 | 105 | ||
77 | selectedGeneratedModel: string | undefined; | 106 | selectedGeneratedModel: string | undefined; |
78 | 107 | ||
108 | fileName: string | undefined; | ||
109 | |||
110 | private fileHandle: FileSystemFileHandle | undefined; | ||
111 | |||
112 | unsavedChanges = false; | ||
113 | |||
79 | constructor( | 114 | constructor( |
80 | initialValue: string, | 115 | initialValue: string, |
81 | pwaStore: PWAStore, | 116 | pwaStore: PWAStore, |
@@ -201,6 +236,9 @@ export default class EditorStore { | |||
201 | log.trace('Editor transaction', tr); | 236 | log.trace('Editor transaction', tr); |
202 | this.state = tr.state; | 237 | this.state = tr.state; |
203 | this.client?.onTransaction(tr); | 238 | this.client?.onTransaction(tr); |
239 | if (tr.docChanged) { | ||
240 | this.unsavedChanges = true; | ||
241 | } | ||
204 | } | 242 | } |
205 | 243 | ||
206 | doCommand(command: Command): boolean { | 244 | doCommand(command: Command): boolean { |
@@ -403,4 +441,88 @@ export default class EditorStore { | |||
403 | }); | 441 | }); |
404 | return generating; | 442 | return generating; |
405 | } | 443 | } |
444 | |||
445 | openFile(): boolean { | ||
446 | openTextFile(FILE_PICKER_OPTIONS) | ||
447 | .then((result) => this.fileOpened(result)) | ||
448 | .catch((error) => log.error('Failed to open file', error)); | ||
449 | return true; | ||
450 | } | ||
451 | |||
452 | private clearUnsavedChanges(): void { | ||
453 | this.unsavedChanges = false; | ||
454 | } | ||
455 | |||
456 | private setFile({ name, handle }: OpenResult): void { | ||
457 | log.info('Opened file', name); | ||
458 | this.fileName = name; | ||
459 | this.fileHandle = handle; | ||
460 | } | ||
461 | |||
462 | private fileOpened(result: OpenTextFileResult): void { | ||
463 | this.dispatch({ | ||
464 | changes: [ | ||
465 | { | ||
466 | from: 0, | ||
467 | to: this.state.doc.length, | ||
468 | insert: result.text, | ||
469 | }, | ||
470 | ], | ||
471 | effects: [historyCompartment.reconfigure([])], | ||
472 | }); | ||
473 | // Clear history by removing and re-adding the history extension. See | ||
474 | // https://stackoverflow.com/a/77943295 and | ||
475 | // https://discuss.codemirror.net/t/codemirror-6-cm-clearhistory-equivalent/2851/10 | ||
476 | this.dispatch({ | ||
477 | effects: [historyCompartment.reconfigure([createHistoryExtension()])], | ||
478 | }); | ||
479 | this.setFile(result); | ||
480 | this.clearUnsavedChanges(); | ||
481 | } | ||
482 | |||
483 | saveFile(): boolean { | ||
484 | if (!this.unsavedChanges) { | ||
485 | return false; | ||
486 | } | ||
487 | if (this.fileHandle === undefined) { | ||
488 | return this.saveFileAs(); | ||
489 | } | ||
490 | saveTextFile(this.fileHandle, this.state.sliceDoc()) | ||
491 | .then(() => this.clearUnsavedChanges()) | ||
492 | .catch((error) => log.error('Failed to save file', error)); | ||
493 | return true; | ||
494 | } | ||
495 | |||
496 | saveFileAs(): boolean { | ||
497 | const blob = new Blob([this.state.sliceDoc()], { | ||
498 | type: REFINERY_CONTENT_TYPE, | ||
499 | }); | ||
500 | saveBlob(blob, this.fileName ?? 'graph.problem', FILE_PICKER_OPTIONS) | ||
501 | .then((result) => this.fileSavedAs(result)) | ||
502 | .catch((error) => log.error('Failed to save file', error)); | ||
503 | return true; | ||
504 | } | ||
505 | |||
506 | private fileSavedAs(result: OpenResult | undefined) { | ||
507 | if (result !== undefined) { | ||
508 | this.setFile(result); | ||
509 | } | ||
510 | this.clearUnsavedChanges(); | ||
511 | } | ||
512 | |||
513 | get simpleName(): string | undefined { | ||
514 | const { fileName } = this; | ||
515 | if (fileName === undefined) { | ||
516 | return undefined; | ||
517 | } | ||
518 | const index = fileName.lastIndexOf('.'); | ||
519 | if (index < 0) { | ||
520 | return fileName; | ||
521 | } | ||
522 | return fileName.substring(0, index); | ||
523 | } | ||
524 | |||
525 | get simpleNameOrFallback(): string { | ||
526 | return this.simpleName ?? 'graph'; | ||
527 | } | ||
406 | } | 528 | } |
diff --git a/subprojects/frontend/src/editor/GeneratedModelStore.ts b/subprojects/frontend/src/editor/GeneratedModelStore.ts index f2695d9a..4af49e2c 100644 --- a/subprojects/frontend/src/editor/GeneratedModelStore.ts +++ b/subprojects/frontend/src/editor/GeneratedModelStore.ts | |||
@@ -21,7 +21,7 @@ export default class GeneratedModelStore { | |||
21 | graph: GraphStore | undefined; | 21 | graph: GraphStore | undefined; |
22 | 22 | ||
23 | constructor( | 23 | constructor( |
24 | randomSeed: number, | 24 | private readonly randomSeed: number, |
25 | private readonly editorStore: EditorStore, | 25 | private readonly editorStore: EditorStore, |
26 | ) { | 26 | ) { |
27 | const time = new Date().toLocaleTimeString(undefined, { hour12: false }); | 27 | const time = new Date().toLocaleTimeString(undefined, { hour12: false }); |
@@ -50,7 +50,8 @@ export default class GeneratedModelStore { | |||
50 | 50 | ||
51 | setSemantics(semantics: SemanticsSuccessResult): void { | 51 | setSemantics(semantics: SemanticsSuccessResult): void { |
52 | if (this.running) { | 52 | if (this.running) { |
53 | this.graph = new GraphStore(this.editorStore); | 53 | const name = `${this.editorStore.simpleNameOrFallback}_solution_${this.randomSeed}`; |
54 | this.graph = new GraphStore(this.editorStore, name); | ||
54 | this.graph.setSemantics(semantics); | 55 | this.graph.setSemantics(semantics); |
55 | } | 56 | } |
56 | } | 57 | } |
diff --git a/subprojects/frontend/src/editor/createEditorState.ts b/subprojects/frontend/src/editor/createEditorState.ts index 67b8fb9e..9b29228f 100644 --- a/subprojects/frontend/src/editor/createEditorState.ts +++ b/subprojects/frontend/src/editor/createEditorState.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -26,7 +26,7 @@ import { | |||
26 | } from '@codemirror/language'; | 26 | } from '@codemirror/language'; |
27 | import { lintKeymap, lintGutter } from '@codemirror/lint'; | 27 | import { lintKeymap, lintGutter } from '@codemirror/lint'; |
28 | import { search, searchKeymap } from '@codemirror/search'; | 28 | import { search, searchKeymap } from '@codemirror/search'; |
29 | import { EditorState } from '@codemirror/state'; | 29 | import { Compartment, EditorState, type Extension } from '@codemirror/state'; |
30 | import { | 30 | import { |
31 | drawSelection, | 31 | drawSelection, |
32 | highlightActiveLine, | 32 | highlightActiveLine, |
@@ -46,6 +46,12 @@ import exposeDiagnostics from './exposeDiagnostics'; | |||
46 | import findOccurrences from './findOccurrences'; | 46 | import findOccurrences from './findOccurrences'; |
47 | import semanticHighlighting from './semanticHighlighting'; | 47 | import semanticHighlighting from './semanticHighlighting'; |
48 | 48 | ||
49 | export const historyCompartment = new Compartment(); | ||
50 | |||
51 | export function createHistoryExtension(): Extension { | ||
52 | return history(); | ||
53 | } | ||
54 | |||
49 | export default function createEditorState( | 55 | export default function createEditorState( |
50 | initialValue: string, | 56 | initialValue: string, |
51 | store: EditorStore, | 57 | store: EditorStore, |
@@ -66,7 +72,7 @@ export default function createEditorState( | |||
66 | highlightActiveLine(), | 72 | highlightActiveLine(), |
67 | highlightActiveLineGutter(), | 73 | highlightActiveLineGutter(), |
68 | highlightSpecialChars(), | 74 | highlightSpecialChars(), |
69 | history(), | 75 | historyCompartment.of([createHistoryExtension()]), |
70 | indentOnInput(), | 76 | indentOnInput(), |
71 | rectangularSelection(), | 77 | rectangularSelection(), |
72 | search({ | 78 | search({ |
@@ -103,6 +109,9 @@ export default function createEditorState( | |||
103 | }), | 109 | }), |
104 | keymap.of([ | 110 | keymap.of([ |
105 | { key: 'Mod-Shift-f', run: () => store.formatText() }, | 111 | { key: 'Mod-Shift-f', run: () => store.formatText() }, |
112 | { key: 'Ctrl-o', run: () => store.openFile() }, | ||
113 | { key: 'Ctrl-s', run: () => store.saveFile() }, | ||
114 | { key: 'Ctrl-Shift-s', run: () => store.saveFileAs() }, | ||
106 | ...closeBracketsKeymap, | 115 | ...closeBracketsKeymap, |
107 | ...completionKeymap, | 116 | ...completionKeymap, |
108 | ...foldKeymap, | 117 | ...foldKeymap, |
diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx index 72ac58fa..cc8b5116 100644 --- a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx +++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx | |||
@@ -30,11 +30,13 @@ function DotGraphVisualizer({ | |||
30 | fitZoom, | 30 | fitZoom, |
31 | transitionTime, | 31 | transitionTime, |
32 | animateThreshold, | 32 | animateThreshold, |
33 | setSvgContainer, | ||
33 | }: { | 34 | }: { |
34 | graph: GraphStore; | 35 | graph: GraphStore; |
35 | fitZoom?: FitZoomCallback; | 36 | fitZoom?: FitZoomCallback; |
36 | transitionTime?: number; | 37 | transitionTime?: number; |
37 | animateThreshold?: number; | 38 | animateThreshold?: number; |
39 | setSvgContainer?: (container: HTMLElement | undefined) => void; | ||
38 | }): JSX.Element { | 40 | }): JSX.Element { |
39 | const transitionTimeOrDefault = | 41 | const transitionTimeOrDefault = |
40 | transitionTime ?? DotGraphVisualizer.defaultProps.transitionTime; | 42 | transitionTime ?? DotGraphVisualizer.defaultProps.transitionTime; |
@@ -48,6 +50,7 @@ function DotGraphVisualizer({ | |||
48 | 50 | ||
49 | const setElement = useCallback( | 51 | const setElement = useCallback( |
50 | (element: HTMLDivElement | null) => { | 52 | (element: HTMLDivElement | null) => { |
53 | setSvgContainer?.(element ?? undefined); | ||
51 | if (disposerRef.current !== undefined) { | 54 | if (disposerRef.current !== undefined) { |
52 | disposerRef.current(); | 55 | disposerRef.current(); |
53 | disposerRef.current = undefined; | 56 | disposerRef.current = undefined; |
@@ -147,6 +150,7 @@ function DotGraphVisualizer({ | |||
147 | transitionTimeOrDefault, | 150 | transitionTimeOrDefault, |
148 | animateThresholdOrDefault, | 151 | animateThresholdOrDefault, |
149 | animate, | 152 | animate, |
153 | setSvgContainer, | ||
150 | ], | 154 | ], |
151 | ); | 155 | ); |
152 | 156 | ||
@@ -157,6 +161,7 @@ DotGraphVisualizer.defaultProps = { | |||
157 | fitZoom: undefined, | 161 | fitZoom: undefined, |
158 | transitionTime: 250, | 162 | transitionTime: 250, |
159 | animateThreshold: 100, | 163 | animateThreshold: 100, |
164 | setSvgContainer: undefined, | ||
160 | }; | 165 | }; |
161 | 166 | ||
162 | export default observer(DotGraphVisualizer); | 167 | export default observer(DotGraphVisualizer); |
diff --git a/subprojects/frontend/src/graph/GraphArea.tsx b/subprojects/frontend/src/graph/GraphArea.tsx index d5801b9a..b5d93aef 100644 --- a/subprojects/frontend/src/graph/GraphArea.tsx +++ b/subprojects/frontend/src/graph/GraphArea.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -7,18 +7,21 @@ | |||
7 | import Box from '@mui/material/Box'; | 7 | import Box from '@mui/material/Box'; |
8 | import { useTheme } from '@mui/material/styles'; | 8 | import { useTheme } from '@mui/material/styles'; |
9 | import { observer } from 'mobx-react-lite'; | 9 | import { observer } from 'mobx-react-lite'; |
10 | import { useState } from 'react'; | ||
10 | import { useResizeDetector } from 'react-resize-detector'; | 11 | import { useResizeDetector } from 'react-resize-detector'; |
11 | 12 | ||
12 | import DotGraphVisualizer from './DotGraphVisualizer'; | 13 | import DotGraphVisualizer from './DotGraphVisualizer'; |
13 | import type GraphStore from './GraphStore'; | 14 | import type GraphStore from './GraphStore'; |
14 | import VisibilityPanel from './VisibilityPanel'; | 15 | import VisibilityPanel from './VisibilityPanel'; |
15 | import ZoomCanvas from './ZoomCanvas'; | 16 | import ZoomCanvas from './ZoomCanvas'; |
17 | import ExportPanel from './export/ExportPanel'; | ||
16 | 18 | ||
17 | function GraphArea({ graph }: { graph: GraphStore }): JSX.Element { | 19 | function GraphArea({ graph }: { graph: GraphStore }): JSX.Element { |
18 | const { breakpoints } = useTheme(); | 20 | const { breakpoints } = useTheme(); |
19 | const { ref, width, height } = useResizeDetector({ | 21 | const { ref, width, height } = useResizeDetector({ |
20 | refreshMode: 'debounce', | 22 | refreshMode: 'debounce', |
21 | }); | 23 | }); |
24 | const [svgContainer, setSvgContainer] = useState<HTMLElement | undefined>(); | ||
22 | 25 | ||
23 | const breakpoint = breakpoints.values.sm; | 26 | const breakpoint = breakpoints.values.sm; |
24 | const dialog = | 27 | const dialog = |
@@ -36,9 +39,16 @@ function GraphArea({ graph }: { graph: GraphStore }): JSX.Element { | |||
36 | ref={ref} | 39 | ref={ref} |
37 | > | 40 | > |
38 | <ZoomCanvas> | 41 | <ZoomCanvas> |
39 | {(fitZoom) => <DotGraphVisualizer graph={graph} fitZoom={fitZoom} />} | 42 | {(fitZoom) => ( |
43 | <DotGraphVisualizer | ||
44 | graph={graph} | ||
45 | fitZoom={fitZoom} | ||
46 | setSvgContainer={setSvgContainer} | ||
47 | /> | ||
48 | )} | ||
40 | </ZoomCanvas> | 49 | </ZoomCanvas> |
41 | <VisibilityPanel graph={graph} dialog={dialog} /> | 50 | <VisibilityPanel graph={graph} dialog={dialog} /> |
51 | <ExportPanel graph={graph} svgContainer={svgContainer} dialog={dialog} /> | ||
42 | </Box> | 52 | </Box> |
43 | ); | 53 | ); |
44 | } | 54 | } |
diff --git a/subprojects/frontend/src/graph/GraphStore.ts b/subprojects/frontend/src/graph/GraphStore.ts index 58c4422d..d9282326 100644 --- a/subprojects/frontend/src/graph/GraphStore.ts +++ b/subprojects/frontend/src/graph/GraphStore.ts | |||
@@ -66,7 +66,10 @@ export default class GraphStore { | |||
66 | 66 | ||
67 | selectedSymbol: RelationMetadata | undefined; | 67 | selectedSymbol: RelationMetadata | undefined; |
68 | 68 | ||
69 | constructor(private readonly editorStore: EditorStore) { | 69 | constructor( |
70 | private readonly editorStore: EditorStore, | ||
71 | private readonly nameOverride?: string, | ||
72 | ) { | ||
70 | makeAutoObservable<GraphStore, 'editorStore'>(this, { | 73 | makeAutoObservable<GraphStore, 'editorStore'>(this, { |
71 | editorStore: false, | 74 | editorStore: false, |
72 | semantics: observable.ref, | 75 | semantics: observable.ref, |
@@ -190,4 +193,8 @@ export default class GraphStore { | |||
190 | get colorNodes(): boolean { | 193 | get colorNodes(): boolean { |
191 | return this.editorStore.colorIdentifiers; | 194 | return this.editorStore.colorIdentifiers; |
192 | } | 195 | } |
196 | |||
197 | get name(): string { | ||
198 | return this.nameOverride ?? this.editorStore.simpleNameOrFallback; | ||
199 | } | ||
193 | } | 200 | } |
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx index 7334f559..34954345 100644 --- a/subprojects/frontend/src/graph/GraphTheme.tsx +++ b/subprojects/frontend/src/graph/GraphTheme.tsx | |||
@@ -27,10 +27,10 @@ function createEdgeColor( | |||
27 | '& text': { | 27 | '& text': { |
28 | fill: stroke, | 28 | fill: stroke, |
29 | }, | 29 | }, |
30 | '& [stroke="black"]': { | 30 | '.edge-line': { |
31 | stroke, | 31 | stroke, |
32 | }, | 32 | }, |
33 | '& [fill="black"]': { | 33 | '.edge-arrow': { |
34 | fill: fill ?? stroke, | 34 | fill: fill ?? stroke, |
35 | }, | 35 | }, |
36 | }, | 36 | }, |
@@ -43,50 +43,72 @@ function createTypeHashStyles(theme: Theme, colorNodes: boolean): CSSObject { | |||
43 | } | 43 | } |
44 | const result: CSSObject = {}; | 44 | const result: CSSObject = {}; |
45 | range(theme.palette.highlight.typeHash.length).forEach((i) => { | 45 | range(theme.palette.highlight.typeHash.length).forEach((i) => { |
46 | result[`.node-typeHash-${i}`] = { | 46 | result[`.node-typeHash-${i} .node-header`] = { |
47 | '& [fill="green"]': { | 47 | fill: theme.palette.highlight.typeHash[i]?.box, |
48 | fill: theme.palette.highlight.typeHash[i]?.box, | ||
49 | }, | ||
50 | }; | 48 | }; |
51 | }); | 49 | }); |
52 | return result; | 50 | return result; |
53 | } | 51 | } |
54 | 52 | ||
55 | export default styled('div', { | 53 | function iconStyle( |
56 | name: 'GraphTheme', | 54 | svg: string, |
57 | })<{ colorNodes: boolean }>(({ theme, colorNodes }) => ({ | 55 | color: string, |
58 | '& svg': { | 56 | noEmbedIcons?: boolean, |
59 | userSelect: 'none', | 57 | ): CSSObject { |
58 | if (noEmbedIcons) { | ||
59 | return { | ||
60 | fill: color, | ||
61 | }; | ||
62 | } | ||
63 | return { | ||
64 | maskImage: svgURL(svg), | ||
65 | background: color, | ||
66 | }; | ||
67 | } | ||
68 | |||
69 | export function createGraphTheme({ | ||
70 | theme, | ||
71 | colorNodes, | ||
72 | noEmbedIcons, | ||
73 | }: { | ||
74 | theme: Theme; | ||
75 | colorNodes: boolean; | ||
76 | noEmbedIcons?: boolean; | ||
77 | }): CSSObject { | ||
78 | const shadowAlapha = theme.palette.mode === 'dark' ? 0.32 : 0.24; | ||
79 | |||
80 | return { | ||
60 | '.node': { | 81 | '.node': { |
61 | '& text': { | 82 | '& text': { |
62 | fontFamily: theme.typography.fontFamily, | 83 | fontFamily: theme.typography.fontFamily, |
63 | fill: theme.palette.text.primary, | 84 | fill: theme.palette.text.primary, |
64 | }, | 85 | }, |
65 | '& [stroke="black"]': { | 86 | '.node-outline': { |
66 | stroke: theme.palette.text.primary, | 87 | stroke: theme.palette.text.primary, |
67 | }, | 88 | }, |
68 | '& [fill="green"]': { | 89 | '.node-header': { |
69 | fill: | 90 | fill: |
70 | theme.palette.mode === 'dark' | 91 | theme.palette.mode === 'dark' |
71 | ? theme.palette.primary.dark | 92 | ? theme.palette.primary.dark |
72 | : theme.palette.primary.light, | 93 | : theme.palette.primary.light, |
73 | }, | 94 | }, |
74 | '& [fill="white"]': { | 95 | '.node-bg': { |
75 | fill: theme.palette.background.default, | 96 | fill: theme.palette.background.default, |
76 | }, | 97 | }, |
77 | }, | 98 | }, |
78 | '.node-INDIVIDUAL': { | 99 | '.node-INDIVIDUAL .node-outline': { |
79 | '& [stroke="black"]': { | 100 | strokeWidth: 2, |
80 | strokeWidth: 2, | ||
81 | }, | ||
82 | }, | ||
83 | '.node-shadow[fill="white"]': { | ||
84 | fill: alpha( | ||
85 | theme.palette.text.primary, | ||
86 | theme.palette.mode === 'dark' ? 0.32 : 0.24, | ||
87 | ), | ||
88 | }, | 101 | }, |
89 | '.node-exists-UNKNOWN [stroke="black"]': { | 102 | '.node-shadow.node-bg': noEmbedIcons |
103 | ? { | ||
104 | // Inkscape can't handle opacity in exported SVG. | ||
105 | fill: theme.palette.text.primary, | ||
106 | opacity: shadowAlapha, | ||
107 | } | ||
108 | : { | ||
109 | fill: alpha(theme.palette.text.primary, shadowAlapha), | ||
110 | }, | ||
111 | '.node-exists-UNKNOWN .node-outline': { | ||
90 | strokeDasharray: '5 2', | 112 | strokeDasharray: '5 2', |
91 | }, | 113 | }, |
92 | ...createTypeHashStyles(theme, colorNodes), | 114 | ...createTypeHashStyles(theme, colorNodes), |
@@ -95,39 +117,47 @@ export default styled('div', { | |||
95 | fontFamily: theme.typography.fontFamily, | 117 | fontFamily: theme.typography.fontFamily, |
96 | fill: theme.palette.text.primary, | 118 | fill: theme.palette.text.primary, |
97 | }, | 119 | }, |
98 | '& [stroke="black"]': { | 120 | '.edge-line': { |
99 | stroke: theme.palette.text.primary, | 121 | stroke: theme.palette.text.primary, |
100 | }, | 122 | }, |
101 | '& [fill="black"]': { | 123 | '.edge-arrow': { |
102 | fill: theme.palette.text.primary, | 124 | fill: theme.palette.text.primary, |
103 | }, | 125 | }, |
104 | }, | 126 | }, |
105 | ...createEdgeColor('UNKNOWN', theme.palette.text.secondary, 'none'), | 127 | ...createEdgeColor('UNKNOWN', theme.palette.text.secondary, 'none'), |
106 | ...createEdgeColor('ERROR', theme.palette.error.main), | 128 | ...createEdgeColor('ERROR', theme.palette.error.main), |
107 | '.icon': { | 129 | ...(noEmbedIcons |
108 | maskSize: '12px 12px', | 130 | ? {} |
109 | maskPosition: '50% 50%', | 131 | : { |
110 | maskRepeat: 'no-repeat', | 132 | '.icon': { |
111 | width: '100%', | 133 | maskSize: '12px 12px', |
112 | height: '100%', | 134 | maskPosition: '50% 50%', |
113 | }, | 135 | maskRepeat: 'no-repeat', |
114 | '.icon-TRUE': { | 136 | width: '100%', |
115 | maskImage: svgURL(labelSVG), | 137 | height: '100%', |
116 | background: theme.palette.text.primary, | 138 | }, |
117 | }, | 139 | }), |
118 | '.icon-UNKNOWN': { | 140 | '.icon-TRUE': iconStyle(labelSVG, theme.palette.text.primary, noEmbedIcons), |
119 | maskImage: svgURL(labelOutlinedSVG), | 141 | '.icon-UNKNOWN': iconStyle( |
120 | background: theme.palette.text.secondary, | 142 | labelOutlinedSVG, |
121 | }, | 143 | theme.palette.text.secondary, |
122 | '.icon-ERROR': { | 144 | noEmbedIcons, |
123 | maskImage: svgURL(cancelSVG), | 145 | ), |
124 | background: theme.palette.error.main, | 146 | '.icon-ERROR': iconStyle(cancelSVG, theme.palette.error.main, noEmbedIcons), |
125 | }, | ||
126 | 'text.label-UNKNOWN': { | 147 | 'text.label-UNKNOWN': { |
127 | fill: theme.palette.text.secondary, | 148 | fill: theme.palette.text.secondary, |
128 | }, | 149 | }, |
129 | 'text.label-ERROR': { | 150 | 'text.label-ERROR': { |
130 | fill: theme.palette.error.main, | 151 | fill: theme.palette.error.main, |
131 | }, | 152 | }, |
153 | }; | ||
154 | } | ||
155 | |||
156 | export default styled('div', { | ||
157 | name: 'GraphTheme', | ||
158 | })<{ colorNodes: boolean }>((args) => ({ | ||
159 | '& svg': { | ||
160 | userSelect: 'none', | ||
161 | ...createGraphTheme(args), | ||
132 | }, | 162 | }, |
133 | })); | 163 | })); |
diff --git a/subprojects/frontend/src/graph/SlideInDialog.tsx b/subprojects/frontend/src/graph/SlideInDialog.tsx new file mode 100644 index 00000000..d9060fb0 --- /dev/null +++ b/subprojects/frontend/src/graph/SlideInDialog.tsx | |||
@@ -0,0 +1,109 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import CloseIcon from '@mui/icons-material/Close'; | ||
8 | import Button from '@mui/material/Button'; | ||
9 | import IconButton from '@mui/material/IconButton'; | ||
10 | import Typography from '@mui/material/Typography'; | ||
11 | import { styled } from '@mui/material/styles'; | ||
12 | import React, { useId } from 'react'; | ||
13 | |||
14 | const SlideInDialogRoot = styled('div', { | ||
15 | name: 'SlideInDialog-Root', | ||
16 | shouldForwardProp: (propName) => propName !== 'dialog', | ||
17 | })<{ dialog: boolean }>(({ theme, dialog }) => { | ||
18 | return { | ||
19 | maxHeight: '100%', | ||
20 | maxWidth: '100%', | ||
21 | overflow: 'hidden', | ||
22 | display: 'flex', | ||
23 | flexDirection: 'column', | ||
24 | '.SlideInDialog-title': { | ||
25 | display: 'flex', | ||
26 | flexDirection: 'row', | ||
27 | alignItems: 'center', | ||
28 | padding: theme.spacing(1), | ||
29 | paddingLeft: theme.spacing(2), | ||
30 | borderBottom: `1px solid ${theme.palette.divider}`, | ||
31 | '& h2': { | ||
32 | flexGrow: 1, | ||
33 | }, | ||
34 | '.MuiIconButton-root': { | ||
35 | flexGrow: 0, | ||
36 | flexShrink: 0, | ||
37 | marginLeft: theme.spacing(2), | ||
38 | }, | ||
39 | }, | ||
40 | '.MuiFormControlLabel-root': { | ||
41 | marginLeft: 0, | ||
42 | paddingTop: theme.spacing(1), | ||
43 | paddingLeft: theme.spacing(1), | ||
44 | '& + .MuiFormControlLabel-root': { | ||
45 | paddingTop: 0, | ||
46 | }, | ||
47 | }, | ||
48 | '.SlideInDialog-buttons': { | ||
49 | padding: theme.spacing(1), | ||
50 | display: 'flex', | ||
51 | flexDirection: 'row', | ||
52 | justifyContent: 'flex-end', | ||
53 | ...(dialog | ||
54 | ? { | ||
55 | marginTop: theme.spacing(1), | ||
56 | borderTop: `1px solid ${theme.palette.divider}`, | ||
57 | } | ||
58 | : {}), | ||
59 | }, | ||
60 | }; | ||
61 | }); | ||
62 | |||
63 | export default function SlideInDialog({ | ||
64 | close, | ||
65 | dialog, | ||
66 | title, | ||
67 | buttons, | ||
68 | children, | ||
69 | }: { | ||
70 | close: () => void; | ||
71 | dialog?: boolean; | ||
72 | title: string; | ||
73 | buttons: React.ReactNode | ((close: () => void) => React.ReactNode); | ||
74 | children?: React.ReactNode; | ||
75 | }): JSX.Element { | ||
76 | const titleId = useId(); | ||
77 | |||
78 | return ( | ||
79 | <SlideInDialogRoot | ||
80 | dialog={dialog ?? SlideInDialog.defaultProps.dialog} | ||
81 | aria-labelledby={dialog ? titleId : undefined} | ||
82 | > | ||
83 | {dialog && ( | ||
84 | <div className="SlideInDialog-title"> | ||
85 | <Typography variant="h6" component="h2" id={titleId}> | ||
86 | {title} | ||
87 | </Typography> | ||
88 | <IconButton aria-label="Close" onClick={close}> | ||
89 | <CloseIcon /> | ||
90 | </IconButton> | ||
91 | </div> | ||
92 | )} | ||
93 | {children} | ||
94 | <div className="SlideInDialog-buttons"> | ||
95 | {typeof buttons === 'function' ? buttons(close) : buttons} | ||
96 | {!dialog && ( | ||
97 | <Button color="inherit" onClick={close}> | ||
98 | Close | ||
99 | </Button> | ||
100 | )} | ||
101 | </div> | ||
102 | </SlideInDialogRoot> | ||
103 | ); | ||
104 | } | ||
105 | |||
106 | SlideInDialog.defaultProps = { | ||
107 | dialog: false, | ||
108 | children: undefined, | ||
109 | }; | ||
diff --git a/subprojects/frontend/src/graph/SlideInPanel.tsx b/subprojects/frontend/src/graph/SlideInPanel.tsx new file mode 100644 index 00000000..2c189b5b --- /dev/null +++ b/subprojects/frontend/src/graph/SlideInPanel.tsx | |||
@@ -0,0 +1,97 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import Dialog from '@mui/material/Dialog'; | ||
8 | import IconButton from '@mui/material/IconButton'; | ||
9 | import Paper from '@mui/material/Paper'; | ||
10 | import Slide from '@mui/material/Slide'; | ||
11 | import { styled } from '@mui/material/styles'; | ||
12 | import React, { useCallback, useId, useState } from 'react'; | ||
13 | |||
14 | import SlideInDialog from './SlideInDialog'; | ||
15 | |||
16 | const SlideInPanelRoot = styled('div', { | ||
17 | name: 'SlideInPanel-Root', | ||
18 | shouldForwardProp: (propName) => propName !== 'anchor', | ||
19 | })<{ anchor: 'left' | 'right' }>(({ theme, anchor }) => ({ | ||
20 | position: 'absolute', | ||
21 | padding: theme.spacing(1), | ||
22 | top: 0, | ||
23 | [anchor]: 0, | ||
24 | maxHeight: '100%', | ||
25 | maxWidth: '100%', | ||
26 | overflow: 'hidden', | ||
27 | display: 'flex', | ||
28 | flexDirection: 'column', | ||
29 | alignItems: anchor === 'left' ? 'start' : 'end', | ||
30 | '.SlideInPanel-drawer': { | ||
31 | overflow: 'hidden', | ||
32 | display: 'flex', | ||
33 | maxWidth: '100%', | ||
34 | margin: theme.spacing(1), | ||
35 | }, | ||
36 | })); | ||
37 | |||
38 | export default function SlideInPanel({ | ||
39 | anchor, | ||
40 | dialog, | ||
41 | title, | ||
42 | icon, | ||
43 | iconLabel, | ||
44 | buttons, | ||
45 | children, | ||
46 | }: { | ||
47 | anchor: 'left' | 'right'; | ||
48 | dialog: boolean; | ||
49 | title: string; | ||
50 | icon: (show: boolean) => React.ReactNode; | ||
51 | iconLabel: string; | ||
52 | buttons: React.ReactNode | ((close: () => void) => React.ReactNode); | ||
53 | children?: React.ReactNode; | ||
54 | }): JSX.Element { | ||
55 | const id = useId(); | ||
56 | const [show, setShow] = useState(false); | ||
57 | const close = useCallback(() => setShow(false), []); | ||
58 | |||
59 | return ( | ||
60 | <SlideInPanelRoot anchor={anchor}> | ||
61 | <IconButton | ||
62 | role="switch" | ||
63 | aria-checked={show} | ||
64 | aria-controls={dialog ? undefined : id} | ||
65 | aria-label={iconLabel} | ||
66 | onClick={() => setShow(!show)} | ||
67 | > | ||
68 | {icon(show)} | ||
69 | </IconButton> | ||
70 | {dialog ? ( | ||
71 | <Dialog open={show} onClose={close} maxWidth="xl"> | ||
72 | <SlideInDialog close={close} dialog title={title} buttons={buttons}> | ||
73 | {children} | ||
74 | </SlideInDialog> | ||
75 | </Dialog> | ||
76 | ) : ( | ||
77 | <Slide | ||
78 | direction={anchor === 'left' ? 'right' : 'left'} | ||
79 | in={show} | ||
80 | id={id} | ||
81 | mountOnEnter | ||
82 | unmountOnExit | ||
83 | > | ||
84 | <Paper className="SlideInPanel-drawer" elevation={4}> | ||
85 | <SlideInDialog close={close} title={title} buttons={buttons}> | ||
86 | {children} | ||
87 | </SlideInDialog> | ||
88 | </Paper> | ||
89 | </Slide> | ||
90 | )} | ||
91 | </SlideInPanelRoot> | ||
92 | ); | ||
93 | } | ||
94 | |||
95 | SlideInPanel.defaultProps = { | ||
96 | children: undefined, | ||
97 | }; | ||
diff --git a/subprojects/frontend/src/graph/VisibilityDialog.tsx b/subprojects/frontend/src/graph/VisibilityDialog.tsx deleted file mode 100644 index bfdcd59f..00000000 --- a/subprojects/frontend/src/graph/VisibilityDialog.tsx +++ /dev/null | |||
@@ -1,318 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import CloseIcon from '@mui/icons-material/Close'; | ||
8 | import FilterListIcon from '@mui/icons-material/FilterList'; | ||
9 | import LabelIcon from '@mui/icons-material/Label'; | ||
10 | import LabelOutlinedIcon from '@mui/icons-material/LabelOutlined'; | ||
11 | import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied'; | ||
12 | import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; | ||
13 | import Button from '@mui/material/Button'; | ||
14 | import Checkbox from '@mui/material/Checkbox'; | ||
15 | import FormControlLabel from '@mui/material/FormControlLabel'; | ||
16 | import IconButton from '@mui/material/IconButton'; | ||
17 | import Switch from '@mui/material/Switch'; | ||
18 | import Typography from '@mui/material/Typography'; | ||
19 | import { styled } from '@mui/material/styles'; | ||
20 | import { observer } from 'mobx-react-lite'; | ||
21 | import { useId } from 'react'; | ||
22 | |||
23 | import type GraphStore from './GraphStore'; | ||
24 | import { isVisibilityAllowed } from './GraphStore'; | ||
25 | import RelationName from './RelationName'; | ||
26 | |||
27 | const VisibilityDialogRoot = styled('div', { | ||
28 | name: 'VisibilityDialog-Root', | ||
29 | shouldForwardProp: (propName) => propName !== 'dialog', | ||
30 | })<{ dialog: boolean }>(({ theme, dialog }) => { | ||
31 | const overlayOpacity = dialog ? 0.16 : 0.09; | ||
32 | return { | ||
33 | maxHeight: '100%', | ||
34 | maxWidth: '100%', | ||
35 | overflow: 'hidden', | ||
36 | display: 'flex', | ||
37 | flexDirection: 'column', | ||
38 | '.VisibilityDialog-title': { | ||
39 | display: 'flex', | ||
40 | flexDirection: 'row', | ||
41 | alignItems: 'center', | ||
42 | padding: theme.spacing(1), | ||
43 | paddingLeft: theme.spacing(2), | ||
44 | borderBottom: `1px solid ${theme.palette.divider}`, | ||
45 | '& h2': { | ||
46 | flexGrow: 1, | ||
47 | }, | ||
48 | '.MuiIconButton-root': { | ||
49 | flexGrow: 0, | ||
50 | flexShrink: 0, | ||
51 | marginLeft: theme.spacing(2), | ||
52 | }, | ||
53 | }, | ||
54 | '.MuiFormControlLabel-root': { | ||
55 | marginLeft: 0, | ||
56 | paddingTop: theme.spacing(1), | ||
57 | paddingLeft: theme.spacing(1), | ||
58 | '& + .MuiFormControlLabel-root': { | ||
59 | paddingTop: 0, | ||
60 | }, | ||
61 | }, | ||
62 | '.VisibilityDialog-scroll': { | ||
63 | display: 'flex', | ||
64 | flexDirection: 'column', | ||
65 | height: 'auto', | ||
66 | overflowX: 'hidden', | ||
67 | overflowY: 'auto', | ||
68 | margin: `0 ${theme.spacing(2)}`, | ||
69 | '& table': { | ||
70 | // We use flexbox instead of `display: table` to get proper text-overflow | ||
71 | // behavior for overly long relation names. | ||
72 | display: 'flex', | ||
73 | flexDirection: 'column', | ||
74 | }, | ||
75 | '& thead, & tbody': { | ||
76 | display: 'flex', | ||
77 | flexDirection: 'column', | ||
78 | }, | ||
79 | '& thead': { | ||
80 | position: 'sticky', | ||
81 | top: 0, | ||
82 | zIndex: 999, | ||
83 | backgroundColor: theme.palette.background.paper, | ||
84 | ...(theme.palette.mode === 'dark' | ||
85 | ? { | ||
86 | // In dark mode, MUI Paper gets a lighter overlay. | ||
87 | backgroundImage: `linear-gradient( | ||
88 | rgba(255, 255, 255, ${overlayOpacity}), | ||
89 | rgba(255, 255, 255, ${overlayOpacity}) | ||
90 | )`, | ||
91 | } | ||
92 | : {}), | ||
93 | '& tr': { | ||
94 | height: '44px', | ||
95 | }, | ||
96 | }, | ||
97 | '& tr': { | ||
98 | display: 'flex', | ||
99 | flexDirection: 'row', | ||
100 | maxWidth: '100%', | ||
101 | }, | ||
102 | '& tbody tr': { | ||
103 | transition: theme.transitions.create('background', { | ||
104 | duration: theme.transitions.duration.shortest, | ||
105 | }), | ||
106 | '&:hover': { | ||
107 | background: theme.palette.action.hover, | ||
108 | '@media (hover: none)': { | ||
109 | background: 'transparent', | ||
110 | }, | ||
111 | }, | ||
112 | }, | ||
113 | '& th, & td': { | ||
114 | display: 'flex', | ||
115 | flexDirection: 'row', | ||
116 | alignItems: 'center', | ||
117 | justifyContent: 'center', | ||
118 | // Set width in advance, since we can't rely on `display: table-cell`. | ||
119 | width: '44px', | ||
120 | }, | ||
121 | '& th:nth-of-type(3), & td:nth-of-type(3)': { | ||
122 | justifyContent: 'start', | ||
123 | paddingLeft: theme.spacing(1), | ||
124 | paddingRight: theme.spacing(2), | ||
125 | // Only let the last column grow or shrink. | ||
126 | flexGrow: 1, | ||
127 | flexShrink: 1, | ||
128 | // Compute the maximum available space in advance to let the text overflow. | ||
129 | maxWidth: 'calc(100% - 88px)', | ||
130 | width: 'min-content', | ||
131 | }, | ||
132 | '& td:nth-of-type(3)': { | ||
133 | cursor: 'pointer', | ||
134 | userSelect: 'none', | ||
135 | WebkitTapHighlightColor: 'transparent', | ||
136 | }, | ||
137 | |||
138 | '& thead th, .VisibilityDialog-custom tr:last-child td': { | ||
139 | borderBottom: `1px solid ${theme.palette.divider}`, | ||
140 | }, | ||
141 | }, | ||
142 | // Hack to apply `text-overflow`. | ||
143 | '.VisibilityDialog-nowrap': { | ||
144 | maxWidth: '100%', | ||
145 | overflow: 'hidden', | ||
146 | wordWrap: 'nowrap', | ||
147 | textOverflow: 'ellipsis', | ||
148 | }, | ||
149 | '.VisibilityDialog-buttons': { | ||
150 | padding: theme.spacing(1), | ||
151 | display: 'flex', | ||
152 | flexDirection: 'row', | ||
153 | justifyContent: 'flex-end', | ||
154 | ...(dialog | ||
155 | ? { | ||
156 | marginTop: theme.spacing(1), | ||
157 | borderTop: `1px solid ${theme.palette.divider}`, | ||
158 | } | ||
159 | : {}), | ||
160 | }, | ||
161 | '.VisibilityDialog-empty': { | ||
162 | display: 'flex', | ||
163 | flexDirection: 'column', | ||
164 | alignItems: 'center', | ||
165 | color: theme.palette.text.secondary, | ||
166 | }, | ||
167 | '.VisibilityDialog-emptyIcon': { | ||
168 | fontSize: '6rem', | ||
169 | marginBottom: theme.spacing(1), | ||
170 | }, | ||
171 | }; | ||
172 | }); | ||
173 | |||
174 | function VisibilityDialog({ | ||
175 | graph, | ||
176 | close, | ||
177 | dialog, | ||
178 | }: { | ||
179 | graph: GraphStore; | ||
180 | close: () => void; | ||
181 | dialog?: boolean; | ||
182 | }): JSX.Element { | ||
183 | const titleId = useId(); | ||
184 | |||
185 | const builtinRows: JSX.Element[] = []; | ||
186 | const rows: JSX.Element[] = []; | ||
187 | graph.relationMetadata.forEach((metadata, name) => { | ||
188 | if (!isVisibilityAllowed(metadata, 'must')) { | ||
189 | return; | ||
190 | } | ||
191 | const visibility = graph.getVisibility(name); | ||
192 | const row = ( | ||
193 | <tr key={metadata.name}> | ||
194 | <td> | ||
195 | <Checkbox | ||
196 | checked={visibility !== 'none'} | ||
197 | aria-label={`Show true and error values of ${metadata.simpleName}`} | ||
198 | onClick={() => | ||
199 | graph.setVisibility(name, visibility === 'none' ? 'must' : 'none') | ||
200 | } | ||
201 | /> | ||
202 | </td> | ||
203 | <td> | ||
204 | <Checkbox | ||
205 | checked={visibility === 'all'} | ||
206 | disabled={!isVisibilityAllowed(metadata, 'all')} | ||
207 | aria-label={`Show all values of ${metadata.simpleName}`} | ||
208 | onClick={() => | ||
209 | graph.setVisibility(name, visibility === 'all' ? 'must' : 'all') | ||
210 | } | ||
211 | /> | ||
212 | </td> | ||
213 | <td | ||
214 | onClick={() => graph.cycleVisibility(name)} | ||
215 | aria-label="Toggle visiblity" | ||
216 | > | ||
217 | <div className="VisibilityDialog-nowrap"> | ||
218 | <RelationName metadata={metadata} abbreviate={graph.abbreviate} /> | ||
219 | </div> | ||
220 | </td> | ||
221 | </tr> | ||
222 | ); | ||
223 | if (name.startsWith('builtin::')) { | ||
224 | builtinRows.push(row); | ||
225 | } else { | ||
226 | rows.push(row); | ||
227 | } | ||
228 | }); | ||
229 | |||
230 | const hasRows = rows.length > 0 || builtinRows.length > 0; | ||
231 | |||
232 | return ( | ||
233 | <VisibilityDialogRoot | ||
234 | dialog={dialog ?? VisibilityDialog.defaultProps.dialog} | ||
235 | aria-labelledby={dialog ? titleId : undefined} | ||
236 | > | ||
237 | {dialog && ( | ||
238 | <div className="VisibilityDialog-title"> | ||
239 | <Typography variant="h6" component="h2" id={titleId}> | ||
240 | Customize view | ||
241 | </Typography> | ||
242 | <IconButton aria-label="Close" onClick={close}> | ||
243 | <CloseIcon /> | ||
244 | </IconButton> | ||
245 | </div> | ||
246 | )} | ||
247 | <FormControlLabel | ||
248 | control={ | ||
249 | <Switch | ||
250 | checked={!graph.abbreviate} | ||
251 | onClick={() => graph.toggleAbbrevaite()} | ||
252 | /> | ||
253 | } | ||
254 | label="Fully qualified names" | ||
255 | /> | ||
256 | <FormControlLabel | ||
257 | control={ | ||
258 | <Switch checked={graph.scopes} onClick={() => graph.toggleScopes()} /> | ||
259 | } | ||
260 | label="Object scopes" | ||
261 | /> | ||
262 | <div className="VisibilityDialog-scroll"> | ||
263 | {hasRows ? ( | ||
264 | <table cellSpacing={0}> | ||
265 | <thead> | ||
266 | <tr> | ||
267 | <th aria-label="Show true and error values"> | ||
268 | <LabelIcon /> | ||
269 | </th> | ||
270 | <th aria-label="Show unknown values"> | ||
271 | <LabelOutlinedIcon /> | ||
272 | </th> | ||
273 | <th>Symbol</th> | ||
274 | </tr> | ||
275 | </thead> | ||
276 | <tbody className="VisibilityDialog-custom">{...rows}</tbody> | ||
277 | <tbody className="VisibilityDialog-builtin">{...builtinRows}</tbody> | ||
278 | </table> | ||
279 | ) : ( | ||
280 | <div className="VisibilityDialog-empty"> | ||
281 | <SentimentVeryDissatisfiedIcon | ||
282 | className="VisibilityDialog-emptyIcon" | ||
283 | fontSize="inherit" | ||
284 | /> | ||
285 | <div>Partial model is empty</div> | ||
286 | </div> | ||
287 | )} | ||
288 | </div> | ||
289 | <div className="VisibilityDialog-buttons"> | ||
290 | <Button | ||
291 | color="inherit" | ||
292 | onClick={() => graph.hideAll()} | ||
293 | startIcon={<VisibilityOffIcon />} | ||
294 | > | ||
295 | Hide all | ||
296 | </Button> | ||
297 | <Button | ||
298 | color="inherit" | ||
299 | onClick={() => graph.resetFilter()} | ||
300 | startIcon={<FilterListIcon />} | ||
301 | > | ||
302 | Reset filter | ||
303 | </Button> | ||
304 | {!dialog && ( | ||
305 | <Button color="inherit" onClick={close}> | ||
306 | Close | ||
307 | </Button> | ||
308 | )} | ||
309 | </div> | ||
310 | </VisibilityDialogRoot> | ||
311 | ); | ||
312 | } | ||
313 | |||
314 | VisibilityDialog.defaultProps = { | ||
315 | dialog: false, | ||
316 | }; | ||
317 | |||
318 | export default observer(VisibilityDialog); | ||
diff --git a/subprojects/frontend/src/graph/VisibilityPanel.tsx b/subprojects/frontend/src/graph/VisibilityPanel.tsx index 20c4ffca..210ff5d5 100644 --- a/subprojects/frontend/src/graph/VisibilityPanel.tsx +++ b/subprojects/frontend/src/graph/VisibilityPanel.tsx | |||
@@ -1,43 +1,133 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | 6 | ||
7 | import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; | 7 | import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; |
8 | import FilterListIcon from '@mui/icons-material/FilterList'; | ||
9 | import LabelIcon from '@mui/icons-material/Label'; | ||
10 | import LabelOutlinedIcon from '@mui/icons-material/LabelOutlined'; | ||
11 | import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied'; | ||
8 | import TuneIcon from '@mui/icons-material/Tune'; | 12 | import TuneIcon from '@mui/icons-material/Tune'; |
13 | import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; | ||
9 | import Badge from '@mui/material/Badge'; | 14 | import Badge from '@mui/material/Badge'; |
10 | import Dialog from '@mui/material/Dialog'; | 15 | import Button from '@mui/material/Button'; |
11 | import IconButton from '@mui/material/IconButton'; | 16 | import Checkbox from '@mui/material/Checkbox'; |
12 | import Paper from '@mui/material/Paper'; | 17 | import FormControlLabel from '@mui/material/FormControlLabel'; |
13 | import Slide from '@mui/material/Slide'; | 18 | import Switch from '@mui/material/Switch'; |
14 | import { styled } from '@mui/material/styles'; | 19 | import { styled } from '@mui/material/styles'; |
15 | import { observer } from 'mobx-react-lite'; | 20 | import { observer } from 'mobx-react-lite'; |
16 | import { useCallback, useId, useState } from 'react'; | 21 | import { useCallback } from 'react'; |
17 | 22 | ||
18 | import type GraphStore from './GraphStore'; | 23 | import type GraphStore from './GraphStore'; |
19 | import VisibilityDialog from './VisibilityDialog'; | 24 | import { isVisibilityAllowed } from './GraphStore'; |
25 | import RelationName from './RelationName'; | ||
26 | import SlideInPanel from './SlideInPanel'; | ||
20 | 27 | ||
21 | const VisibilityPanelRoot = styled('div', { | 28 | const VisibilityDialogScroll = styled('div', { |
22 | name: 'VisibilityPanel-Root', | 29 | name: 'VisibilityDialog-Scroll', |
23 | })(({ theme }) => ({ | 30 | shouldForwardProp: (propName) => propName !== 'dialog', |
24 | position: 'absolute', | 31 | })<{ dialog: boolean }>(({ theme, dialog }) => { |
25 | padding: theme.spacing(1), | 32 | const overlayOpacity = dialog ? 0.16 : 0.09; |
26 | top: 0, | 33 | return { |
27 | left: 0, | ||
28 | maxHeight: '100%', | ||
29 | maxWidth: '100%', | ||
30 | overflow: 'hidden', | ||
31 | display: 'flex', | ||
32 | flexDirection: 'column', | ||
33 | alignItems: 'start', | ||
34 | '.VisibilityPanel-drawer': { | ||
35 | overflow: 'hidden', | ||
36 | display: 'flex', | 34 | display: 'flex', |
37 | maxWidth: '100%', | 35 | flexDirection: 'column', |
38 | margin: theme.spacing(1), | 36 | height: 'auto', |
39 | }, | 37 | overflowX: 'hidden', |
40 | })); | 38 | overflowY: 'auto', |
39 | margin: `0 ${theme.spacing(2)}`, | ||
40 | '& table': { | ||
41 | // We use flexbox instead of `display: table` to get proper text-overflow | ||
42 | // behavior for overly long relation names. | ||
43 | display: 'flex', | ||
44 | flexDirection: 'column', | ||
45 | }, | ||
46 | '& thead, & tbody': { | ||
47 | display: 'flex', | ||
48 | flexDirection: 'column', | ||
49 | }, | ||
50 | '& thead': { | ||
51 | position: 'sticky', | ||
52 | top: 0, | ||
53 | zIndex: 999, | ||
54 | backgroundColor: theme.palette.background.paper, | ||
55 | ...(theme.palette.mode === 'dark' | ||
56 | ? { | ||
57 | // In dark mode, MUI Paper gets a lighter overlay. | ||
58 | backgroundImage: `linear-gradient( | ||
59 | rgba(255, 255, 255, ${overlayOpacity}), | ||
60 | rgba(255, 255, 255, ${overlayOpacity}) | ||
61 | )`, | ||
62 | } | ||
63 | : {}), | ||
64 | '& tr': { | ||
65 | height: '44px', | ||
66 | }, | ||
67 | }, | ||
68 | '& tr': { | ||
69 | display: 'flex', | ||
70 | flexDirection: 'row', | ||
71 | maxWidth: '100%', | ||
72 | }, | ||
73 | '& tbody tr': { | ||
74 | transition: theme.transitions.create('background', { | ||
75 | duration: theme.transitions.duration.shortest, | ||
76 | }), | ||
77 | '&:hover': { | ||
78 | background: theme.palette.action.hover, | ||
79 | '@media (hover: none)': { | ||
80 | background: 'transparent', | ||
81 | }, | ||
82 | }, | ||
83 | }, | ||
84 | '& th, & td': { | ||
85 | display: 'flex', | ||
86 | flexDirection: 'row', | ||
87 | alignItems: 'center', | ||
88 | justifyContent: 'center', | ||
89 | // Set width in advance, since we can't rely on `display: table-cell`. | ||
90 | width: '44px', | ||
91 | }, | ||
92 | '& th:nth-of-type(3), & td:nth-of-type(3)': { | ||
93 | justifyContent: 'start', | ||
94 | paddingLeft: theme.spacing(1), | ||
95 | paddingRight: theme.spacing(2), | ||
96 | // Only let the last column grow or shrink. | ||
97 | flexGrow: 1, | ||
98 | flexShrink: 1, | ||
99 | // Compute the maximum available space in advance to let the text overflow. | ||
100 | maxWidth: 'calc(100% - 88px)', | ||
101 | width: 'min-content', | ||
102 | }, | ||
103 | '& td:nth-of-type(3)': { | ||
104 | cursor: 'pointer', | ||
105 | userSelect: 'none', | ||
106 | WebkitTapHighlightColor: 'transparent', | ||
107 | }, | ||
108 | |||
109 | '& thead th, .VisibilityDialog-custom tr:last-child td': { | ||
110 | borderBottom: `1px solid ${theme.palette.divider}`, | ||
111 | }, | ||
112 | // Hack to apply `text-overflow`. | ||
113 | '.VisibilityDialog-nowrap': { | ||
114 | maxWidth: '100%', | ||
115 | overflow: 'hidden', | ||
116 | wordWrap: 'nowrap', | ||
117 | textOverflow: 'ellipsis', | ||
118 | }, | ||
119 | '.VisibilityDialog-empty': { | ||
120 | display: 'flex', | ||
121 | flexDirection: 'column', | ||
122 | alignItems: 'center', | ||
123 | color: theme.palette.text.secondary, | ||
124 | }, | ||
125 | '.VisibilityDialog-emptyIcon': { | ||
126 | fontSize: '6rem', | ||
127 | marginBottom: theme.spacing(1), | ||
128 | }, | ||
129 | }; | ||
130 | }); | ||
41 | 131 | ||
42 | function VisibilityPanel({ | 132 | function VisibilityPanel({ |
43 | graph, | 133 | graph, |
@@ -46,45 +136,132 @@ function VisibilityPanel({ | |||
46 | graph: GraphStore; | 136 | graph: GraphStore; |
47 | dialog: boolean; | 137 | dialog: boolean; |
48 | }): JSX.Element { | 138 | }): JSX.Element { |
49 | const id = useId(); | 139 | const builtinRows: JSX.Element[] = []; |
50 | const [showFilter, setShowFilter] = useState(false); | 140 | const rows: JSX.Element[] = []; |
51 | const close = useCallback(() => setShowFilter(false), []); | 141 | graph.relationMetadata.forEach((metadata, name) => { |
142 | if (!isVisibilityAllowed(metadata, 'must')) { | ||
143 | return; | ||
144 | } | ||
145 | const visibility = graph.getVisibility(name); | ||
146 | const row = ( | ||
147 | <tr key={metadata.name}> | ||
148 | <td> | ||
149 | <Checkbox | ||
150 | checked={visibility !== 'none'} | ||
151 | aria-label={`Show true and error values of ${metadata.simpleName}`} | ||
152 | onClick={() => | ||
153 | graph.setVisibility(name, visibility === 'none' ? 'must' : 'none') | ||
154 | } | ||
155 | /> | ||
156 | </td> | ||
157 | <td> | ||
158 | <Checkbox | ||
159 | checked={visibility === 'all'} | ||
160 | disabled={!isVisibilityAllowed(metadata, 'all')} | ||
161 | aria-label={`Show all values of ${metadata.simpleName}`} | ||
162 | onClick={() => | ||
163 | graph.setVisibility(name, visibility === 'all' ? 'must' : 'all') | ||
164 | } | ||
165 | /> | ||
166 | </td> | ||
167 | <td | ||
168 | onClick={() => graph.cycleVisibility(name)} | ||
169 | aria-label="Toggle visiblity" | ||
170 | > | ||
171 | <div className="VisibilityDialog-nowrap"> | ||
172 | <RelationName metadata={metadata} abbreviate={graph.abbreviate} /> | ||
173 | </div> | ||
174 | </td> | ||
175 | </tr> | ||
176 | ); | ||
177 | if (name.startsWith('builtin::')) { | ||
178 | builtinRows.push(row); | ||
179 | } else { | ||
180 | rows.push(row); | ||
181 | } | ||
182 | }); | ||
183 | |||
184 | const hasRows = rows.length > 0 || builtinRows.length > 0; | ||
185 | |||
186 | const hideBadge = graph.visibility.size === 0; | ||
187 | const icon = useCallback( | ||
188 | (show: boolean) => ( | ||
189 | <Badge color="primary" variant="dot" invisible={hideBadge}> | ||
190 | {show && !dialog ? <ChevronLeftIcon /> : <TuneIcon />} | ||
191 | </Badge> | ||
192 | ), | ||
193 | [dialog, hideBadge], | ||
194 | ); | ||
52 | 195 | ||
53 | return ( | 196 | return ( |
54 | <VisibilityPanelRoot> | 197 | <SlideInPanel |
55 | <IconButton | 198 | anchor="left" |
56 | role="switch" | 199 | dialog={dialog} |
57 | aria-checked={showFilter} | 200 | title="Customize view" |
58 | aria-controls={dialog ? undefined : id} | 201 | icon={icon} |
59 | aria-label="Show filter panel" | 202 | iconLabel="Show filter panel" |
60 | onClick={() => setShowFilter(!showFilter)} | 203 | buttons={ |
61 | > | 204 | <> |
62 | <Badge | 205 | <Button |
63 | color="primary" | 206 | color="inherit" |
64 | variant="dot" | 207 | onClick={() => graph.hideAll()} |
65 | invisible={graph.visibility.size === 0} | 208 | startIcon={<VisibilityOffIcon />} |
66 | > | 209 | > |
67 | {showFilter && !dialog ? <ChevronLeftIcon /> : <TuneIcon />} | 210 | Hide all |
68 | </Badge> | 211 | </Button> |
69 | </IconButton> | 212 | <Button |
70 | {dialog ? ( | 213 | color="inherit" |
71 | <Dialog open={showFilter} onClose={close} maxWidth="xl"> | 214 | onClick={() => graph.resetFilter()} |
72 | <VisibilityDialog graph={graph} close={close} dialog /> | 215 | startIcon={<FilterListIcon />} |
73 | </Dialog> | 216 | > |
74 | ) : ( | 217 | Reset filter |
75 | <Slide | 218 | </Button> |
76 | direction="right" | 219 | </> |
77 | in={showFilter} | 220 | } |
78 | id={id} | 221 | > |
79 | mountOnEnter | 222 | <FormControlLabel |
80 | unmountOnExit | 223 | control={ |
81 | > | 224 | <Switch |
82 | <Paper className="VisibilityPanel-drawer" elevation={4}> | 225 | checked={!graph.abbreviate} |
83 | <VisibilityDialog graph={graph} close={close} /> | 226 | onClick={() => graph.toggleAbbrevaite()} |
84 | </Paper> | 227 | /> |
85 | </Slide> | 228 | } |
86 | )} | 229 | label="Fully qualified names" |
87 | </VisibilityPanelRoot> | 230 | /> |
231 | <FormControlLabel | ||
232 | control={ | ||
233 | <Switch checked={graph.scopes} onClick={() => graph.toggleScopes()} /> | ||
234 | } | ||
235 | label="Object scopes" | ||
236 | /> | ||
237 | <VisibilityDialogScroll dialog={dialog}> | ||
238 | {hasRows ? ( | ||
239 | <table cellSpacing={0}> | ||
240 | <thead> | ||
241 | <tr> | ||
242 | <th aria-label="Show true and error values"> | ||
243 | <LabelIcon /> | ||
244 | </th> | ||
245 | <th aria-label="Show unknown values"> | ||
246 | <LabelOutlinedIcon /> | ||
247 | </th> | ||
248 | <th>Symbol</th> | ||
249 | </tr> | ||
250 | </thead> | ||
251 | <tbody className="VisibilityDialog-custom">{...rows}</tbody> | ||
252 | <tbody className="VisibilityDialog-builtin">{...builtinRows}</tbody> | ||
253 | </table> | ||
254 | ) : ( | ||
255 | <div className="VisibilityDialog-empty"> | ||
256 | <SentimentVeryDissatisfiedIcon | ||
257 | className="VisibilityDialog-emptyIcon" | ||
258 | fontSize="inherit" | ||
259 | /> | ||
260 | <div>Partial model is empty</div> | ||
261 | </div> | ||
262 | )} | ||
263 | </VisibilityDialogScroll> | ||
264 | </SlideInPanel> | ||
88 | ); | 265 | ); |
89 | } | 266 | } |
90 | 267 | ||
diff --git a/subprojects/frontend/src/graph/export/ExportPanel.tsx b/subprojects/frontend/src/graph/export/ExportPanel.tsx new file mode 100644 index 00000000..c93fa837 --- /dev/null +++ b/subprojects/frontend/src/graph/export/ExportPanel.tsx | |||
@@ -0,0 +1,227 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import ChevronRightIcon from '@mui/icons-material/ChevronRight'; | ||
8 | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; | ||
9 | import DarkModeIcon from '@mui/icons-material/DarkMode'; | ||
10 | import ImageIcon from '@mui/icons-material/Image'; | ||
11 | import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; | ||
12 | import LightModeIcon from '@mui/icons-material/LightMode'; | ||
13 | import SaveAltIcon from '@mui/icons-material/SaveAlt'; | ||
14 | import ShapeLineIcon from '@mui/icons-material/ShapeLine'; | ||
15 | import Box from '@mui/material/Box'; | ||
16 | import Button from '@mui/material/Button'; | ||
17 | import FormControlLabel from '@mui/material/FormControlLabel'; | ||
18 | import Slider from '@mui/material/Slider'; | ||
19 | import Stack from '@mui/material/Stack'; | ||
20 | import Switch from '@mui/material/Switch'; | ||
21 | import ToggleButton from '@mui/material/ToggleButton'; | ||
22 | import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; | ||
23 | import Typography from '@mui/material/Typography'; | ||
24 | import { styled } from '@mui/material/styles'; | ||
25 | import { observer } from 'mobx-react-lite'; | ||
26 | import { useCallback } from 'react'; | ||
27 | |||
28 | import { useRootStore } from '../../RootStoreProvider'; | ||
29 | import getLogger from '../../utils/getLogger'; | ||
30 | import type GraphStore from '../GraphStore'; | ||
31 | import SlideInPanel from '../SlideInPanel'; | ||
32 | |||
33 | import exportDiagram from './exportDiagram'; | ||
34 | |||
35 | const log = getLogger('graph.ExportPanel'); | ||
36 | |||
37 | const SwitchButtonGroup = styled(ToggleButtonGroup, { | ||
38 | name: 'ExportPanel-SwitchButtonGroup', | ||
39 | })(({ theme }) => ({ | ||
40 | marginTop: theme.spacing(2), | ||
41 | marginInline: theme.spacing(2), | ||
42 | minWidth: '260px', | ||
43 | '.MuiToggleButton-root': { | ||
44 | width: '100%', | ||
45 | fontSize: '1rem', | ||
46 | lineHeight: '1.5', | ||
47 | }, | ||
48 | '& svg': { | ||
49 | margin: '0 6px 0 0', | ||
50 | }, | ||
51 | })); | ||
52 | |||
53 | function getLabel(value: number): string { | ||
54 | return `${value}%`; | ||
55 | } | ||
56 | |||
57 | const marks = [100, 200, 300, 400].map((value) => ({ | ||
58 | value, | ||
59 | label: ( | ||
60 | <Stack direction="column" alignItems="center"> | ||
61 | <ImageIcon sx={{ width: `${11 + (value / 100) * 3}px` }} /> | ||
62 | <Typography variant="caption">{getLabel(value)}</Typography> | ||
63 | </Stack> | ||
64 | ), | ||
65 | })); | ||
66 | |||
67 | function ExportPanel({ | ||
68 | graph, | ||
69 | svgContainer, | ||
70 | dialog, | ||
71 | }: { | ||
72 | graph: GraphStore; | ||
73 | svgContainer: HTMLElement | undefined; | ||
74 | dialog: boolean; | ||
75 | }): JSX.Element { | ||
76 | const { exportSettingsStore } = useRootStore(); | ||
77 | |||
78 | const icon = useCallback( | ||
79 | (show: boolean) => | ||
80 | show && !dialog ? <ChevronRightIcon /> : <SaveAltIcon />, | ||
81 | [dialog], | ||
82 | ); | ||
83 | |||
84 | const { format } = exportSettingsStore; | ||
85 | const emptyGraph = graph.semantics.nodes.length === 0; | ||
86 | const buttons = useCallback( | ||
87 | (close: () => void) => ( | ||
88 | <> | ||
89 | <Button | ||
90 | color="inherit" | ||
91 | startIcon={<SaveAltIcon />} | ||
92 | disabled={emptyGraph} | ||
93 | onClick={() => { | ||
94 | exportDiagram(svgContainer, graph, exportSettingsStore, 'download') | ||
95 | .then(close) | ||
96 | .catch((error) => { | ||
97 | log.error('Failed to download diagram', error); | ||
98 | }); | ||
99 | }} | ||
100 | > | ||
101 | Download | ||
102 | </Button> | ||
103 | {'write' in navigator.clipboard && format === 'png' && ( | ||
104 | <Button | ||
105 | color="inherit" | ||
106 | startIcon={<ContentCopyIcon />} | ||
107 | disabled={emptyGraph} | ||
108 | onClick={() => { | ||
109 | exportDiagram(svgContainer, graph, exportSettingsStore, 'copy') | ||
110 | .then(close) | ||
111 | .catch((error) => { | ||
112 | log.error('Failed to copy diagram', error); | ||
113 | }); | ||
114 | }} | ||
115 | > | ||
116 | Copy | ||
117 | </Button> | ||
118 | )} | ||
119 | </> | ||
120 | ), | ||
121 | [svgContainer, graph, exportSettingsStore, format, emptyGraph], | ||
122 | ); | ||
123 | |||
124 | return ( | ||
125 | <SlideInPanel | ||
126 | anchor="right" | ||
127 | dialog={dialog} | ||
128 | title="Export diagram" | ||
129 | icon={icon} | ||
130 | iconLabel="Show export panel" | ||
131 | buttons={buttons} | ||
132 | > | ||
133 | <SwitchButtonGroup size="small" className="rounded"> | ||
134 | <ToggleButton | ||
135 | value="svg" | ||
136 | selected={exportSettingsStore.format === 'svg'} | ||
137 | onClick={() => exportSettingsStore.setFormat('svg')} | ||
138 | > | ||
139 | <ShapeLineIcon fontSize="small" /> SVG | ||
140 | </ToggleButton> | ||
141 | <ToggleButton | ||
142 | value="pdf" | ||
143 | selected={exportSettingsStore.format === 'pdf'} | ||
144 | onClick={() => exportSettingsStore.setFormat('pdf')} | ||
145 | > | ||
146 | <InsertDriveFileOutlinedIcon fontSize="small" /> PDF | ||
147 | </ToggleButton> | ||
148 | <ToggleButton | ||
149 | value="png" | ||
150 | selected={exportSettingsStore.format === 'png'} | ||
151 | onClick={() => exportSettingsStore.setFormat('png')} | ||
152 | > | ||
153 | <ImageIcon fontSize="small" /> PNG | ||
154 | </ToggleButton> | ||
155 | </SwitchButtonGroup> | ||
156 | <SwitchButtonGroup size="small" className="rounded"> | ||
157 | <ToggleButton | ||
158 | value="svg" | ||
159 | selected={exportSettingsStore.theme === 'light'} | ||
160 | onClick={() => exportSettingsStore.setTheme('light')} | ||
161 | > | ||
162 | <LightModeIcon fontSize="small" /> Light | ||
163 | </ToggleButton> | ||
164 | <ToggleButton | ||
165 | value="png" | ||
166 | selected={exportSettingsStore.theme === 'dark'} | ||
167 | onClick={() => exportSettingsStore.setTheme('dark')} | ||
168 | > | ||
169 | <DarkModeIcon fontSize="small" /> Dark | ||
170 | </ToggleButton> | ||
171 | </SwitchButtonGroup> | ||
172 | <FormControlLabel | ||
173 | control={ | ||
174 | <Switch | ||
175 | checked={exportSettingsStore.transparent} | ||
176 | onClick={() => exportSettingsStore.toggleTransparent()} | ||
177 | /> | ||
178 | } | ||
179 | label="Transparent background" | ||
180 | /> | ||
181 | {exportSettingsStore.canEmbedFonts && ( | ||
182 | <FormControlLabel | ||
183 | control={ | ||
184 | <Switch | ||
185 | checked={exportSettingsStore.embedFonts} | ||
186 | onClick={() => exportSettingsStore.toggleEmbedFonts()} | ||
187 | /> | ||
188 | } | ||
189 | label={ | ||
190 | <Stack direction="column"> | ||
191 | <Typography>Embed fonts</Typography> | ||
192 | <Typography variant="caption"> | ||
193 | {exportSettingsStore.format === 'pdf' ? ( | ||
194 | <>+20 kB fully embedded</> | ||
195 | ) : ( | ||
196 | <>+75 kB, only supported in browsers</> | ||
197 | )} | ||
198 | </Typography> | ||
199 | </Stack> | ||
200 | } | ||
201 | /> | ||
202 | )} | ||
203 | {exportSettingsStore.canScale && ( | ||
204 | <Box mx={4} mt={1} mb={2}> | ||
205 | <Slider | ||
206 | aria-label="Image scale" | ||
207 | value={exportSettingsStore.scale} | ||
208 | min={100} | ||
209 | max={400} | ||
210 | valueLabelFormat={getLabel} | ||
211 | getAriaValueText={getLabel} | ||
212 | step={50} | ||
213 | valueLabelDisplay="auto" | ||
214 | marks={marks} | ||
215 | onChange={(_, value) => { | ||
216 | if (typeof value === 'number') { | ||
217 | exportSettingsStore.setScale(value); | ||
218 | } | ||
219 | }} | ||
220 | /> | ||
221 | </Box> | ||
222 | )} | ||
223 | </SlideInPanel> | ||
224 | ); | ||
225 | } | ||
226 | |||
227 | export default observer(ExportPanel); | ||
diff --git a/subprojects/frontend/src/graph/export/ExportSettingsStore.ts b/subprojects/frontend/src/graph/export/ExportSettingsStore.ts new file mode 100644 index 00000000..53a161ab --- /dev/null +++ b/subprojects/frontend/src/graph/export/ExportSettingsStore.ts | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import { makeAutoObservable } from 'mobx'; | ||
8 | |||
9 | export type ExportFormat = 'svg' | 'pdf' | 'png'; | ||
10 | export type ExportTheme = 'light' | 'dark'; | ||
11 | |||
12 | export default class ExportSettingsStore { | ||
13 | format: ExportFormat = 'svg'; | ||
14 | |||
15 | theme: ExportTheme = 'light'; | ||
16 | |||
17 | transparent = true; | ||
18 | |||
19 | embedSVGFonts = false; | ||
20 | |||
21 | embedPDFFonts = true; | ||
22 | |||
23 | scale = 100; | ||
24 | |||
25 | constructor() { | ||
26 | makeAutoObservable(this); | ||
27 | } | ||
28 | |||
29 | setFormat(format: ExportFormat): void { | ||
30 | this.format = format; | ||
31 | } | ||
32 | |||
33 | setTheme(theme: ExportTheme): void { | ||
34 | this.theme = theme; | ||
35 | } | ||
36 | |||
37 | toggleTransparent(): void { | ||
38 | this.transparent = !this.transparent; | ||
39 | } | ||
40 | |||
41 | toggleEmbedFonts(): void { | ||
42 | this.embedFonts = !this.embedFonts; | ||
43 | } | ||
44 | |||
45 | setScale(scale: number): void { | ||
46 | this.scale = scale; | ||
47 | } | ||
48 | |||
49 | get embedFonts(): boolean { | ||
50 | return this.format === 'pdf' ? this.embedPDFFonts : this.embedSVGFonts; | ||
51 | } | ||
52 | |||
53 | private set embedFonts(embedFonts: boolean) { | ||
54 | if (this.format === 'pdf') { | ||
55 | this.embedPDFFonts = embedFonts; | ||
56 | } | ||
57 | this.embedSVGFonts = embedFonts; | ||
58 | } | ||
59 | |||
60 | get canEmbedFonts(): boolean { | ||
61 | return this.format === 'svg' || this.format === 'pdf'; | ||
62 | } | ||
63 | |||
64 | get canScale(): boolean { | ||
65 | return this.format === 'png'; | ||
66 | } | ||
67 | } | ||
diff --git a/subprojects/frontend/src/graph/export/exportDiagram.tsx b/subprojects/frontend/src/graph/export/exportDiagram.tsx new file mode 100644 index 00000000..44489d28 --- /dev/null +++ b/subprojects/frontend/src/graph/export/exportDiagram.tsx | |||
@@ -0,0 +1,389 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import createCache from '@emotion/cache'; | ||
8 | import { serializeStyles } from '@emotion/serialize'; | ||
9 | import type { StyleSheet } from '@emotion/utils'; | ||
10 | import italicFontURL from '@fontsource/open-sans/files/open-sans-latin-400-italic.woff2?url'; | ||
11 | import normalFontURL from '@fontsource/open-sans/files/open-sans-latin-400-normal.woff2?url'; | ||
12 | import boldFontURL from '@fontsource/open-sans/files/open-sans-latin-700-normal.woff2?url'; | ||
13 | import variableItalicFontURL from '@fontsource-variable/open-sans/files/open-sans-latin-wght-italic.woff2?url'; | ||
14 | import variableFontURL from '@fontsource-variable/open-sans/files/open-sans-latin-wght-normal.woff2?url'; | ||
15 | import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; | ||
16 | import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw'; | ||
17 | import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; | ||
18 | import type { Theme } from '@mui/material/styles'; | ||
19 | |||
20 | import { darkTheme, lightTheme } from '../../theme/ThemeProvider'; | ||
21 | import { copyBlob, saveBlob } from '../../utils/fileIO'; | ||
22 | import type GraphStore from '../GraphStore'; | ||
23 | import { createGraphTheme } from '../GraphTheme'; | ||
24 | import { SVG_NS } from '../postProcessSVG'; | ||
25 | |||
26 | import type ExportSettingsStore from './ExportSettingsStore'; | ||
27 | |||
28 | const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'; | ||
29 | const PNG_CONTENT_TYPE = 'image/png'; | ||
30 | const SVG_CONTENT_TYPE = 'image/svg+xml'; | ||
31 | const EXPORT_ID = 'export-image'; | ||
32 | |||
33 | const ICONS: Map<string, Element> = new Map(); | ||
34 | |||
35 | function importSVG(svgSource: string, className: string): void { | ||
36 | const parser = new DOMParser(); | ||
37 | const svgDocument = parser.parseFromString(svgSource, SVG_CONTENT_TYPE); | ||
38 | const root = svgDocument.children[0]; | ||
39 | if (root === undefined) { | ||
40 | return; | ||
41 | } | ||
42 | root.id = className; | ||
43 | root.classList.add(className); | ||
44 | ICONS.set(className, root); | ||
45 | } | ||
46 | |||
47 | importSVG(labelSVG, 'icon-TRUE'); | ||
48 | importSVG(labelOutlinedSVG, 'icon-UNKNOWN'); | ||
49 | importSVG(cancelSVG, 'icon-ERROR'); | ||
50 | |||
51 | function addBackground( | ||
52 | svgDocument: XMLDocument, | ||
53 | svg: SVGSVGElement, | ||
54 | theme: Theme, | ||
55 | ): void { | ||
56 | const viewBox = svg.getAttribute('viewBox')?.split(' '); | ||
57 | const rect = svgDocument.createElementNS(SVG_NS, 'rect'); | ||
58 | rect.setAttribute('x', viewBox?.[0] ?? '0'); | ||
59 | rect.setAttribute('y', viewBox?.[1] ?? '0'); | ||
60 | rect.setAttribute('width', viewBox?.[2] ?? '0'); | ||
61 | rect.setAttribute('height', viewBox?.[3] ?? '0'); | ||
62 | rect.setAttribute('fill', theme.palette.background.default); | ||
63 | svg.prepend(rect); | ||
64 | } | ||
65 | |||
66 | async function fetchAsFontURL(url: string): Promise<string> { | ||
67 | const fetchResult = await fetch(url); | ||
68 | const buffer = await fetchResult.arrayBuffer(); | ||
69 | const blob = new Blob([buffer], { type: 'font/woff2' }); | ||
70 | return new Promise((resolve, reject) => { | ||
71 | const fileReader = new FileReader(); | ||
72 | fileReader.addEventListener('load', () => { | ||
73 | resolve(fileReader.result as string); | ||
74 | }); | ||
75 | fileReader.addEventListener('error', () => { | ||
76 | reject(fileReader.error); | ||
77 | }); | ||
78 | fileReader.readAsDataURL(blob); | ||
79 | }); | ||
80 | } | ||
81 | |||
82 | let fontCSS: string | undefined; | ||
83 | let variableFontCSS: string | undefined; | ||
84 | |||
85 | async function fetchFontCSS(): Promise<string> { | ||
86 | if (fontCSS !== undefined) { | ||
87 | return fontCSS; | ||
88 | } | ||
89 | const [normalDataURL, boldDataURL, italicDataURL] = await Promise.all([ | ||
90 | fetchAsFontURL(normalFontURL), | ||
91 | fetchAsFontURL(boldFontURL), | ||
92 | fetchAsFontURL(italicFontURL), | ||
93 | ]); | ||
94 | fontCSS = ` | ||
95 | @font-face { | ||
96 | font-family: 'Open Sans'; | ||
97 | font-style: normal; | ||
98 | font-display: swap; | ||
99 | font-weight: 400; | ||
100 | src: url(${normalDataURL}) format('woff2'); | ||
101 | } | ||
102 | @font-face { | ||
103 | font-family: 'Open Sans'; | ||
104 | font-style: normal; | ||
105 | font-display: swap; | ||
106 | font-weight: 700; | ||
107 | src: url(${boldDataURL}) format('woff2'); | ||
108 | } | ||
109 | @font-face { | ||
110 | font-family: 'Open Sans'; | ||
111 | font-style: italic; | ||
112 | font-display: swap; | ||
113 | font-weight: 400; | ||
114 | src: url(${italicDataURL}) format('woff2'); | ||
115 | }`; | ||
116 | return fontCSS; | ||
117 | } | ||
118 | |||
119 | async function fetchVariableFontCSS(): Promise<string> { | ||
120 | if (variableFontCSS !== undefined) { | ||
121 | return variableFontCSS; | ||
122 | } | ||
123 | const [variableDataURL, variableItalicDataURL] = await Promise.all([ | ||
124 | fetchAsFontURL(variableFontURL), | ||
125 | fetchAsFontURL(variableItalicFontURL), | ||
126 | ]); | ||
127 | variableFontCSS = ` | ||
128 | @font-face { | ||
129 | font-family: 'Open Sans Variable'; | ||
130 | font-style: normal; | ||
131 | font-display: swap; | ||
132 | font-weight: 300 800; | ||
133 | src: url(${variableDataURL}) format('woff2-variations'); | ||
134 | } | ||
135 | @font-face { | ||
136 | font-family: 'Open Sans Variable'; | ||
137 | font-style: italic; | ||
138 | font-display: swap; | ||
139 | font-weight: 300 800; | ||
140 | src: url(${variableItalicDataURL}) format('woff2-variations'); | ||
141 | }`; | ||
142 | return variableFontCSS; | ||
143 | } | ||
144 | |||
145 | function appendStyles( | ||
146 | svgDocument: XMLDocument, | ||
147 | svg: SVGSVGElement, | ||
148 | theme: Theme, | ||
149 | colorNodes: boolean, | ||
150 | fontsCSS: string, | ||
151 | ): void { | ||
152 | const cache = createCache({ | ||
153 | key: 'refinery', | ||
154 | container: svg, | ||
155 | prepend: true, | ||
156 | }); | ||
157 | // @ts-expect-error `CSSObject` types don't match up between `@mui/material` and | ||
158 | // `@emotion/serialize`, but they are compatible in practice. | ||
159 | const styles = serializeStyles([createGraphTheme], cache.registered, { | ||
160 | theme, | ||
161 | colorNodes, | ||
162 | noEmbedIcons: true, | ||
163 | }); | ||
164 | const rules: string[] = [fontsCSS]; | ||
165 | const sheet = { | ||
166 | insert(rule) { | ||
167 | rules.push(rule); | ||
168 | }, | ||
169 | } as StyleSheet; | ||
170 | cache.insert('', styles, sheet, false); | ||
171 | const styleElement = svgDocument.createElementNS(SVG_NS, 'style'); | ||
172 | svg.prepend(styleElement); | ||
173 | styleElement.innerHTML = rules.join(''); | ||
174 | } | ||
175 | |||
176 | function fixForeignObjects(svgDocument: XMLDocument, svg: SVGSVGElement): void { | ||
177 | const foreignObjects: SVGForeignObjectElement[] = []; | ||
178 | svg | ||
179 | .querySelectorAll('foreignObject') | ||
180 | .forEach((object) => foreignObjects.push(object)); | ||
181 | foreignObjects.forEach((object) => { | ||
182 | const useElement = svgDocument.createElementNS(SVG_NS, 'use'); | ||
183 | let x = Number(object.getAttribute('x') ?? '0'); | ||
184 | let y = Number(object.getAttribute('y') ?? '0'); | ||
185 | const width = Number(object.getAttribute('width') ?? '0'); | ||
186 | const height = Number(object.getAttribute('height') ?? '0'); | ||
187 | const size = Math.min(width, height); | ||
188 | x += (width - size) / 2; | ||
189 | y += (height - size) / 2; | ||
190 | useElement.setAttribute('x', String(x)); | ||
191 | useElement.setAttribute('y', String(y)); | ||
192 | useElement.setAttribute('width', String(size)); | ||
193 | useElement.setAttribute('height', String(size)); | ||
194 | useElement.id = object.id; | ||
195 | object.children[0]?.classList?.forEach((className) => { | ||
196 | useElement.classList.add(className); | ||
197 | if (ICONS.has(className)) { | ||
198 | useElement.setAttribute('href', `#${className}`); | ||
199 | } | ||
200 | }); | ||
201 | object.replaceWith(useElement); | ||
202 | }); | ||
203 | const defs = svgDocument.createElementNS(SVG_NS, 'defs'); | ||
204 | svg.prepend(defs); | ||
205 | ICONS.forEach((value) => { | ||
206 | const importedValue = svgDocument.importNode(value, true); | ||
207 | defs.appendChild(importedValue); | ||
208 | }); | ||
209 | } | ||
210 | |||
211 | function serializeSVG(svgDocument: XMLDocument): Blob { | ||
212 | const serializer = new XMLSerializer(); | ||
213 | const svgText = `${PROLOG}\n${serializer.serializeToString(svgDocument)}`; | ||
214 | return new Blob([svgText], { | ||
215 | type: SVG_CONTENT_TYPE, | ||
216 | }); | ||
217 | } | ||
218 | |||
219 | async function serializePNG( | ||
220 | serializedSVG: Blob, | ||
221 | svg: SVGSVGElement, | ||
222 | settings: ExportSettingsStore, | ||
223 | theme: Theme, | ||
224 | ): Promise<Blob> { | ||
225 | const scale = settings.scale / 100; | ||
226 | const baseWidth = svg.width.baseVal.value; | ||
227 | const baseHeight = svg.height.baseVal.value; | ||
228 | const exactWidth = baseWidth * scale; | ||
229 | const exactHeight = baseHeight * scale; | ||
230 | const width = Math.round(exactWidth); | ||
231 | const height = Math.round(exactHeight); | ||
232 | |||
233 | const canvas = document.createElement('canvas'); | ||
234 | canvas.width = width; | ||
235 | canvas.height = height; | ||
236 | |||
237 | const image = document.createElement('img'); | ||
238 | const url = window.URL.createObjectURL(serializedSVG); | ||
239 | try { | ||
240 | await new Promise((resolve, reject) => { | ||
241 | image.addEventListener('load', () => resolve(undefined)); | ||
242 | image.addEventListener('error', ({ error }) => | ||
243 | reject( | ||
244 | error instanceof Error | ||
245 | ? error | ||
246 | : new Error(`Failed to load image: ${error}`), | ||
247 | ), | ||
248 | ); | ||
249 | image.src = url; | ||
250 | }); | ||
251 | } finally { | ||
252 | window.URL.revokeObjectURL(url); | ||
253 | } | ||
254 | |||
255 | const context = canvas.getContext('2d'); | ||
256 | if (context === null) { | ||
257 | throw new Error('Failed to get canvas 2D context'); | ||
258 | } | ||
259 | if (!settings.transparent) { | ||
260 | context.fillStyle = theme.palette.background.default; | ||
261 | context.fillRect(0, 0, width, height); | ||
262 | } | ||
263 | context.drawImage( | ||
264 | image, | ||
265 | 0, | ||
266 | 0, | ||
267 | baseWidth, | ||
268 | baseHeight, | ||
269 | 0, | ||
270 | 0, | ||
271 | exactWidth, | ||
272 | exactHeight, | ||
273 | ); | ||
274 | |||
275 | return new Promise<Blob>((resolve, reject) => { | ||
276 | canvas.toBlob((exportedBlob) => { | ||
277 | if (exportedBlob === null) { | ||
278 | reject(new Error('Failed to export PNG blob')); | ||
279 | } else { | ||
280 | resolve(exportedBlob); | ||
281 | } | ||
282 | }, PNG_CONTENT_TYPE); | ||
283 | }); | ||
284 | } | ||
285 | |||
286 | let serializePDFCached: | ||
287 | | ((svg: SVGSVGElement, embedFonts: boolean) => Promise<Blob>) | ||
288 | | undefined; | ||
289 | |||
290 | async function serializePDF( | ||
291 | svg: SVGSVGElement, | ||
292 | settings: ExportSettingsStore, | ||
293 | ): Promise<Blob> { | ||
294 | if (serializePDFCached === undefined) { | ||
295 | serializePDFCached = (await import('./serializePDF')).default; | ||
296 | } | ||
297 | return serializePDFCached(svg, settings.embedFonts); | ||
298 | } | ||
299 | |||
300 | export default async function exportDiagram( | ||
301 | svgContainer: HTMLElement | undefined, | ||
302 | graph: GraphStore, | ||
303 | settings: ExportSettingsStore, | ||
304 | mode: 'download' | 'copy', | ||
305 | ): Promise<void> { | ||
306 | const svg = svgContainer?.querySelector('svg'); | ||
307 | if (!svg) { | ||
308 | return; | ||
309 | } | ||
310 | const svgDocument = document.implementation.createDocument( | ||
311 | SVG_NS, | ||
312 | 'svg', | ||
313 | null, | ||
314 | ); | ||
315 | const copyOfSVG = svgDocument.importNode(svg, true); | ||
316 | const originalRoot = svgDocument.childNodes[0]; | ||
317 | if (originalRoot === undefined) { | ||
318 | svgDocument.appendChild(copyOfSVG); | ||
319 | } else { | ||
320 | svgDocument.replaceChild(copyOfSVG, originalRoot); | ||
321 | } | ||
322 | |||
323 | const theme = settings.theme === 'light' ? lightTheme : darkTheme; | ||
324 | if (!settings.transparent) { | ||
325 | addBackground(svgDocument, copyOfSVG, theme); | ||
326 | } | ||
327 | |||
328 | fixForeignObjects(svgDocument, copyOfSVG); | ||
329 | |||
330 | const { colorNodes } = graph; | ||
331 | let fontsCSS = ''; | ||
332 | if (settings.format === 'png') { | ||
333 | // If we are creating a PNG, font file size doesn't matter, | ||
334 | // and we can reuse fonts the browser has already downloaded. | ||
335 | fontsCSS = await fetchVariableFontCSS(); | ||
336 | } else if (settings.format === 'svg' && settings.embedFonts) { | ||
337 | fontsCSS = await fetchFontCSS(); | ||
338 | } | ||
339 | appendStyles(svgDocument, copyOfSVG, theme, colorNodes, fontsCSS); | ||
340 | |||
341 | if (settings.format === 'pdf') { | ||
342 | const pdf = await serializePDF(copyOfSVG, settings); | ||
343 | await saveBlob(pdf, `${graph.name}.pdf`, { | ||
344 | id: EXPORT_ID, | ||
345 | types: [ | ||
346 | { | ||
347 | description: 'PDF files', | ||
348 | accept: { | ||
349 | 'application/pdf': ['.pdf', '.PDF'], | ||
350 | }, | ||
351 | }, | ||
352 | ], | ||
353 | }); | ||
354 | return; | ||
355 | } | ||
356 | const serializedSVG = serializeSVG(svgDocument); | ||
357 | if (settings.format === 'png') { | ||
358 | const png = await serializePNG(serializedSVG, svg, settings, theme); | ||
359 | if (mode === 'copy') { | ||
360 | await copyBlob(png); | ||
361 | } else { | ||
362 | await saveBlob(png, `${graph.name}.png`, { | ||
363 | id: EXPORT_ID, | ||
364 | types: [ | ||
365 | { | ||
366 | description: 'PNG graphics', | ||
367 | accept: { | ||
368 | [PNG_CONTENT_TYPE]: ['.png', '.PNG'], | ||
369 | }, | ||
370 | }, | ||
371 | ], | ||
372 | }); | ||
373 | } | ||
374 | } else if (mode === 'copy') { | ||
375 | await copyBlob(serializedSVG); | ||
376 | } else { | ||
377 | await saveBlob(serializedSVG, `${graph.name}.svg`, { | ||
378 | id: EXPORT_ID, | ||
379 | types: [ | ||
380 | { | ||
381 | description: 'SVG graphics', | ||
382 | accept: { | ||
383 | [SVG_CONTENT_TYPE]: ['.svg', '.SVG'], | ||
384 | }, | ||
385 | }, | ||
386 | ], | ||
387 | }); | ||
388 | } | ||
389 | } | ||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf new file mode 100644 index 00000000..7472d192 --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf | |||
Binary files differ | |||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license new file mode 100644 index 00000000..442f1821 --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license | |||
@@ -0,0 +1,8 @@ | |||
1 | Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) | ||
2 | |||
3 | SPDX-License-Identifier: OFL-1.1 | ||
4 | |||
5 | This file was derived from Open Sans v3.3 | ||
6 | (https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Bold.ttf) | ||
7 | using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator) | ||
8 | with the Basic Subsetting setting to reduce the file size by retaining latin characters only. | ||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf new file mode 100644 index 00000000..cecb0ce1 --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf | |||
Binary files differ | |||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license new file mode 100644 index 00000000..0657164a --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license | |||
@@ -0,0 +1,8 @@ | |||
1 | Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) | ||
2 | |||
3 | SPDX-License-Identifier: OFL-1.1 | ||
4 | |||
5 | This file was derived from Open Sans v3.3 | ||
6 | (https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Italic.ttf) | ||
7 | using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator) | ||
8 | with the Basic Subsetting setting to reduce the file size by retaining latin characters only. | ||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf new file mode 100644 index 00000000..46c0f716 --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf | |||
Binary files differ | |||
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license new file mode 100644 index 00000000..8bc20e51 --- /dev/null +++ b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license | |||
@@ -0,0 +1,8 @@ | |||
1 | Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) | ||
2 | |||
3 | SPDX-License-Identifier: OFL-1.1 | ||
4 | |||
5 | This file was derived from Open Sans v3.3 | ||
6 | (https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Regular.ttf) | ||
7 | using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator) | ||
8 | with the Basic Subsetting setting to reduce the file size by retaining latin characters only. | ||
diff --git a/subprojects/frontend/src/graph/export/serializePDF.ts b/subprojects/frontend/src/graph/export/serializePDF.ts new file mode 100644 index 00000000..75d1a4f4 --- /dev/null +++ b/subprojects/frontend/src/graph/export/serializePDF.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import { jsPDF } from 'jspdf'; | ||
8 | import { svg2pdf } from 'svg2pdf.js'; | ||
9 | |||
10 | import boldFontURL from './open-sans-latin-bold.ttf?url'; | ||
11 | import italicFontURL from './open-sans-latin-italic.ttf?url'; | ||
12 | import normalFontURL from './open-sans-latin-regular.ttf?url'; | ||
13 | |||
14 | export default async function serializePDF( | ||
15 | svg: SVGSVGElement, | ||
16 | embedFonts: boolean, | ||
17 | ): Promise<Blob> { | ||
18 | const width = svg.width.baseVal.value; | ||
19 | const height = svg.height.baseVal.value; | ||
20 | // eslint-disable-next-line new-cap -- jsPDF uses a lowercase constructor. | ||
21 | const document = new jsPDF({ | ||
22 | orientation: width > height ? 'l' : 'p', | ||
23 | unit: 'px', | ||
24 | format: [width, height], | ||
25 | compress: true, | ||
26 | }); | ||
27 | if (embedFonts) { | ||
28 | document.addFont(normalFontURL, 'Open Sans', 'normal', 400); | ||
29 | document.addFont(italicFontURL, 'Open Sans', 'italic', 400); | ||
30 | document.addFont(boldFontURL, 'Open Sans', 'normal', 700); | ||
31 | } | ||
32 | const result = await svg2pdf(svg, document, { | ||
33 | width, | ||
34 | height, | ||
35 | }); | ||
36 | return result.output('blob'); | ||
37 | } | ||
diff --git a/subprojects/frontend/src/graph/postProcessSVG.ts b/subprojects/frontend/src/graph/postProcessSVG.ts index a580f5c6..bf990f3a 100644 --- a/subprojects/frontend/src/graph/postProcessSVG.ts +++ b/subprojects/frontend/src/graph/postProcessSVG.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | 6 | ||
7 | import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox'; | 7 | import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox'; |
8 | 8 | ||
9 | const SVG_NS = 'http://www.w3.org/2000/svg'; | 9 | export const SVG_NS = 'http://www.w3.org/2000/svg'; |
10 | const XLINK_NS = 'http://www.w3.org/1999/xlink'; | 10 | export const XLINK_NS = 'http://www.w3.org/1999/xlink'; |
11 | 11 | ||
12 | function modifyAttribute(element: Element, attribute: string, change: number) { | 12 | function modifyAttribute(element: Element, attribute: string, change: number) { |
13 | const valueString = element.getAttribute(attribute); | 13 | const valueString = element.getAttribute(attribute); |
@@ -166,6 +166,32 @@ function replaceImages(node: SVGGElement) { | |||
166 | }); | 166 | }); |
167 | } | 167 | } |
168 | 168 | ||
169 | function markerColorToClass(svg: SVGSVGElement) { | ||
170 | svg.querySelectorAll('.node [stroke="black"]').forEach((node) => { | ||
171 | node.removeAttribute('stroke'); | ||
172 | node.classList.add('node-outline'); | ||
173 | }); | ||
174 | svg.querySelectorAll('.node [fill="green"]').forEach((node) => { | ||
175 | node.removeAttribute('fill'); | ||
176 | node.classList.add('node-header'); | ||
177 | }); | ||
178 | svg.querySelectorAll('.node [fill="white"]').forEach((node) => { | ||
179 | node.removeAttribute('fill'); | ||
180 | node.classList.add('node-bg'); | ||
181 | }); | ||
182 | svg.querySelectorAll('.edge [stroke="black"]').forEach((node) => { | ||
183 | node.removeAttribute('stroke'); | ||
184 | node.classList.add('edge-line'); | ||
185 | }); | ||
186 | svg.querySelectorAll('.edge [fill="black"]').forEach((node) => { | ||
187 | node.removeAttribute('fill'); | ||
188 | node.classList.add('edge-arrow'); | ||
189 | }); | ||
190 | svg.querySelectorAll('[font-family]').forEach((node) => { | ||
191 | node.removeAttribute('font-family'); | ||
192 | }); | ||
193 | } | ||
194 | |||
169 | export default function postProcessSvg(svg: SVGSVGElement) { | 195 | export default function postProcessSvg(svg: SVGSVGElement) { |
170 | // svg | 196 | // svg |
171 | // .querySelectorAll<SVGTitleElement>('title') | 197 | // .querySelectorAll<SVGTitleElement>('title') |
@@ -183,4 +209,5 @@ export default function postProcessSvg(svg: SVGSVGElement) { | |||
183 | svg.viewBox.baseVal.height + 12, | 209 | svg.viewBox.baseVal.height + 12, |
184 | ]; | 210 | ]; |
185 | svg.setAttribute('viewBox', viewBox.join(' ')); | 211 | svg.setAttribute('viewBox', viewBox.join(' ')); |
212 | markerColorToClass(svg); | ||
186 | } | 213 | } |
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index a996cde8..6905fb4b 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -220,7 +220,7 @@ function createResponsiveTheme( | |||
220 | return responsiveFontSizes(themeWithOverrides); | 220 | return responsiveFontSizes(themeWithOverrides); |
221 | } | 221 | } |
222 | 222 | ||
223 | const lightTheme = (() => { | 223 | export const lightTheme = (() => { |
224 | const primaryText = '#19202b'; | 224 | const primaryText = '#19202b'; |
225 | const disabledText = '#a0a1a7'; | 225 | const disabledText = '#a0a1a7'; |
226 | const darkBackground = '#f5f5f5'; | 226 | const darkBackground = '#f5f5f5'; |
@@ -282,7 +282,7 @@ const lightTheme = (() => { | |||
282 | }); | 282 | }); |
283 | })(); | 283 | })(); |
284 | 284 | ||
285 | const darkTheme = (() => { | 285 | export const darkTheme = (() => { |
286 | const primaryText = '#ebebff'; | 286 | const primaryText = '#ebebff'; |
287 | const secondaryText = '#abb2bf'; | 287 | const secondaryText = '#abb2bf'; |
288 | const darkBackground = '#21252b'; | 288 | const darkBackground = '#21252b'; |
diff --git a/subprojects/frontend/src/utils/fileIO.ts b/subprojects/frontend/src/utils/fileIO.ts new file mode 100644 index 00000000..fe0b1fbb --- /dev/null +++ b/subprojects/frontend/src/utils/fileIO.ts | |||
@@ -0,0 +1,105 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | export interface OpenResult { | ||
8 | name: string; | ||
9 | handle: FileSystemFileHandle | undefined; | ||
10 | } | ||
11 | |||
12 | export interface OpenTextFileResult extends OpenResult { | ||
13 | text: string; | ||
14 | } | ||
15 | |||
16 | export async function openTextFile( | ||
17 | options: FilePickerOptions, | ||
18 | ): Promise<OpenTextFileResult> { | ||
19 | let file: File; | ||
20 | let handle: FileSystemFileHandle | undefined; | ||
21 | if ('showOpenFilePicker' in window) { | ||
22 | [handle] = await window.showOpenFilePicker(options); | ||
23 | if (handle === undefined) { | ||
24 | throw new Error('No file was selected'); | ||
25 | } | ||
26 | file = await handle.getFile(); | ||
27 | } else { | ||
28 | const input = document.createElement('input'); | ||
29 | input.type = 'file'; | ||
30 | file = await new Promise((resolve, reject) => { | ||
31 | input.addEventListener('change', () => { | ||
32 | const { files } = input; | ||
33 | const result = files?.item(0); | ||
34 | if (result) { | ||
35 | resolve(result); | ||
36 | } else { | ||
37 | reject(new Error('No file was selected')); | ||
38 | } | ||
39 | }); | ||
40 | input.click(); | ||
41 | }); | ||
42 | } | ||
43 | const text = await file.text(); | ||
44 | return { | ||
45 | name: file.name, | ||
46 | text, | ||
47 | handle, | ||
48 | }; | ||
49 | } | ||
50 | |||
51 | export async function saveTextFile( | ||
52 | handle: FileSystemFileHandle, | ||
53 | text: string, | ||
54 | ): Promise<void> { | ||
55 | const writable = await handle.createWritable(); | ||
56 | try { | ||
57 | await writable.write(text); | ||
58 | } finally { | ||
59 | await writable.close(); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | export async function saveBlob( | ||
64 | blob: Blob, | ||
65 | name: string, | ||
66 | options: FilePickerOptions, | ||
67 | ): Promise<OpenResult | undefined> { | ||
68 | if ('showSaveFilePicker' in window) { | ||
69 | const handle = await window.showSaveFilePicker({ | ||
70 | ...options, | ||
71 | suggestedName: name, | ||
72 | }); | ||
73 | const writable = await handle.createWritable(); | ||
74 | try { | ||
75 | await writable.write(blob); | ||
76 | } finally { | ||
77 | await writable.close(); | ||
78 | } | ||
79 | return { | ||
80 | name: handle.name, | ||
81 | handle, | ||
82 | }; | ||
83 | } | ||
84 | const link = document.createElement('a'); | ||
85 | const url = window.URL.createObjectURL(blob); | ||
86 | try { | ||
87 | link.href = url; | ||
88 | link.download = name; | ||
89 | link.click(); | ||
90 | } finally { | ||
91 | window.URL.revokeObjectURL(url); | ||
92 | } | ||
93 | return undefined; | ||
94 | } | ||
95 | |||
96 | export async function copyBlob(blob: Blob): Promise<void> { | ||
97 | const { clipboard } = navigator; | ||
98 | if ('write' in clipboard) { | ||
99 | await clipboard.write([ | ||
100 | new ClipboardItem({ | ||
101 | [blob.type]: blob, | ||
102 | }), | ||
103 | ]); | ||
104 | } | ||
105 | } | ||
diff --git a/subprojects/frontend/types/filesystemAccess.d.ts b/subprojects/frontend/types/filesystemAccess.d.ts new file mode 100644 index 00000000..e9accc77 --- /dev/null +++ b/subprojects/frontend/types/filesystemAccess.d.ts | |||
@@ -0,0 +1,40 @@ | |||
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 | id?: string; | ||
9 | types?: { | ||
10 | description?: string; | ||
11 | accept: Record<string, string[]>; | ||
12 | }[]; | ||
13 | } | ||
14 | |||
15 | interface FilePickerSaveOptions extends FilePickerOptions { | ||
16 | suggestedName?: string; | ||
17 | } | ||
18 | |||
19 | interface Window { | ||
20 | showOpenFilePicker?: ( | ||
21 | options?: FilePickerOpenOptions, | ||
22 | ) => Promise<FileSystemFileHandle[]>; | ||
23 | showSaveFilePicker?: ( | ||
24 | options?: FilePickerSaveOptions, | ||
25 | ) => Promise<FileSystemFileHandle>; | ||
26 | } | ||
27 | |||
28 | interface FileSystemHandlePermissionDescriptor { | ||
29 | mode?: 'read' | 'readwrite'; | ||
30 | } | ||
31 | |||
32 | interface FileSystemHandle { | ||
33 | queryPermission?: ( | ||
34 | options?: FileSystemHandlePermissionDescriptor, | ||
35 | ) => Promise<PermissionStatus>; | ||
36 | |||
37 | requestPermission?: ( | ||
38 | options?: FileSystemHandlePermissionDescriptor, | ||
39 | ) => Promise<PermissionStatus>; | ||
40 | } | ||
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index fffaae20..2d066e38 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -30,6 +30,9 @@ const { mode, isDevelopment, devModePlugins, serverOptions } = | |||
30 | process.env['NODE_ENV'] ??= mode; | 30 | process.env['NODE_ENV'] ??= mode; |
31 | 31 | ||
32 | const fontsGlob = [ | 32 | const fontsGlob = [ |
33 | 'open-sans-latin-*.ttf', | ||
34 | 'open-sans-latin-400-{normal,italic}-*.woff2', | ||
35 | 'open-sans-latin-700-*.woff2', | ||
33 | 'open-sans-latin-wdth-{normal,italic}-*.woff2', | 36 | 'open-sans-latin-wdth-{normal,italic}-*.woff2', |
34 | 'jetbrains-mono-latin-wght-{normal,italic}-*.woff2', | 37 | 'jetbrains-mono-latin-wght-{normal,italic}-*.woff2', |
35 | ]; | 38 | ]; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java index cc87917f..19eeeff3 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java | |||
@@ -20,8 +20,8 @@ public class SecurityHeadersFilter implements Filter { | |||
20 | // CodeMirror needs inline styles, see e.g., | 20 | // CodeMirror needs inline styles, see e.g., |
21 | // https://discuss.codemirror.net/t/inline-styles-and-content-security-policy/1311/2 | 21 | // https://discuss.codemirror.net/t/inline-styles-and-content-security-policy/1311/2 |
22 | "style-src 'self' 'unsafe-inline'; " + | 22 | "style-src 'self' 'unsafe-inline'; " + |
23 | // Use 'data:' for displaying inline SVG backgrounds. | 23 | // Use 'data:' for displaying inline SVG backgrounds and blob for rendering SVG. |
24 | "img-src 'self' data:; " + | 24 | "img-src 'self' data: blob:; " + |
25 | "font-src 'self'; " + | 25 | "font-src 'self'; " + |
26 | // Fetch data:application/octet-stream;base64 URIs to unpack compressed URL fragments. | 26 | // Fetch data:application/octet-stream;base64 URIs to unpack compressed URL fragments. |
27 | "connect-src 'self' data:; " + | 27 | "connect-src 'self' data:; " + |
@@ -36,12 +36,12 @@ __metadata: | |||
36 | linkType: hard | 36 | linkType: hard |
37 | 37 | ||
38 | "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.22.5": | 38 | "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.22.5": |
39 | version: 7.22.13 | 39 | version: 7.23.5 |
40 | resolution: "@babel/code-frame@npm:7.22.13" | 40 | resolution: "@babel/code-frame@npm:7.23.5" |
41 | dependencies: | 41 | dependencies: |
42 | "@babel/highlight": "npm:^7.22.13" | 42 | "@babel/highlight": "npm:^7.23.4" |
43 | chalk: "npm:^2.4.2" | 43 | chalk: "npm:^2.4.2" |
44 | checksum: 10c0/f4cc8ae1000265677daf4845083b72f88d00d311adb1a93c94eb4b07bf0ed6828a81ae4ac43ee7d476775000b93a28a9cddec18fbdc5796212d8dcccd5de72bd | 44 | checksum: 10c0/a10e843595ddd9f97faa99917414813c06214f4d9205294013e20c70fbdf4f943760da37dec1d998bf3e6fc20fa2918a47c0e987a7e458663feb7698063ad7c6 |
45 | languageName: node | 45 | languageName: node |
46 | linkType: hard | 46 | linkType: hard |
47 | 47 | ||
@@ -211,11 +211,11 @@ __metadata: | |||
211 | linkType: hard | 211 | linkType: hard |
212 | 212 | ||
213 | "@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.22.5": | 213 | "@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.22.5": |
214 | version: 7.22.5 | 214 | version: 7.22.15 |
215 | resolution: "@babel/helper-module-imports@npm:7.22.5" | 215 | resolution: "@babel/helper-module-imports@npm:7.22.15" |
216 | dependencies: | 216 | dependencies: |
217 | "@babel/types": "npm:^7.22.5" | 217 | "@babel/types": "npm:^7.22.15" |
218 | checksum: 10c0/04f8c0586c485c33017c63e0fc5fc16bd33b883cef3c88e4b3a8bf7bc807b3f9a7bcb9372fbcc01c0a539a5d1cdb477e7bdec77e250669edab00f796683b6b07 | 218 | checksum: 10c0/4e0d7fc36d02c1b8c8b3006dfbfeedf7a367d3334a04934255de5128115ea0bafdeb3e5736a2559917f0653e4e437400d54542da0468e08d3cbc86d3bbfa8f30 |
219 | languageName: node | 219 | languageName: node |
220 | linkType: hard | 220 | linkType: hard |
221 | 221 | ||
@@ -349,14 +349,14 @@ __metadata: | |||
349 | languageName: node | 349 | languageName: node |
350 | linkType: hard | 350 | linkType: hard |
351 | 351 | ||
352 | "@babel/highlight@npm:^7.22.13": | 352 | "@babel/highlight@npm:^7.23.4": |
353 | version: 7.22.20 | 353 | version: 7.23.4 |
354 | resolution: "@babel/highlight@npm:7.22.20" | 354 | resolution: "@babel/highlight@npm:7.23.4" |
355 | dependencies: | 355 | dependencies: |
356 | "@babel/helper-validator-identifier": "npm:^7.22.20" | 356 | "@babel/helper-validator-identifier": "npm:^7.22.20" |
357 | chalk: "npm:^2.4.2" | 357 | chalk: "npm:^2.4.2" |
358 | js-tokens: "npm:^4.0.0" | 358 | js-tokens: "npm:^4.0.0" |
359 | checksum: 10c0/f3c3a193afad23434297d88e81d1d6c0c2cf02423de2139ada7ce0a7fc62d8559abf4cc996533c1a9beca7fc990010eb8d544097f75e818ac113bf39ed810aa2 | 359 | checksum: 10c0/fbff9fcb2f5539289c3c097d130e852afd10d89a3a08ac0b5ebebbc055cc84a4bcc3dcfed463d488cde12dd0902ef1858279e31d7349b2e8cee43913744bda33 |
360 | languageName: node | 360 | languageName: node |
361 | linkType: hard | 361 | linkType: hard |
362 | 362 | ||
@@ -1234,7 +1234,7 @@ __metadata: | |||
1234 | languageName: node | 1234 | languageName: node |
1235 | linkType: hard | 1235 | linkType: hard |
1236 | 1236 | ||
1237 | "@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": | 1237 | "@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": |
1238 | version: 7.23.9 | 1238 | version: 7.23.9 |
1239 | resolution: "@babel/runtime@npm:7.23.9" | 1239 | resolution: "@babel/runtime@npm:7.23.9" |
1240 | dependencies: | 1240 | dependencies: |
@@ -1348,21 +1348,21 @@ __metadata: | |||
1348 | languageName: node | 1348 | languageName: node |
1349 | linkType: hard | 1349 | linkType: hard |
1350 | 1350 | ||
1351 | "@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0": | 1351 | "@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.4.1": |
1352 | version: 6.4.0 | 1352 | version: 6.4.1 |
1353 | resolution: "@codemirror/state@npm:6.4.0" | 1353 | resolution: "@codemirror/state@npm:6.4.1" |
1354 | checksum: 10c0/f1a94ab45dd5ae067d7f273bbe1a02fb118fa3defae012ff8a18711083bc7fb29436e3a7642c2621bb19a0d51546b398595b63dfdd48e5e15dcf6c629ec81ca1 | 1354 | checksum: 10c0/cdab74d0ca4e262531a257ac419c9c44124f3ace8b0ca1262598a9218fbb6fd8f0afeb4b5ed2f64552a9573a0fc5d55481d4b9b05e9505ef729f9bd0f9469423 |
1355 | languageName: node | 1355 | languageName: node |
1356 | linkType: hard | 1356 | linkType: hard |
1357 | 1357 | ||
1358 | "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.24.0": | 1358 | "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.24.1": |
1359 | version: 6.24.0 | 1359 | version: 6.24.1 |
1360 | resolution: "@codemirror/view@npm:6.24.0" | 1360 | resolution: "@codemirror/view@npm:6.24.1" |
1361 | dependencies: | 1361 | dependencies: |
1362 | "@codemirror/state": "npm:^6.4.0" | 1362 | "@codemirror/state": "npm:^6.4.0" |
1363 | style-mod: "npm:^4.1.0" | 1363 | style-mod: "npm:^4.1.0" |
1364 | w3c-keyname: "npm:^2.2.4" | 1364 | w3c-keyname: "npm:^2.2.4" |
1365 | checksum: 10c0/27ab6fb82093a533b0a1a2f032b4a0f5707ae85513d21350fb05e1e03c3284a8bb278054c1a77317a6ae512f491a466337f7e35a32eadfb4da33f7efa1b6dcde | 1365 | checksum: 10c0/56ec45fa5b064310dd9561cec324633b992c1b918b09c304e8856a4bc22eab0d686c5ab37ed8ccb6e4b19bd0dade5c8a3dd256f056305e7d91b303e847949d23 |
1366 | languageName: node | 1366 | languageName: node |
1367 | linkType: hard | 1367 | linkType: hard |
1368 | 1368 | ||
@@ -1701,10 +1701,10 @@ __metadata: | |||
1701 | languageName: node | 1701 | languageName: node |
1702 | linkType: hard | 1702 | linkType: hard |
1703 | 1703 | ||
1704 | "@eslint/js@npm:8.56.0": | 1704 | "@eslint/js@npm:8.57.0": |
1705 | version: 8.56.0 | 1705 | version: 8.57.0 |
1706 | resolution: "@eslint/js@npm:8.56.0" | 1706 | resolution: "@eslint/js@npm:8.57.0" |
1707 | checksum: 10c0/60b3a1cf240e2479cec9742424224465dc50e46d781da1b7f5ef240501b2d1202c225bd456207faac4b34a64f4765833345bc4ddffd00395e1db40fa8c426f5a | 1707 | checksum: 10c0/9a518bb8625ba3350613903a6d8c622352ab0c6557a59fe6ff6178bf882bf57123f9d92aa826ee8ac3ee74b9c6203fe630e9ee00efb03d753962dcf65ee4bd94 |
1708 | languageName: node | 1708 | languageName: node |
1709 | linkType: hard | 1709 | linkType: hard |
1710 | 1710 | ||
@@ -1760,6 +1760,13 @@ __metadata: | |||
1760 | languageName: node | 1760 | languageName: node |
1761 | linkType: hard | 1761 | linkType: hard |
1762 | 1762 | ||
1763 | "@fontsource/open-sans@npm:^5.0.24": | ||
1764 | version: 5.0.24 | ||
1765 | resolution: "@fontsource/open-sans@npm:5.0.24" | ||
1766 | checksum: 10c0/e3306f3ff0b9d85703f5b3114f33e31747dcbdf35a2eff425d0a9afb61bfd69c7eb29a691a5acd4582aa808ec123e51f223b49dad60d2caae9890d72e03feb7d | ||
1767 | languageName: node | ||
1768 | linkType: hard | ||
1769 | |||
1763 | "@hpcc-js/wasm@npm:^2.16.0": | 1770 | "@hpcc-js/wasm@npm:^2.16.0": |
1764 | version: 2.16.0 | 1771 | version: 2.16.0 |
1765 | resolution: "@hpcc-js/wasm@npm:2.16.0" | 1772 | resolution: "@hpcc-js/wasm@npm:2.16.0" |
@@ -1771,14 +1778,14 @@ __metadata: | |||
1771 | languageName: node | 1778 | languageName: node |
1772 | linkType: hard | 1779 | linkType: hard |
1773 | 1780 | ||
1774 | "@humanwhocodes/config-array@npm:^0.11.13": | 1781 | "@humanwhocodes/config-array@npm:^0.11.14": |
1775 | version: 0.11.13 | 1782 | version: 0.11.14 |
1776 | resolution: "@humanwhocodes/config-array@npm:0.11.13" | 1783 | resolution: "@humanwhocodes/config-array@npm:0.11.14" |
1777 | dependencies: | 1784 | dependencies: |
1778 | "@humanwhocodes/object-schema": "npm:^2.0.1" | 1785 | "@humanwhocodes/object-schema": "npm:^2.0.2" |
1779 | debug: "npm:^4.1.1" | 1786 | debug: "npm:^4.3.1" |
1780 | minimatch: "npm:^3.0.5" | 1787 | minimatch: "npm:^3.0.5" |
1781 | checksum: 10c0/d76ca802d853366094d0e98ff0d0994117fc8eff96649cd357b15e469e428228f597cd2e929d54ab089051684949955f16ee905bb19f7b2f0446fb377157be7a | 1788 | checksum: 10c0/66f725b4ee5fdd8322c737cb5013e19fac72d4d69c8bf4b7feb192fcb83442b035b92186f8e9497c220e58b2d51a080f28a73f7899bc1ab288c3be172c467541 |
1782 | languageName: node | 1789 | languageName: node |
1783 | linkType: hard | 1790 | linkType: hard |
1784 | 1791 | ||
@@ -1789,10 +1796,10 @@ __metadata: | |||
1789 | languageName: node | 1796 | languageName: node |
1790 | linkType: hard | 1797 | linkType: hard |
1791 | 1798 | ||
1792 | "@humanwhocodes/object-schema@npm:^2.0.1": | 1799 | "@humanwhocodes/object-schema@npm:^2.0.2": |
1793 | version: 2.0.1 | 1800 | version: 2.0.2 |
1794 | resolution: "@humanwhocodes/object-schema@npm:2.0.1" | 1801 | resolution: "@humanwhocodes/object-schema@npm:2.0.2" |
1795 | checksum: 10c0/9dba24e59fdb4041829d92b693aacb778add3b6f612aaa9c0774f3b650c11a378cc64f042a59da85c11dae33df456580a3c36837b953541aed6ff94294f97fac | 1802 | checksum: 10c0/6fd83dc320231d71c4541d0244051df61f301817e9f9da9fd4cb7e44ec8aacbde5958c1665b0c419401ab935114fdf532a6ad5d4e7294b1af2f347dd91a6983f |
1796 | languageName: node | 1803 | languageName: node |
1797 | linkType: hard | 1804 | linkType: hard |
1798 | 1805 | ||
@@ -1906,14 +1913,14 @@ __metadata: | |||
1906 | languageName: node | 1913 | languageName: node |
1907 | linkType: hard | 1914 | linkType: hard |
1908 | 1915 | ||
1909 | "@mui/base@npm:5.0.0-beta.36": | 1916 | "@mui/base@npm:5.0.0-beta.37": |
1910 | version: 5.0.0-beta.36 | 1917 | version: 5.0.0-beta.37 |
1911 | resolution: "@mui/base@npm:5.0.0-beta.36" | 1918 | resolution: "@mui/base@npm:5.0.0-beta.37" |
1912 | dependencies: | 1919 | dependencies: |
1913 | "@babel/runtime": "npm:^7.23.9" | 1920 | "@babel/runtime": "npm:^7.23.9" |
1914 | "@floating-ui/react-dom": "npm:^2.0.8" | 1921 | "@floating-ui/react-dom": "npm:^2.0.8" |
1915 | "@mui/types": "npm:^7.2.13" | 1922 | "@mui/types": "npm:^7.2.13" |
1916 | "@mui/utils": "npm:^5.15.9" | 1923 | "@mui/utils": "npm:^5.15.11" |
1917 | "@popperjs/core": "npm:^2.11.8" | 1924 | "@popperjs/core": "npm:^2.11.8" |
1918 | clsx: "npm:^2.1.0" | 1925 | clsx: "npm:^2.1.0" |
1919 | prop-types: "npm:^15.8.1" | 1926 | prop-types: "npm:^15.8.1" |
@@ -1924,20 +1931,20 @@ __metadata: | |||
1924 | peerDependenciesMeta: | 1931 | peerDependenciesMeta: |
1925 | "@types/react": | 1932 | "@types/react": |
1926 | optional: true | 1933 | optional: true |
1927 | checksum: 10c0/b789cd9e6906d56698bb5536704aefa475283081f1287b8340bb74dea4465565f7958c5e61bbc000f0d2a8e97aaf95a203f35cc737115e3edc7ea389da3d9c9e | 1934 | checksum: 10c0/bcee89198381b1058aa2a7e3122597a34a03fb3b351b1e0776674a996fb633bdeeae0656f14f905df7d4fced2aa6ae0a2b4fb3b462de387668481d8427448ca3 |
1928 | languageName: node | 1935 | languageName: node |
1929 | linkType: hard | 1936 | linkType: hard |
1930 | 1937 | ||
1931 | "@mui/core-downloads-tracker@npm:^5.15.10": | 1938 | "@mui/core-downloads-tracker@npm:^5.15.11": |
1932 | version: 5.15.10 | 1939 | version: 5.15.11 |
1933 | resolution: "@mui/core-downloads-tracker@npm:5.15.10" | 1940 | resolution: "@mui/core-downloads-tracker@npm:5.15.11" |
1934 | checksum: 10c0/d05da33eb3532994aa0b71e8131c8c21b4c6256976bfa704b2268c2bd88ae2c6853b0832c2af904507820e56563d373020827310abd626e146e026200b76eacf | 1941 | checksum: 10c0/e7d9fd674c1d71eb428ceffb9bbb36edaa421caa8c438e6977014f283bcae8530d6daa7c4289015576976505c6321ddaec2970b0af4c2d8619da206a025cc033 |
1935 | languageName: node | 1942 | languageName: node |
1936 | linkType: hard | 1943 | linkType: hard |
1937 | 1944 | ||
1938 | "@mui/icons-material@npm:^5.15.10": | 1945 | "@mui/icons-material@npm:^5.15.11": |
1939 | version: 5.15.10 | 1946 | version: 5.15.11 |
1940 | resolution: "@mui/icons-material@npm:5.15.10" | 1947 | resolution: "@mui/icons-material@npm:5.15.11" |
1941 | dependencies: | 1948 | dependencies: |
1942 | "@babel/runtime": "npm:^7.23.9" | 1949 | "@babel/runtime": "npm:^7.23.9" |
1943 | peerDependencies: | 1950 | peerDependencies: |
@@ -1947,20 +1954,20 @@ __metadata: | |||
1947 | peerDependenciesMeta: | 1954 | peerDependenciesMeta: |
1948 | "@types/react": | 1955 | "@types/react": |
1949 | optional: true | 1956 | optional: true |
1950 | checksum: 10c0/d49747d962ad9dec172d498173ee60fe29f3c818d15458fdc21b5d7a1cfa3a5acf8c6f3e6e35c665a3b0f4a088ac6806601c0d408403117f6711740448db4346 | 1957 | checksum: 10c0/59cc9e62a82985cd357815df021ab2d4ef3b8528574ca8d67245ef02eb4df290110ba146bf014ece463992906fdb6e08b495800f94644b9c43ef1b3a4cbf6783 |
1951 | languageName: node | 1958 | languageName: node |
1952 | linkType: hard | 1959 | linkType: hard |
1953 | 1960 | ||
1954 | "@mui/material@npm:^5.15.10": | 1961 | "@mui/material@npm:^5.15.11": |
1955 | version: 5.15.10 | 1962 | version: 5.15.11 |
1956 | resolution: "@mui/material@npm:5.15.10" | 1963 | resolution: "@mui/material@npm:5.15.11" |
1957 | dependencies: | 1964 | dependencies: |
1958 | "@babel/runtime": "npm:^7.23.9" | 1965 | "@babel/runtime": "npm:^7.23.9" |
1959 | "@mui/base": "npm:5.0.0-beta.36" | 1966 | "@mui/base": "npm:5.0.0-beta.37" |
1960 | "@mui/core-downloads-tracker": "npm:^5.15.10" | 1967 | "@mui/core-downloads-tracker": "npm:^5.15.11" |
1961 | "@mui/system": "npm:^5.15.9" | 1968 | "@mui/system": "npm:^5.15.11" |
1962 | "@mui/types": "npm:^7.2.13" | 1969 | "@mui/types": "npm:^7.2.13" |
1963 | "@mui/utils": "npm:^5.15.9" | 1970 | "@mui/utils": "npm:^5.15.11" |
1964 | "@types/react-transition-group": "npm:^4.4.10" | 1971 | "@types/react-transition-group": "npm:^4.4.10" |
1965 | clsx: "npm:^2.1.0" | 1972 | clsx: "npm:^2.1.0" |
1966 | csstype: "npm:^3.1.3" | 1973 | csstype: "npm:^3.1.3" |
@@ -1980,16 +1987,16 @@ __metadata: | |||
1980 | optional: true | 1987 | optional: true |
1981 | "@types/react": | 1988 | "@types/react": |
1982 | optional: true | 1989 | optional: true |
1983 | checksum: 10c0/057147a7f68269641954dd456852a801c71fe10cc1dee7c4d669e8327a792ac2dee4083c4562b918a3696276a9035ba2a5b1b5261dc7f1a385711b938ae508fe | 1990 | checksum: 10c0/21f885a07d3cf1407d21a3f1d1dcd86076585508f912e85c00d8093e181b4dfb2fc8ebf954e06f699f691220eb4a3f1601f7a51b39e90486ae7fcd222f114015 |
1984 | languageName: node | 1991 | languageName: node |
1985 | linkType: hard | 1992 | linkType: hard |
1986 | 1993 | ||
1987 | "@mui/private-theming@npm:^5.15.9": | 1994 | "@mui/private-theming@npm:^5.15.11": |
1988 | version: 5.15.9 | 1995 | version: 5.15.11 |
1989 | resolution: "@mui/private-theming@npm:5.15.9" | 1996 | resolution: "@mui/private-theming@npm:5.15.11" |
1990 | dependencies: | 1997 | dependencies: |
1991 | "@babel/runtime": "npm:^7.23.9" | 1998 | "@babel/runtime": "npm:^7.23.9" |
1992 | "@mui/utils": "npm:^5.15.9" | 1999 | "@mui/utils": "npm:^5.15.11" |
1993 | prop-types: "npm:^15.8.1" | 2000 | prop-types: "npm:^15.8.1" |
1994 | peerDependencies: | 2001 | peerDependencies: |
1995 | "@types/react": ^17.0.0 || ^18.0.0 | 2002 | "@types/react": ^17.0.0 || ^18.0.0 |
@@ -1997,13 +2004,13 @@ __metadata: | |||
1997 | peerDependenciesMeta: | 2004 | peerDependenciesMeta: |
1998 | "@types/react": | 2005 | "@types/react": |
1999 | optional: true | 2006 | optional: true |
2000 | checksum: 10c0/a5c995b7f2faa9aa39ccf258bc2cafde9a8c265dd15bfbaec974650690d81d662eaae0524f0f8e17d16fe7893a6907cc0c388ac5648cce7b502a77d722daa116 | 2007 | checksum: 10c0/29763c2df3a18bd5a4772302dc1d734c9101c02f42b7ae1c2c21b048c4fc5b7a12f83c30ac2297087c1499222ebcfcd3e23d72717cc163732f962dfde5260dd3 |
2001 | languageName: node | 2008 | languageName: node |
2002 | linkType: hard | 2009 | linkType: hard |
2003 | 2010 | ||
2004 | "@mui/styled-engine@npm:^5.15.9": | 2011 | "@mui/styled-engine@npm:^5.15.11": |
2005 | version: 5.15.9 | 2012 | version: 5.15.11 |
2006 | resolution: "@mui/styled-engine@npm:5.15.9" | 2013 | resolution: "@mui/styled-engine@npm:5.15.11" |
2007 | dependencies: | 2014 | dependencies: |
2008 | "@babel/runtime": "npm:^7.23.9" | 2015 | "@babel/runtime": "npm:^7.23.9" |
2009 | "@emotion/cache": "npm:^11.11.0" | 2016 | "@emotion/cache": "npm:^11.11.0" |
@@ -2018,19 +2025,19 @@ __metadata: | |||
2018 | optional: true | 2025 | optional: true |
2019 | "@emotion/styled": | 2026 | "@emotion/styled": |
2020 | optional: true | 2027 | optional: true |
2021 | checksum: 10c0/47968ec55a5a36936dc8c245c1d62fe9be82bdaba800aeea08d111702b3aea8388bc3c2a46ee4ee315efe77b1c7ecf63308fc518afa69114b2fc90a203de48e0 | 2028 | checksum: 10c0/b168565256c82ed34946f954e9088c339917c980451b5dd3d6dca9d36e6d39b5900ea85ffc5736b66d25f841dc761a8c2c70c111c42bec794309f2cafb3975ce |
2022 | languageName: node | 2029 | languageName: node |
2023 | linkType: hard | 2030 | linkType: hard |
2024 | 2031 | ||
2025 | "@mui/system@npm:^5.15.9": | 2032 | "@mui/system@npm:^5.15.11": |
2026 | version: 5.15.9 | 2033 | version: 5.15.11 |
2027 | resolution: "@mui/system@npm:5.15.9" | 2034 | resolution: "@mui/system@npm:5.15.11" |
2028 | dependencies: | 2035 | dependencies: |
2029 | "@babel/runtime": "npm:^7.23.9" | 2036 | "@babel/runtime": "npm:^7.23.9" |
2030 | "@mui/private-theming": "npm:^5.15.9" | 2037 | "@mui/private-theming": "npm:^5.15.11" |
2031 | "@mui/styled-engine": "npm:^5.15.9" | 2038 | "@mui/styled-engine": "npm:^5.15.11" |
2032 | "@mui/types": "npm:^7.2.13" | 2039 | "@mui/types": "npm:^7.2.13" |
2033 | "@mui/utils": "npm:^5.15.9" | 2040 | "@mui/utils": "npm:^5.15.11" |
2034 | clsx: "npm:^2.1.0" | 2041 | clsx: "npm:^2.1.0" |
2035 | csstype: "npm:^3.1.3" | 2042 | csstype: "npm:^3.1.3" |
2036 | prop-types: "npm:^15.8.1" | 2043 | prop-types: "npm:^15.8.1" |
@@ -2046,7 +2053,7 @@ __metadata: | |||
2046 | optional: true | 2053 | optional: true |
2047 | "@types/react": | 2054 | "@types/react": |
2048 | optional: true | 2055 | optional: true |
2049 | checksum: 10c0/187a14e689be1434c7db646f2f0f19c5fe60eb565dd0fa2d0a7b903240fd36ccdd3a5d2318630a9e0186036ef61fbebd58a9ef752f7b9b329660614ddbdf37f1 | 2056 | checksum: 10c0/27094ac751ad024bf9e67b26feb94dd09ea3550facb6d3dc1a6e15f1901a1cc03751221826908fa881df1eab5d5c03c8b5e737bfae627e1d5a0fa3033c5decaa |
2050 | languageName: node | 2057 | languageName: node |
2051 | linkType: hard | 2058 | linkType: hard |
2052 | 2059 | ||
@@ -2062,9 +2069,9 @@ __metadata: | |||
2062 | languageName: node | 2069 | languageName: node |
2063 | linkType: hard | 2070 | linkType: hard |
2064 | 2071 | ||
2065 | "@mui/utils@npm:^5.14.16, @mui/utils@npm:^5.15.9": | 2072 | "@mui/utils@npm:^5.14.16, @mui/utils@npm:^5.15.11": |
2066 | version: 5.15.9 | 2073 | version: 5.15.11 |
2067 | resolution: "@mui/utils@npm:5.15.9" | 2074 | resolution: "@mui/utils@npm:5.15.11" |
2068 | dependencies: | 2075 | dependencies: |
2069 | "@babel/runtime": "npm:^7.23.9" | 2076 | "@babel/runtime": "npm:^7.23.9" |
2070 | "@types/prop-types": "npm:^15.7.11" | 2077 | "@types/prop-types": "npm:^15.7.11" |
@@ -2076,13 +2083,13 @@ __metadata: | |||
2076 | peerDependenciesMeta: | 2083 | peerDependenciesMeta: |
2077 | "@types/react": | 2084 | "@types/react": |
2078 | optional: true | 2085 | optional: true |
2079 | checksum: 10c0/42d3db7f73f47ad719b8a4769eac2aeaec683166c953e67518f6edb7d14464576daf6566619a384f5f7e48dcb1478053ff3e4ce1be244fd332f7e988da9e1401 | 2086 | checksum: 10c0/bbdd0a8cd5bd04021b6e0b7836bd5a5852e54d95392ad786816dd0e5c1942f82f5d645721ac8c716394f74b0628a8c909139fc86103a3aa40d147ab9eb234fb9 |
2080 | languageName: node | 2087 | languageName: node |
2081 | linkType: hard | 2088 | linkType: hard |
2082 | 2089 | ||
2083 | "@mui/x-data-grid@npm:^6.19.4": | 2090 | "@mui/x-data-grid@npm:^6.19.5": |
2084 | version: 6.19.4 | 2091 | version: 6.19.5 |
2085 | resolution: "@mui/x-data-grid@npm:6.19.4" | 2092 | resolution: "@mui/x-data-grid@npm:6.19.5" |
2086 | dependencies: | 2093 | dependencies: |
2087 | "@babel/runtime": "npm:^7.23.2" | 2094 | "@babel/runtime": "npm:^7.23.2" |
2088 | "@mui/utils": "npm:^5.14.16" | 2095 | "@mui/utils": "npm:^5.14.16" |
@@ -2094,7 +2101,7 @@ __metadata: | |||
2094 | "@mui/system": ^5.4.1 | 2101 | "@mui/system": ^5.4.1 |
2095 | react: ^17.0.0 || ^18.0.0 | 2102 | react: ^17.0.0 || ^18.0.0 |
2096 | react-dom: ^17.0.0 || ^18.0.0 | 2103 | react-dom: ^17.0.0 || ^18.0.0 |
2097 | checksum: 10c0/2de403c7cadb9c34baed609afc500288c927518abf70f362eb8a45ac6161497aebb9f3b13e72e0192a6deb508c0db2ffb13d8ae09cece2e52490bd2b956179ed | 2104 | checksum: 10c0/64fadfab30fc210c5f7f4fff16bc08a4357498c67b8c341106d42189bcda8e17e59115b9b7cdae7af1f0c181ce4afeae9b41d56d455dd2bfad51bb8c5ef3786d |
2098 | languageName: node | 2105 | languageName: node |
2099 | linkType: hard | 2106 | linkType: hard |
2100 | 2107 | ||
@@ -2164,34 +2171,39 @@ __metadata: | |||
2164 | "@codemirror/language": "npm:^6.10.1" | 2171 | "@codemirror/language": "npm:^6.10.1" |
2165 | "@codemirror/lint": "npm:^6.5.0" | 2172 | "@codemirror/lint": "npm:^6.5.0" |
2166 | "@codemirror/search": "npm:^6.5.6" | 2173 | "@codemirror/search": "npm:^6.5.6" |
2167 | "@codemirror/state": "npm:^6.4.0" | 2174 | "@codemirror/state": "npm:^6.4.1" |
2168 | "@codemirror/view": "npm:^6.24.0" | 2175 | "@codemirror/view": "npm:^6.24.1" |
2176 | "@emotion/cache": "npm:^11.11.0" | ||
2169 | "@emotion/react": "npm:^11.11.3" | 2177 | "@emotion/react": "npm:^11.11.3" |
2178 | "@emotion/serialize": "npm:^1.1.3" | ||
2170 | "@emotion/styled": "npm:^11.11.0" | 2179 | "@emotion/styled": "npm:^11.11.0" |
2180 | "@emotion/utils": "npm:^1.2.1" | ||
2171 | "@fontsource-variable/jetbrains-mono": "npm:^5.0.19" | 2181 | "@fontsource-variable/jetbrains-mono": "npm:^5.0.19" |
2172 | "@fontsource-variable/open-sans": "npm:^5.0.25" | 2182 | "@fontsource-variable/open-sans": "npm:^5.0.25" |
2183 | "@fontsource/open-sans": "npm:^5.0.24" | ||
2173 | "@hpcc-js/wasm": "npm:^2.16.0" | 2184 | "@hpcc-js/wasm": "npm:^2.16.0" |
2174 | "@lezer/common": "npm:^1.2.1" | 2185 | "@lezer/common": "npm:^1.2.1" |
2175 | "@lezer/generator": "npm:^1.6.0" | 2186 | "@lezer/generator": "npm:^1.6.0" |
2176 | "@lezer/highlight": "npm:^1.2.0" | 2187 | "@lezer/highlight": "npm:^1.2.0" |
2177 | "@lezer/lr": "npm:^1.4.0" | 2188 | "@lezer/lr": "npm:^1.4.0" |
2178 | "@material-icons/svg": "npm:^1.0.33" | 2189 | "@material-icons/svg": "npm:^1.0.33" |
2179 | "@mui/icons-material": "npm:^5.15.10" | 2190 | "@mui/icons-material": "npm:^5.15.11" |
2180 | "@mui/material": "npm:^5.15.10" | 2191 | "@mui/material": "npm:^5.15.11" |
2181 | "@mui/system": "npm:^5.15.9" | 2192 | "@mui/system": "npm:^5.15.11" |
2182 | "@mui/x-data-grid": "npm:^6.19.4" | 2193 | "@mui/x-data-grid": "npm:^6.19.5" |
2183 | "@types/d3": "npm:^7.4.3" | 2194 | "@types/d3": "npm:^7.4.3" |
2184 | "@types/d3-graphviz": "npm:^2.6.10" | 2195 | "@types/d3-graphviz": "npm:^2.6.10" |
2185 | "@types/d3-selection": "npm:^3.0.10" | 2196 | "@types/d3-selection": "npm:^3.0.10" |
2186 | "@types/d3-zoom": "npm:^3.0.8" | 2197 | "@types/d3-zoom": "npm:^3.0.8" |
2187 | "@types/eslint": "npm:^8.56.2" | 2198 | "@types/eslint": "npm:^8.56.3" |
2188 | "@types/html-minifier-terser": "npm:^7.0.2" | 2199 | "@types/html-minifier-terser": "npm:^7.0.2" |
2200 | "@types/jspdf": "npm:^2.0.0" | ||
2189 | "@types/lodash-es": "npm:^4.17.12" | 2201 | "@types/lodash-es": "npm:^4.17.12" |
2190 | "@types/micromatch": "npm:^4.0.6" | 2202 | "@types/micromatch": "npm:^4.0.6" |
2191 | "@types/ms": "npm:^0.7.34" | 2203 | "@types/ms": "npm:^0.7.34" |
2192 | "@types/node": "npm:^20.11.19" | 2204 | "@types/node": "npm:^20.11.20" |
2193 | "@types/pnpapi": "npm:^0.0.5" | 2205 | "@types/pnpapi": "npm:^0.0.5" |
2194 | "@types/react": "npm:^18.2.56" | 2206 | "@types/react": "npm:^18.2.58" |
2195 | "@types/react-dom": "npm:^18.2.19" | 2207 | "@types/react-dom": "npm:^18.2.19" |
2196 | "@typescript-eslint/eslint-plugin": "npm:^6.21.0" | 2208 | "@typescript-eslint/eslint-plugin": "npm:^6.21.0" |
2197 | "@typescript-eslint/parser": "npm:^6.21.0" | 2209 | "@typescript-eslint/parser": "npm:^6.21.0" |
@@ -2205,7 +2217,7 @@ __metadata: | |||
2205 | d3-selection: "npm:^3.0.0" | 2217 | d3-selection: "npm:^3.0.0" |
2206 | d3-zoom: "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch" | 2218 | d3-zoom: "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch" |
2207 | escape-string-regexp: "npm:^5.0.0" | 2219 | escape-string-regexp: "npm:^5.0.0" |
2208 | eslint: "npm:^8.56.0" | 2220 | eslint: "npm:^8.57.0" |
2209 | eslint-config-airbnb: "npm:^19.0.4" | 2221 | eslint-config-airbnb: "npm:^19.0.4" |
2210 | eslint-config-airbnb-typescript: "npm:^17.1.0" | 2222 | eslint-config-airbnb-typescript: "npm:^17.1.0" |
2211 | eslint-config-prettier: "npm:^9.1.0" | 2223 | eslint-config-prettier: "npm:^9.1.0" |
@@ -2217,6 +2229,7 @@ __metadata: | |||
2217 | eslint-plugin-react: "npm:^7.33.2" | 2229 | eslint-plugin-react: "npm:^7.33.2" |
2218 | eslint-plugin-react-hooks: "npm:^4.6.0" | 2230 | eslint-plugin-react-hooks: "npm:^4.6.0" |
2219 | html-minifier-terser: "npm:^7.2.0" | 2231 | html-minifier-terser: "npm:^7.2.0" |
2232 | jspdf: "npm:^2.5.1" | ||
2220 | lodash-es: "npm:^4.17.21" | 2233 | lodash-es: "npm:^4.17.21" |
2221 | loglevel: "npm:^1.9.1" | 2234 | loglevel: "npm:^1.9.1" |
2222 | loglevel-plugin-prefix: "npm:^0.8.4" | 2235 | loglevel-plugin-prefix: "npm:^0.8.4" |
@@ -2224,15 +2237,16 @@ __metadata: | |||
2224 | mobx: "npm:^6.12.0" | 2237 | mobx: "npm:^6.12.0" |
2225 | mobx-react-lite: "npm:^4.0.5" | 2238 | mobx-react-lite: "npm:^4.0.5" |
2226 | ms: "npm:^2.1.3" | 2239 | ms: "npm:^2.1.3" |
2227 | nanoid: "npm:^5.0.5" | 2240 | nanoid: "npm:^5.0.6" |
2228 | notistack: "npm:^3.0.1" | 2241 | notistack: "npm:^3.0.1" |
2229 | pnpapi: "npm:^0.0.0" | 2242 | pnpapi: "npm:^0.0.0" |
2230 | prettier: "npm:^3.2.5" | 2243 | prettier: "npm:^3.2.5" |
2231 | react: "npm:^18.2.0" | 2244 | react: "npm:^18.2.0" |
2232 | react-dom: "npm:^18.2.0" | 2245 | react-dom: "npm:^18.2.0" |
2233 | react-resize-detector: "npm:^10.0.1" | 2246 | react-resize-detector: "npm:^10.0.1" |
2247 | svg2pdf.js: "npm:^2.2.3" | ||
2234 | typescript: "npm:5.3.3" | 2248 | typescript: "npm:5.3.3" |
2235 | vite: "npm:^5.1.3" | 2249 | vite: "npm:^5.1.4" |
2236 | vite-plugin-pwa: "npm:^0.19.0" | 2250 | vite-plugin-pwa: "npm:^0.19.0" |
2237 | workbox-window: "npm:^7.0.0" | 2251 | workbox-window: "npm:^7.0.0" |
2238 | xstate: "npm:^4.38.3" | 2252 | xstate: "npm:^4.38.3" |
@@ -2244,7 +2258,7 @@ __metadata: | |||
2244 | version: 0.0.0-use.local | 2258 | version: 0.0.0-use.local |
2245 | resolution: "@refinery/root@workspace:." | 2259 | resolution: "@refinery/root@workspace:." |
2246 | dependencies: | 2260 | dependencies: |
2247 | eslint: "npm:^8.56.0" | 2261 | eslint: "npm:^8.57.0" |
2248 | typescript: "npm:5.3.3" | 2262 | typescript: "npm:5.3.3" |
2249 | languageName: unknown | 2263 | languageName: unknown |
2250 | linkType: soft | 2264 | linkType: soft |
@@ -2872,13 +2886,13 @@ __metadata: | |||
2872 | languageName: node | 2886 | languageName: node |
2873 | linkType: hard | 2887 | linkType: hard |
2874 | 2888 | ||
2875 | "@types/eslint@npm:^8.56.2": | 2889 | "@types/eslint@npm:^8.56.3": |
2876 | version: 8.56.2 | 2890 | version: 8.56.3 |
2877 | resolution: "@types/eslint@npm:8.56.2" | 2891 | resolution: "@types/eslint@npm:8.56.3" |
2878 | dependencies: | 2892 | dependencies: |
2879 | "@types/estree": "npm:*" | 2893 | "@types/estree": "npm:*" |
2880 | "@types/json-schema": "npm:*" | 2894 | "@types/json-schema": "npm:*" |
2881 | checksum: 10c0/e33ca87a30a9454ba9943e1270ac759996f5fe598a1c1afbaec1d1e7346a339e20bf2a9d81f177067116bbaa6cfa4f748993cb338f57978ae862ad38ffae56fe | 2895 | checksum: 10c0/c5d81d0001fae211451b39d82b2bc8d7224b00d52a514954a33840a3665f36f3bde3be602eec6ad08d1fff59108052cd7746ced4237116bc3d8ac01a7cf5b5fe |
2882 | languageName: node | 2896 | languageName: node |
2883 | linkType: hard | 2897 | linkType: hard |
2884 | 2898 | ||
@@ -2924,6 +2938,15 @@ __metadata: | |||
2924 | languageName: node | 2938 | languageName: node |
2925 | linkType: hard | 2939 | linkType: hard |
2926 | 2940 | ||
2941 | "@types/jspdf@npm:^2.0.0": | ||
2942 | version: 2.0.0 | ||
2943 | resolution: "@types/jspdf@npm:2.0.0" | ||
2944 | dependencies: | ||
2945 | jspdf: "npm:*" | ||
2946 | checksum: 10c0/69bed9c099c9a0d369c2734fa97862cb67c4d1151ce45b7f9f3c6dfedf7b1bb612cf0561aa5768b7adf5cec207adf2fa82ffbcb7907c716b33b1f5a8f75fe5fc | ||
2947 | languageName: node | ||
2948 | linkType: hard | ||
2949 | |||
2927 | "@types/lodash-es@npm:^4.17.12": | 2950 | "@types/lodash-es@npm:^4.17.12": |
2928 | version: 4.17.12 | 2951 | version: 4.17.12 |
2929 | resolution: "@types/lodash-es@npm:4.17.12" | 2952 | resolution: "@types/lodash-es@npm:4.17.12" |
@@ -2956,19 +2979,19 @@ __metadata: | |||
2956 | languageName: node | 2979 | languageName: node |
2957 | linkType: hard | 2980 | linkType: hard |
2958 | 2981 | ||
2959 | "@types/node@npm:*, @types/node@npm:^20.11.19": | 2982 | "@types/node@npm:*, @types/node@npm:^20.11.20": |
2960 | version: 20.11.19 | 2983 | version: 20.11.20 |
2961 | resolution: "@types/node@npm:20.11.19" | 2984 | resolution: "@types/node@npm:20.11.20" |
2962 | dependencies: | 2985 | dependencies: |
2963 | undici-types: "npm:~5.26.4" | 2986 | undici-types: "npm:~5.26.4" |
2964 | checksum: 10c0/f451ef0a1d78f29c57bad7b77e49ebec945f2a6d0d7a89851d7e185ee9fe7ad94d651c0dfbcb7858c9fa791310c8b40a881e2260f56bd3c1b7e7ae92723373ae | 2987 | checksum: 10c0/8e8de211e6d54425c603388a9b5cc9c434101985d0a1c88aabbf65d10df2b1fccd71855c20e61ae8a75c7aea56cb0f64e722cf7914cff1247d0b62ce21996ac4 |
2965 | languageName: node | 2988 | languageName: node |
2966 | linkType: hard | 2989 | linkType: hard |
2967 | 2990 | ||
2968 | "@types/parse-json@npm:^4.0.0": | 2991 | "@types/parse-json@npm:^4.0.0": |
2969 | version: 4.0.0 | 2992 | version: 4.0.2 |
2970 | resolution: "@types/parse-json@npm:4.0.0" | 2993 | resolution: "@types/parse-json@npm:4.0.2" |
2971 | checksum: 10c0/1d3012ab2fcdad1ba313e1d065b737578f6506c8958e2a7a5bdbdef517c7e930796cb1599ee067d5dee942fb3a764df64b5eef7e9ae98548d776e86dcffba985 | 2994 | checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 |
2972 | languageName: node | 2995 | languageName: node |
2973 | linkType: hard | 2996 | linkType: hard |
2974 | 2997 | ||
@@ -2986,6 +3009,13 @@ __metadata: | |||
2986 | languageName: node | 3009 | languageName: node |
2987 | linkType: hard | 3010 | linkType: hard |
2988 | 3011 | ||
3012 | "@types/raf@npm:^3.4.0": | ||
3013 | version: 3.4.3 | ||
3014 | resolution: "@types/raf@npm:3.4.3" | ||
3015 | checksum: 10c0/dea835f0daa399c51db9137f5337dc08a2b4a5f61f645658966ecabaebbbd0fd59551f384a1141e14e22a1cc5a591da7d4d88c60a525ad1399108b6dd2641d75 | ||
3016 | languageName: node | ||
3017 | linkType: hard | ||
3018 | |||
2989 | "@types/react-dom@npm:^18.2.19": | 3019 | "@types/react-dom@npm:^18.2.19": |
2990 | version: 18.2.19 | 3020 | version: 18.2.19 |
2991 | resolution: "@types/react-dom@npm:18.2.19" | 3021 | resolution: "@types/react-dom@npm:18.2.19" |
@@ -3004,14 +3034,14 @@ __metadata: | |||
3004 | languageName: node | 3034 | languageName: node |
3005 | linkType: hard | 3035 | linkType: hard |
3006 | 3036 | ||
3007 | "@types/react@npm:*, @types/react@npm:^18.2.56": | 3037 | "@types/react@npm:*, @types/react@npm:^18.2.58": |
3008 | version: 18.2.56 | 3038 | version: 18.2.58 |
3009 | resolution: "@types/react@npm:18.2.56" | 3039 | resolution: "@types/react@npm:18.2.58" |
3010 | dependencies: | 3040 | dependencies: |
3011 | "@types/prop-types": "npm:*" | 3041 | "@types/prop-types": "npm:*" |
3012 | "@types/scheduler": "npm:*" | 3042 | "@types/scheduler": "npm:*" |
3013 | csstype: "npm:^3.0.2" | 3043 | csstype: "npm:^3.0.2" |
3014 | checksum: 10c0/a6dab9569799538a9e01d340a721ef1a6f5532bd11cae8d8ab9af00dab2edcafaa00950f7bf2f9ae5bbb1839d890e9ac6eb1ea1186200894b7178dde7b503269 | 3044 | checksum: 10c0/80145b707b780d682092b51d520f58a0171c4067ff36cf488d3346d92b715b27fd334acd0fabb8eb21a4eb6c4061f1535e8bfa6642a7f4025e63ebec868fb6d1 |
3015 | languageName: node | 3045 | languageName: node |
3016 | linkType: hard | 3046 | linkType: hard |
3017 | 3047 | ||
@@ -3537,6 +3567,15 @@ __metadata: | |||
3537 | languageName: node | 3567 | languageName: node |
3538 | linkType: hard | 3568 | linkType: hard |
3539 | 3569 | ||
3570 | "atob@npm:^2.1.2": | ||
3571 | version: 2.1.2 | ||
3572 | resolution: "atob@npm:2.1.2" | ||
3573 | bin: | ||
3574 | atob: bin/atob.js | ||
3575 | checksum: 10c0/ada635b519dc0c576bb0b3ca63a73b50eefacf390abb3f062558342a8d68f2db91d0c8db54ce81b0d89de3b0f000de71f3ae7d761fd7d8cc624278fe443d6c7e | ||
3576 | languageName: node | ||
3577 | linkType: hard | ||
3578 | |||
3540 | "available-typed-arrays@npm:^1.0.5": | 3579 | "available-typed-arrays@npm:^1.0.5": |
3541 | version: 1.0.5 | 3580 | version: 1.0.5 |
3542 | resolution: "available-typed-arrays@npm:1.0.5" | 3581 | resolution: "available-typed-arrays@npm:1.0.5" |
@@ -3623,6 +3662,13 @@ __metadata: | |||
3623 | languageName: node | 3662 | languageName: node |
3624 | linkType: hard | 3663 | linkType: hard |
3625 | 3664 | ||
3665 | "base64-arraybuffer@npm:^1.0.2": | ||
3666 | version: 1.0.2 | ||
3667 | resolution: "base64-arraybuffer@npm:1.0.2" | ||
3668 | checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f | ||
3669 | languageName: node | ||
3670 | linkType: hard | ||
3671 | |||
3626 | "binary-extensions@npm:^2.0.0": | 3672 | "binary-extensions@npm:^2.0.0": |
3627 | version: 2.2.0 | 3673 | version: 2.2.0 |
3628 | resolution: "binary-extensions@npm:2.2.0" | 3674 | resolution: "binary-extensions@npm:2.2.0" |
@@ -3672,6 +3718,15 @@ __metadata: | |||
3672 | languageName: node | 3718 | languageName: node |
3673 | linkType: hard | 3719 | linkType: hard |
3674 | 3720 | ||
3721 | "btoa@npm:^1.2.1": | ||
3722 | version: 1.2.1 | ||
3723 | resolution: "btoa@npm:1.2.1" | ||
3724 | bin: | ||
3725 | btoa: bin/btoa.js | ||
3726 | checksum: 10c0/557b9682e40a68ae057af1b377e28884e6ff756ba0f499fe0f8c7b725a5bfb5c0d891604ac09944dbe330c9d43fb3976fef734f9372608d0d8e78a30eda292ae | ||
3727 | languageName: node | ||
3728 | linkType: hard | ||
3729 | |||
3675 | "buffer-from@npm:^1.0.0": | 3730 | "buffer-from@npm:^1.0.0": |
3676 | version: 1.1.2 | 3731 | version: 1.1.2 |
3677 | resolution: "buffer-from@npm:1.1.2" | 3732 | resolution: "buffer-from@npm:1.1.2" |
@@ -3735,9 +3790,25 @@ __metadata: | |||
3735 | linkType: hard | 3790 | linkType: hard |
3736 | 3791 | ||
3737 | "caniuse-lite@npm:^1.0.30001517": | 3792 | "caniuse-lite@npm:^1.0.30001517": |
3738 | version: 1.0.30001588 | 3793 | version: 1.0.30001589 |
3739 | resolution: "caniuse-lite@npm:1.0.30001588" | 3794 | resolution: "caniuse-lite@npm:1.0.30001589" |
3740 | checksum: 10c0/f8333cb52e7ebc169d462763cecc33807530f1e04d22ba1084e05a583907aa801fb3c013d60b38d54cb792440f48efcd2a1a68f22d5fce896b5bd0277392347c | 3795 | checksum: 10c0/20debfb949413f603011bc7dacaf050010778bc4f8632c86fafd1bd0c43180c95ae7c31f6c82348f6309e5e221934e327c3607a216e3f09640284acf78cd6d4d |
3796 | languageName: node | ||
3797 | linkType: hard | ||
3798 | |||
3799 | "canvg@npm:^3.0.6": | ||
3800 | version: 3.0.10 | ||
3801 | resolution: "canvg@npm:3.0.10" | ||
3802 | dependencies: | ||
3803 | "@babel/runtime": "npm:^7.12.5" | ||
3804 | "@types/raf": "npm:^3.4.0" | ||
3805 | core-js: "npm:^3.8.3" | ||
3806 | raf: "npm:^3.4.1" | ||
3807 | regenerator-runtime: "npm:^0.13.7" | ||
3808 | rgbcolor: "npm:^1.0.1" | ||
3809 | stackblur-canvas: "npm:^2.0.0" | ||
3810 | svg-pathdata: "npm:^6.0.3" | ||
3811 | checksum: 10c0/b6bcd95d60c923c6a4e2be49e1fc1d395790577913a5a68439a2bb5a784ee75533ed7720bef69f2d9d0404203b4d61e89fdf1346f829e5da71e54cc57614153f | ||
3741 | languageName: node | 3812 | languageName: node |
3742 | linkType: hard | 3813 | linkType: hard |
3743 | 3814 | ||
@@ -3942,6 +4013,13 @@ __metadata: | |||
3942 | languageName: node | 4013 | languageName: node |
3943 | linkType: hard | 4014 | linkType: hard |
3944 | 4015 | ||
4016 | "core-js@npm:^3.6.0, core-js@npm:^3.8.3": | ||
4017 | version: 3.36.0 | ||
4018 | resolution: "core-js@npm:3.36.0" | ||
4019 | checksum: 10c0/62dcb41ba79ead581e4c5b2740ae18bfe6ee230e853893736d16edb01b580574d8645ff6c5513d1c75d59620f8451aee45c119d3c4f5ebc66cff5f003a816864 | ||
4020 | languageName: node | ||
4021 | linkType: hard | ||
4022 | |||
3945 | "cosmiconfig@npm:^7.0.0": | 4023 | "cosmiconfig@npm:^7.0.0": |
3946 | version: 7.1.0 | 4024 | version: 7.1.0 |
3947 | resolution: "cosmiconfig@npm:7.1.0" | 4025 | resolution: "cosmiconfig@npm:7.1.0" |
@@ -3992,6 +4070,24 @@ __metadata: | |||
3992 | languageName: node | 4070 | languageName: node |
3993 | linkType: hard | 4071 | linkType: hard |
3994 | 4072 | ||
4073 | "css-line-break@npm:^2.1.0": | ||
4074 | version: 2.1.0 | ||
4075 | resolution: "css-line-break@npm:2.1.0" | ||
4076 | dependencies: | ||
4077 | utrie: "npm:^1.0.2" | ||
4078 | checksum: 10c0/b2222d99d5daf7861ecddc050244fdce296fad74b000dcff6bdfb1eb16dc2ef0b9ffe2c1c965e3239bd05ebe9eadb6d5438a91592fa8648d27a338e827cf9048 | ||
4079 | languageName: node | ||
4080 | linkType: hard | ||
4081 | |||
4082 | "cssesc@npm:^3.0.0": | ||
4083 | version: 3.0.0 | ||
4084 | resolution: "cssesc@npm:3.0.0" | ||
4085 | bin: | ||
4086 | cssesc: bin/cssesc | ||
4087 | checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 | ||
4088 | languageName: node | ||
4089 | linkType: hard | ||
4090 | |||
3995 | "csstype@npm:^3.0.2, csstype@npm:^3.1.3": | 4091 | "csstype@npm:^3.0.2, csstype@npm:^3.1.3": |
3996 | version: 3.1.3 | 4092 | version: 3.1.3 |
3997 | resolution: "csstype@npm:3.1.3" | 4093 | resolution: "csstype@npm:3.1.3" |
@@ -4373,7 +4469,7 @@ __metadata: | |||
4373 | languageName: node | 4469 | languageName: node |
4374 | linkType: hard | 4470 | linkType: hard |
4375 | 4471 | ||
4376 | "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": | 4472 | "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": |
4377 | version: 4.3.4 | 4473 | version: 4.3.4 |
4378 | resolution: "debug@npm:4.3.4" | 4474 | resolution: "debug@npm:4.3.4" |
4379 | dependencies: | 4475 | dependencies: |
@@ -4490,6 +4586,13 @@ __metadata: | |||
4490 | languageName: node | 4586 | languageName: node |
4491 | linkType: hard | 4587 | linkType: hard |
4492 | 4588 | ||
4589 | "dompurify@npm:^2.2.0": | ||
4590 | version: 2.4.7 | ||
4591 | resolution: "dompurify@npm:2.4.7" | ||
4592 | checksum: 10c0/c04fa6a7c7276d0bc80e6330f697cfecd96ec1d3964b17de916f26cb0b5b2c9a98ba343d84e759f2b8339e577e619ef3749f3d128ef18ddb8230b09bd2ff3f29 | ||
4593 | languageName: node | ||
4594 | linkType: hard | ||
4595 | |||
4493 | "dot-case@npm:^3.0.4": | 4596 | "dot-case@npm:^3.0.4": |
4494 | version: 3.0.4 | 4597 | version: 3.0.4 |
4495 | resolution: "dot-case@npm:3.0.4" | 4598 | resolution: "dot-case@npm:3.0.4" |
@@ -5039,15 +5142,15 @@ __metadata: | |||
5039 | languageName: node | 5142 | languageName: node |
5040 | linkType: hard | 5143 | linkType: hard |
5041 | 5144 | ||
5042 | "eslint@npm:^8.56.0": | 5145 | "eslint@npm:^8.57.0": |
5043 | version: 8.56.0 | 5146 | version: 8.57.0 |
5044 | resolution: "eslint@npm:8.56.0" | 5147 | resolution: "eslint@npm:8.57.0" |
5045 | dependencies: | 5148 | dependencies: |
5046 | "@eslint-community/eslint-utils": "npm:^4.2.0" | 5149 | "@eslint-community/eslint-utils": "npm:^4.2.0" |
5047 | "@eslint-community/regexpp": "npm:^4.6.1" | 5150 | "@eslint-community/regexpp": "npm:^4.6.1" |
5048 | "@eslint/eslintrc": "npm:^2.1.4" | 5151 | "@eslint/eslintrc": "npm:^2.1.4" |
5049 | "@eslint/js": "npm:8.56.0" | 5152 | "@eslint/js": "npm:8.57.0" |
5050 | "@humanwhocodes/config-array": "npm:^0.11.13" | 5153 | "@humanwhocodes/config-array": "npm:^0.11.14" |
5051 | "@humanwhocodes/module-importer": "npm:^1.0.1" | 5154 | "@humanwhocodes/module-importer": "npm:^1.0.1" |
5052 | "@nodelib/fs.walk": "npm:^1.2.8" | 5155 | "@nodelib/fs.walk": "npm:^1.2.8" |
5053 | "@ungap/structured-clone": "npm:^1.2.0" | 5156 | "@ungap/structured-clone": "npm:^1.2.0" |
@@ -5083,7 +5186,7 @@ __metadata: | |||
5083 | text-table: "npm:^0.2.0" | 5186 | text-table: "npm:^0.2.0" |
5084 | bin: | 5187 | bin: |
5085 | eslint: bin/eslint.js | 5188 | eslint: bin/eslint.js |
5086 | checksum: 10c0/2be598f7da1339d045ad933ffd3d4742bee610515cd2b0d9a2b8b729395a01d4e913552fff555b559fccaefd89d7b37632825789d1b06470608737ae69ab43fb | 5189 | checksum: 10c0/00bb96fd2471039a312435a6776fe1fd557c056755eaa2b96093ef3a8508c92c8775d5f754768be6b1dddd09fdd3379ddb231eeb9b6c579ee17ea7d68000a529 |
5087 | languageName: node | 5190 | languageName: node |
5088 | linkType: hard | 5191 | linkType: hard |
5089 | 5192 | ||
@@ -5204,6 +5307,13 @@ __metadata: | |||
5204 | languageName: node | 5307 | languageName: node |
5205 | linkType: hard | 5308 | linkType: hard |
5206 | 5309 | ||
5310 | "fflate@npm:^0.4.8": | ||
5311 | version: 0.4.8 | ||
5312 | resolution: "fflate@npm:0.4.8" | ||
5313 | checksum: 10c0/29d1eddaaa5deab61b1c6b0d21282adacadbc4d2c01e94d8b1ee784398151673b9c563e53f97a801bc410a1ae55e8de5378114a743430e643e7a0644ba8e5a42 | ||
5314 | languageName: node | ||
5315 | linkType: hard | ||
5316 | |||
5207 | "file-entry-cache@npm:^6.0.1": | 5317 | "file-entry-cache@npm:^6.0.1": |
5208 | version: 6.0.1 | 5318 | version: 6.0.1 |
5209 | resolution: "file-entry-cache@npm:6.0.1" | 5319 | resolution: "file-entry-cache@npm:6.0.1" |
@@ -5265,6 +5375,13 @@ __metadata: | |||
5265 | languageName: node | 5375 | languageName: node |
5266 | linkType: hard | 5376 | linkType: hard |
5267 | 5377 | ||
5378 | "font-family-papandreou@npm:^0.2.0-patch1": | ||
5379 | version: 0.2.0-patch2 | ||
5380 | resolution: "font-family-papandreou@npm:0.2.0-patch2" | ||
5381 | checksum: 10c0/f2028070906b71a648b3aba63e22b4077fa3adf5949749f27fc7b44a41ffd1f017221e0d9bd550b8a1d8fcef7cfc3fc22cf6e3d99c5cc8a00ebfee29a3f26841 | ||
5382 | languageName: node | ||
5383 | linkType: hard | ||
5384 | |||
5268 | "for-each@npm:^0.3.3": | 5385 | "for-each@npm:^0.3.3": |
5269 | version: 0.3.3 | 5386 | version: 0.3.3 |
5270 | resolution: "for-each@npm:0.3.3" | 5387 | resolution: "for-each@npm:0.3.3" |
@@ -5656,6 +5773,16 @@ __metadata: | |||
5656 | languageName: node | 5773 | languageName: node |
5657 | linkType: hard | 5774 | linkType: hard |
5658 | 5775 | ||
5776 | "html2canvas@npm:^1.0.0-rc.5": | ||
5777 | version: 1.4.1 | ||
5778 | resolution: "html2canvas@npm:1.4.1" | ||
5779 | dependencies: | ||
5780 | css-line-break: "npm:^2.1.0" | ||
5781 | text-segmentation: "npm:^1.0.3" | ||
5782 | checksum: 10c0/6de86f75762b00948edf2ea559f16da0a1ec3facc4a8a7d3f35fcec59bb0c5970463478988ae3d9082152e0173690d46ebf4082e7ac803dd4817bae1d355c0db | ||
5783 | languageName: node | ||
5784 | linkType: hard | ||
5785 | |||
5659 | "http-cache-semantics@npm:^4.1.1": | 5786 | "http-cache-semantics@npm:^4.1.1": |
5660 | version: 4.1.1 | 5787 | version: 4.1.1 |
5661 | resolution: "http-cache-semantics@npm:4.1.1" | 5788 | resolution: "http-cache-semantics@npm:4.1.1" |
@@ -5776,9 +5903,9 @@ __metadata: | |||
5776 | linkType: hard | 5903 | linkType: hard |
5777 | 5904 | ||
5778 | "ip@npm:^2.0.0": | 5905 | "ip@npm:^2.0.0": |
5779 | version: 2.0.0 | 5906 | version: 2.0.1 |
5780 | resolution: "ip@npm:2.0.0" | 5907 | resolution: "ip@npm:2.0.1" |
5781 | checksum: 10c0/8d186cc5585f57372847ae29b6eba258c68862055e18a75cc4933327232cb5c107f89800ce29715d542eef2c254fbb68b382e780a7414f9ee7caf60b7a473958 | 5908 | checksum: 10c0/cab8eb3e88d0abe23e4724829621ec4c4c5cb41a7f936a2e626c947128c1be16ed543448d42af7cca95379f9892bfcacc1ccd8d09bc7e8bea0e86d492ce33616 |
5782 | languageName: node | 5909 | languageName: node |
5783 | linkType: hard | 5910 | linkType: hard |
5784 | 5911 | ||
@@ -6260,6 +6387,31 @@ __metadata: | |||
6260 | languageName: node | 6387 | languageName: node |
6261 | linkType: hard | 6388 | linkType: hard |
6262 | 6389 | ||
6390 | "jspdf@npm:*, jspdf@npm:^2.5.1": | ||
6391 | version: 2.5.1 | ||
6392 | resolution: "jspdf@npm:2.5.1" | ||
6393 | dependencies: | ||
6394 | "@babel/runtime": "npm:^7.14.0" | ||
6395 | atob: "npm:^2.1.2" | ||
6396 | btoa: "npm:^1.2.1" | ||
6397 | canvg: "npm:^3.0.6" | ||
6398 | core-js: "npm:^3.6.0" | ||
6399 | dompurify: "npm:^2.2.0" | ||
6400 | fflate: "npm:^0.4.8" | ||
6401 | html2canvas: "npm:^1.0.0-rc.5" | ||
6402 | dependenciesMeta: | ||
6403 | canvg: | ||
6404 | optional: true | ||
6405 | core-js: | ||
6406 | optional: true | ||
6407 | dompurify: | ||
6408 | optional: true | ||
6409 | html2canvas: | ||
6410 | optional: true | ||
6411 | checksum: 10c0/dad15d4f53ead1d2e9d5f6fd9b6e72c7233ba5cbc30d98461eb0ef609aa908b28fd5eaaf2b763b55df945c7ecca2323097d9331f09fee1d6c23c06785520ab5f | ||
6412 | languageName: node | ||
6413 | linkType: hard | ||
6414 | |||
6263 | "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": | 6415 | "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": |
6264 | version: 3.3.5 | 6416 | version: 3.3.5 |
6265 | resolution: "jsx-ast-utils@npm:3.3.5" | 6417 | resolution: "jsx-ast-utils@npm:3.3.5" |
@@ -6652,12 +6804,12 @@ __metadata: | |||
6652 | languageName: node | 6804 | languageName: node |
6653 | linkType: hard | 6805 | linkType: hard |
6654 | 6806 | ||
6655 | "nanoid@npm:^5.0.5": | 6807 | "nanoid@npm:^5.0.6": |
6656 | version: 5.0.5 | 6808 | version: 5.0.6 |
6657 | resolution: "nanoid@npm:5.0.5" | 6809 | resolution: "nanoid@npm:5.0.6" |
6658 | bin: | 6810 | bin: |
6659 | nanoid: bin/nanoid.js | 6811 | nanoid: bin/nanoid.js |
6660 | checksum: 10c0/b1f881b08d6e918bf70d37dece3e59fff8d99cc29b80a956e29d1852e09d3a7bf35a4884f4b9d5ff26963018937d0c91bef83842c15758a668d88c94704d2da7 | 6812 | checksum: 10c0/6660f99b7bb3816f04fd9a14126859482e07d1705c02e1a6c1a722545c65186659f6f734eb21329f54e838b6409579bef687e2fb13661b716529dcefc5d86ec6 |
6661 | languageName: node | 6813 | languageName: node |
6662 | linkType: hard | 6814 | linkType: hard |
6663 | 6815 | ||
@@ -7004,6 +7156,13 @@ __metadata: | |||
7004 | languageName: node | 7156 | languageName: node |
7005 | linkType: hard | 7157 | linkType: hard |
7006 | 7158 | ||
7159 | "performance-now@npm:^2.1.0": | ||
7160 | version: 2.1.0 | ||
7161 | resolution: "performance-now@npm:2.1.0" | ||
7162 | checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9 | ||
7163 | languageName: node | ||
7164 | linkType: hard | ||
7165 | |||
7007 | "picocolors@npm:^1.0.0": | 7166 | "picocolors@npm:^1.0.0": |
7008 | version: 1.0.0 | 7167 | version: 1.0.0 |
7009 | resolution: "picocolors@npm:1.0.0" | 7168 | resolution: "picocolors@npm:1.0.0" |
@@ -7119,6 +7278,15 @@ __metadata: | |||
7119 | languageName: node | 7278 | languageName: node |
7120 | linkType: hard | 7279 | linkType: hard |
7121 | 7280 | ||
7281 | "raf@npm:^3.4.1": | ||
7282 | version: 3.4.1 | ||
7283 | resolution: "raf@npm:3.4.1" | ||
7284 | dependencies: | ||
7285 | performance-now: "npm:^2.1.0" | ||
7286 | checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e | ||
7287 | languageName: node | ||
7288 | linkType: hard | ||
7289 | |||
7122 | "randombytes@npm:^2.1.0": | 7290 | "randombytes@npm:^2.1.0": |
7123 | version: 2.1.0 | 7291 | version: 2.1.0 |
7124 | resolution: "randombytes@npm:2.1.0" | 7292 | resolution: "randombytes@npm:2.1.0" |
@@ -7253,6 +7421,13 @@ __metadata: | |||
7253 | languageName: node | 7421 | languageName: node |
7254 | linkType: hard | 7422 | linkType: hard |
7255 | 7423 | ||
7424 | "regenerator-runtime@npm:^0.13.7": | ||
7425 | version: 0.13.11 | ||
7426 | resolution: "regenerator-runtime@npm:0.13.11" | ||
7427 | checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24 | ||
7428 | languageName: node | ||
7429 | linkType: hard | ||
7430 | |||
7256 | "regenerator-runtime@npm:^0.14.0": | 7431 | "regenerator-runtime@npm:^0.14.0": |
7257 | version: 0.14.0 | 7432 | version: 0.14.0 |
7258 | resolution: "regenerator-runtime@npm:0.14.0" | 7433 | resolution: "regenerator-runtime@npm:0.14.0" |
@@ -7413,6 +7588,13 @@ __metadata: | |||
7413 | languageName: node | 7588 | languageName: node |
7414 | linkType: hard | 7589 | linkType: hard |
7415 | 7590 | ||
7591 | "rgbcolor@npm:^1.0.1": | ||
7592 | version: 1.0.1 | ||
7593 | resolution: "rgbcolor@npm:1.0.1" | ||
7594 | checksum: 10c0/13af06c523351bac2854b85a22d1dfafd9310efd898e9bd96c8706f9aa09a3ddc8392ab00ae03d12950782164a97677f21834ffd84ffebf76ae106add319f956 | ||
7595 | languageName: node | ||
7596 | linkType: hard | ||
7597 | |||
7416 | "rimraf@npm:^3.0.2": | 7598 | "rimraf@npm:^3.0.2": |
7417 | version: 3.0.2 | 7599 | version: 3.0.2 |
7418 | resolution: "rimraf@npm:3.0.2" | 7600 | resolution: "rimraf@npm:3.0.2" |
@@ -7753,6 +7935,15 @@ __metadata: | |||
7753 | languageName: node | 7935 | languageName: node |
7754 | linkType: hard | 7936 | linkType: hard |
7755 | 7937 | ||
7938 | "specificity@npm:^0.4.1": | ||
7939 | version: 0.4.1 | ||
7940 | resolution: "specificity@npm:0.4.1" | ||
7941 | bin: | ||
7942 | specificity: ./bin/specificity | ||
7943 | checksum: 10c0/5da85a05052b55e344cb0f5bce5d07cbabbbe8945da176a481589db5a13e9fbcfa879ceb075cf564b94e680fae0a2ab14ea55cc87496b86a6d5122545946d7c2 | ||
7944 | languageName: node | ||
7945 | linkType: hard | ||
7946 | |||
7756 | "ssri@npm:^10.0.0": | 7947 | "ssri@npm:^10.0.0": |
7757 | version: 10.0.5 | 7948 | version: 10.0.5 |
7758 | resolution: "ssri@npm:10.0.5" | 7949 | resolution: "ssri@npm:10.0.5" |
@@ -7762,6 +7953,13 @@ __metadata: | |||
7762 | languageName: node | 7953 | languageName: node |
7763 | linkType: hard | 7954 | linkType: hard |
7764 | 7955 | ||
7956 | "stackblur-canvas@npm:^2.0.0": | ||
7957 | version: 2.7.0 | ||
7958 | resolution: "stackblur-canvas@npm:2.7.0" | ||
7959 | checksum: 10c0/df290d0629056d5bb43d37548d0b24cb8593c79d742650e68489abf61013db578c9980724c2508bb738d107204f2e2494ab94c3cf69d6b725caa9c63b8c7e272 | ||
7960 | languageName: node | ||
7961 | linkType: hard | ||
7962 | |||
7765 | "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": | 7963 | "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": |
7766 | version: 4.2.3 | 7964 | version: 4.2.3 |
7767 | resolution: "string-width@npm:4.2.3" | 7965 | resolution: "string-width@npm:4.2.3" |
@@ -7931,6 +8129,34 @@ __metadata: | |||
7931 | languageName: node | 8129 | languageName: node |
7932 | linkType: hard | 8130 | linkType: hard |
7933 | 8131 | ||
8132 | "svg-pathdata@npm:^6.0.3": | ||
8133 | version: 6.0.3 | ||
8134 | resolution: "svg-pathdata@npm:6.0.3" | ||
8135 | checksum: 10c0/1ba4ad2fa81e86df37d6e78d3be9e664bbedf97773b725a863a85db384285be32dc37d9c0d61e477d89594ee95b967d2c53d6bee2d76420aab670ab4124a38b9 | ||
8136 | languageName: node | ||
8137 | linkType: hard | ||
8138 | |||
8139 | "svg2pdf.js@npm:^2.2.3": | ||
8140 | version: 2.2.3 | ||
8141 | resolution: "svg2pdf.js@npm:2.2.3" | ||
8142 | dependencies: | ||
8143 | cssesc: "npm:^3.0.0" | ||
8144 | font-family-papandreou: "npm:^0.2.0-patch1" | ||
8145 | specificity: "npm:^0.4.1" | ||
8146 | svgpath: "npm:^2.3.0" | ||
8147 | peerDependencies: | ||
8148 | jspdf: ^2.0.0 | ||
8149 | checksum: 10c0/9845b837152b255c94ac6ba12e6376e771b18cb7bbfab6b11b56a19f437bca2e077386e58b5aa3f57ada021797ab784c6379d5be8d1df911eb705402f45d0cb2 | ||
8150 | languageName: node | ||
8151 | linkType: hard | ||
8152 | |||
8153 | "svgpath@npm:^2.3.0": | ||
8154 | version: 2.6.0 | ||
8155 | resolution: "svgpath@npm:2.6.0" | ||
8156 | checksum: 10c0/aed042d7cc0e2f3b55d618af2bee58faf8820d53f5d45490194e9bd1cd9ae7d57be641fd3692429a133be2ffb75ff43aad2e6b1c297359ffbc808016a106f199 | ||
8157 | languageName: node | ||
8158 | linkType: hard | ||
8159 | |||
7934 | "synckit@npm:^0.8.6": | 8160 | "synckit@npm:^0.8.6": |
7935 | version: 0.8.8 | 8161 | version: 0.8.8 |
7936 | resolution: "synckit@npm:0.8.8" | 8162 | resolution: "synckit@npm:0.8.8" |
@@ -7995,6 +8221,15 @@ __metadata: | |||
7995 | languageName: node | 8221 | languageName: node |
7996 | linkType: hard | 8222 | linkType: hard |
7997 | 8223 | ||
8224 | "text-segmentation@npm:^1.0.3": | ||
8225 | version: 1.0.3 | ||
8226 | resolution: "text-segmentation@npm:1.0.3" | ||
8227 | dependencies: | ||
8228 | utrie: "npm:^1.0.2" | ||
8229 | checksum: 10c0/8b9ae8524e3a332371060d0ca62f10ad49a13e954719ea689a6c3a8b8c15c8a56365ede2bb91c322fb0d44b6533785f0da603e066b7554d052999967fb72d600 | ||
8230 | languageName: node | ||
8231 | linkType: hard | ||
8232 | |||
7998 | "text-table@npm:^0.2.0": | 8233 | "text-table@npm:^0.2.0": |
7999 | version: 0.2.0 | 8234 | version: 0.2.0 |
8000 | resolution: "text-table@npm:0.2.0" | 8235 | resolution: "text-table@npm:0.2.0" |
@@ -8295,6 +8530,15 @@ __metadata: | |||
8295 | languageName: node | 8530 | languageName: node |
8296 | linkType: hard | 8531 | linkType: hard |
8297 | 8532 | ||
8533 | "utrie@npm:^1.0.2": | ||
8534 | version: 1.0.2 | ||
8535 | resolution: "utrie@npm:1.0.2" | ||
8536 | dependencies: | ||
8537 | base64-arraybuffer: "npm:^1.0.2" | ||
8538 | checksum: 10c0/eaffe645bd81a39e4bc3abb23df5895e9961dbdd49748ef3b173529e8b06ce9dd1163e9705d5309a1c61ee41ffcb825e2043bc0fd1659845ffbdf4b1515dfdb4 | ||
8539 | languageName: node | ||
8540 | linkType: hard | ||
8541 | |||
8298 | "vite-plugin-pwa@npm:^0.19.0": | 8542 | "vite-plugin-pwa@npm:^0.19.0": |
8299 | version: 0.19.0 | 8543 | version: 0.19.0 |
8300 | resolution: "vite-plugin-pwa@npm:0.19.0" | 8544 | resolution: "vite-plugin-pwa@npm:0.19.0" |
@@ -8316,9 +8560,9 @@ __metadata: | |||
8316 | languageName: node | 8560 | languageName: node |
8317 | linkType: hard | 8561 | linkType: hard |
8318 | 8562 | ||
8319 | "vite@npm:^5.1.3": | 8563 | "vite@npm:^5.1.4": |
8320 | version: 5.1.3 | 8564 | version: 5.1.4 |
8321 | resolution: "vite@npm:5.1.3" | 8565 | resolution: "vite@npm:5.1.4" |
8322 | dependencies: | 8566 | dependencies: |
8323 | esbuild: "npm:^0.19.3" | 8567 | esbuild: "npm:^0.19.3" |
8324 | fsevents: "npm:~2.3.3" | 8568 | fsevents: "npm:~2.3.3" |
@@ -8352,7 +8596,7 @@ __metadata: | |||
8352 | optional: true | 8596 | optional: true |
8353 | bin: | 8597 | bin: |
8354 | vite: bin/vite.js | 8598 | vite: bin/vite.js |
8355 | checksum: 10c0/d3b19607d736de60b660f7daf4c0f86589edcbbc1fcb09f8aa36630f99518cc8a063062bb952899b8ccaed62f1314fac22c1df492dd035de3c65998ab27e2d2a | 8599 | checksum: 10c0/8f04c8bed33f266bde27f432412456a3b893b51fe1857f0b8cd259100b376c1393a7927db1dd6344a4376baed72ed179ec5b0428aef2ae8508f1f28f95acb908 |
8356 | languageName: node | 8600 | languageName: node |
8357 | linkType: hard | 8601 | linkType: hard |
8358 | 8602 | ||
diff --git a/yarn.lock.license b/yarn.lock.license index 7a5a2a4b..062a2796 100644 --- a/yarn.lock.license +++ b/yarn.lock.license | |||
@@ -1,3 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 1 | SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
2 | 2 | ||
3 | SPDX-License-Identifier: CC0-1.0 | 3 | SPDX-License-Identifier: CC0-1.0 |