aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-22 21:09:46 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-22 21:09:46 +0100
commit76144b74991110e8f655ce6289b2a2a85d1d9019 (patch)
tree4aa3727d17ec007559a96488f233eb0c26c59753
parentMerge pull request #53 from kris7t/imports (diff)
downloadrefinery-76144b74991110e8f655ce6289b2a2a85d1d9019.tar.gz
refinery-76144b74991110e8f655ce6289b2a2a85d1d9019.tar.zst
refinery-76144b74991110e8f655ce6289b2a2a85d1d9019.zip
feat(web): SVG export
-rw-r--r--subprojects/frontend/package.json5
-rw-r--r--subprojects/frontend/src/graph/DotGraphVisualizer.tsx5
-rw-r--r--subprojects/frontend/src/graph/ExportButton.tsx168
-rw-r--r--subprojects/frontend/src/graph/GraphArea.tsx14
-rw-r--r--subprojects/frontend/src/graph/GraphTheme.tsx94
-rw-r--r--subprojects/frontend/src/graph/postProcessSVG.ts6
-rw-r--r--yarn.lock39
-rw-r--r--yarn.lock.license2
8 files changed, 278 insertions, 55 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 7d5e19d1..685f7cc5 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 ],
@@ -35,8 +35,11 @@
35 "@codemirror/search": "^6.5.6", 35 "@codemirror/search": "^6.5.6",
36 "@codemirror/state": "^6.4.0", 36 "@codemirror/state": "^6.4.0",
37 "@codemirror/view": "^6.24.0", 37 "@codemirror/view": "^6.24.0",
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",
42 "@hpcc-js/wasm": "^2.16.0", 45 "@hpcc-js/wasm": "^2.16.0",
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
162export default observer(DotGraphVisualizer); 167export default observer(DotGraphVisualizer);
diff --git a/subprojects/frontend/src/graph/ExportButton.tsx b/subprojects/frontend/src/graph/ExportButton.tsx
new file mode 100644
index 00000000..91445d00
--- /dev/null
+++ b/subprojects/frontend/src/graph/ExportButton.tsx
@@ -0,0 +1,168 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import createCache from '@emotion/cache';
8import { serializeStyles } from '@emotion/serialize';
9import type { StyleSheet } from '@emotion/utils';
10import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
11import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw';
12import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw';
13import SaveAltIcon from '@mui/icons-material/SaveAlt';
14import IconButton from '@mui/material/IconButton';
15import { styled, useTheme, type Theme } from '@mui/material/styles';
16import { useCallback } from 'react';
17
18import { createGraphTheme } from './GraphTheme';
19import { SVG_NS } from './postProcessSVG';
20
21const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';
22
23const ExportButtonRoot = styled('div', {
24 name: 'ExportButton-Root',
25})(({ theme }) => ({
26 position: 'absolute',
27 padding: theme.spacing(1),
28 top: 0,
29 right: 0,
30 overflow: 'hidden',
31 display: 'flex',
32 flexDirection: 'column',
33 alignItems: 'start',
34}));
35
36const ICONS: Map<string, Element> = new Map();
37
38function importSVG(svgSource: string, className: string): void {
39 const parser = new DOMParser();
40 const svgDocument = parser.parseFromString(svgSource, 'image/svg+xml');
41 const root = svgDocument.children[0];
42 if (root === undefined) {
43 return;
44 }
45 root.id = className;
46 root.classList.add(className);
47 ICONS.set(className, root);
48}
49
50importSVG(labelSVG, 'icon-TRUE');
51importSVG(labelOutlinedSVG, 'icon-UNKNOWN');
52importSVG(cancelSVG, 'icon-ERROR');
53
54function appendStyles(
55 svgDocument: XMLDocument,
56 svg: SVGSVGElement,
57 theme: Theme,
58): void {
59 const cache = createCache({
60 key: 'refinery',
61 container: svg,
62 prepend: true,
63 });
64 // @ts-expect-error `CSSObject` types don't match up between `@mui/material` and
65 // `@emotion/serialize`, but they are compatible in practice.
66 const styles = serializeStyles([createGraphTheme], cache.registered, {
67 theme,
68 colorNodes: true,
69 noEmbedIcons: true,
70 });
71 const rules: string[] = [];
72 const sheet = {
73 insert(rule) {
74 rules.push(rule);
75 },
76 } as StyleSheet;
77 cache.insert('', styles, sheet, false);
78 const styleElement = svgDocument.createElementNS(SVG_NS, 'style');
79 svg.prepend(styleElement);
80 styleElement.innerHTML = rules.join('');
81}
82
83function fixForeignObjects(svgDocument: XMLDocument, svg: SVGSVGElement): void {
84 const foreignObjects: SVGForeignObjectElement[] = [];
85 svg
86 .querySelectorAll('foreignObject')
87 .forEach((object) => foreignObjects.push(object));
88 foreignObjects.forEach((object) => {
89 const useElement = svgDocument.createElementNS(SVG_NS, 'use');
90 let x = Number(object.getAttribute('x') ?? '0');
91 let y = Number(object.getAttribute('y') ?? '0');
92 const width = Number(object.getAttribute('width') ?? '0');
93 const height = Number(object.getAttribute('height') ?? '0');
94 const size = Math.min(width, height);
95 x += (width - size) / 2;
96 y += (height - size) / 2;
97 useElement.setAttribute('x', String(x));
98 useElement.setAttribute('y', String(y));
99 useElement.setAttribute('width', String(size));
100 useElement.setAttribute('height', String(size));
101 useElement.id = object.id;
102 object.children[0]?.classList?.forEach((className) => {
103 useElement.classList.add(className);
104 if (ICONS.has(className)) {
105 useElement.setAttribute('href', `#${className}`);
106 }
107 });
108 object.replaceWith(useElement);
109 });
110 const defs = svgDocument.createElementNS(SVG_NS, 'defs');
111 svg.prepend(defs);
112 ICONS.forEach((value) => {
113 const importedValue = svgDocument.importNode(value, true);
114 defs.appendChild(importedValue);
115 });
116}
117
118function downloadSVG(svgDocument: XMLDocument) {
119 const serializer = new XMLSerializer();
120 const svgText = `${PROLOG}\n${serializer.serializeToString(svgDocument)}`;
121 const blob = new Blob([svgText], {
122 type: 'image/svg+xml',
123 });
124 const link = document.createElement('a');
125 link.href = window.URL.createObjectURL(blob);
126 link.download = 'graph.svg';
127 link.style.display = 'none';
128 document.body.appendChild(link);
129 link.click();
130 document.body.removeChild(link);
131}
132
133export default function ExportButton({
134 svgContainer,
135}: {
136 svgContainer: HTMLElement | undefined;
137}): JSX.Element {
138 const theme = useTheme();
139 const saveCallback = useCallback(() => {
140 const svg = svgContainer?.querySelector('svg');
141 if (!svg) {
142 return;
143 }
144 const svgDocument = document.implementation.createDocument(
145 SVG_NS,
146 'svg',
147 null,
148 );
149 const copyOfSVG = svgDocument.importNode(svg, true);
150 const originalRoot = svgDocument.childNodes[0];
151 if (originalRoot === undefined) {
152 svgDocument.appendChild(copyOfSVG);
153 } else {
154 svgDocument.replaceChild(copyOfSVG, originalRoot);
155 }
156 fixForeignObjects(svgDocument, copyOfSVG);
157 appendStyles(svgDocument, copyOfSVG, theme);
158 downloadSVG(svgDocument);
159 }, [theme, svgContainer]);
160
161 return (
162 <ExportButtonRoot>
163 <IconButton aria-label="Save SVG" onClick={saveCallback}>
164 <SaveAltIcon />
165 </IconButton>
166 </ExportButtonRoot>
167 );
168}
diff --git a/subprojects/frontend/src/graph/GraphArea.tsx b/subprojects/frontend/src/graph/GraphArea.tsx
index d5801b9a..2bf40d1a 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,9 +7,11 @@
7import Box from '@mui/material/Box'; 7import Box from '@mui/material/Box';
8import { useTheme } from '@mui/material/styles'; 8import { useTheme } from '@mui/material/styles';
9import { observer } from 'mobx-react-lite'; 9import { observer } from 'mobx-react-lite';
10import { useState } from 'react';
10import { useResizeDetector } from 'react-resize-detector'; 11import { useResizeDetector } from 'react-resize-detector';
11 12
12import DotGraphVisualizer from './DotGraphVisualizer'; 13import DotGraphVisualizer from './DotGraphVisualizer';
14import ExportButton from './ExportButton';
13import type GraphStore from './GraphStore'; 15import type GraphStore from './GraphStore';
14import VisibilityPanel from './VisibilityPanel'; 16import VisibilityPanel from './VisibilityPanel';
15import ZoomCanvas from './ZoomCanvas'; 17import ZoomCanvas from './ZoomCanvas';
@@ -19,6 +21,7 @@ function GraphArea({ graph }: { graph: GraphStore }): JSX.Element {
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 <ExportButton svgContainer={svgContainer} />
42 </Box> 52 </Box>
43 ); 53 );
44} 54}
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx
index 7334f559..b3f55a35 100644
--- a/subprojects/frontend/src/graph/GraphTheme.tsx
+++ b/subprojects/frontend/src/graph/GraphTheme.tsx
@@ -52,11 +52,34 @@ function createTypeHashStyles(theme: Theme, colorNodes: boolean): CSSObject {
52 return result; 52 return result;
53} 53}
54 54
55export default styled('div', { 55function iconStyle(
56 name: 'GraphTheme', 56 svg: string,
57})<{ colorNodes: boolean }>(({ theme, colorNodes }) => ({ 57 color: string,
58 '& svg': { 58 noEmbedIcons?: boolean,
59 userSelect: 'none', 59): CSSObject {
60 if (noEmbedIcons) {
61 return {
62 fill: color,
63 };
64 }
65 return {
66 maskImage: svgURL(svg),
67 background: color,
68 };
69}
70
71export function createGraphTheme({
72 theme,
73 colorNodes,
74 noEmbedIcons,
75}: {
76 theme: Theme;
77 colorNodes: boolean;
78 noEmbedIcons?: boolean;
79}): CSSObject {
80 const shadowAlapha = theme.palette.mode === 'dark' ? 0.32 : 0.24;
81
82 return {
60 '.node': { 83 '.node': {
61 '& text': { 84 '& text': {
62 fontFamily: theme.typography.fontFamily, 85 fontFamily: theme.typography.fontFamily,
@@ -80,12 +103,15 @@ export default styled('div', {
80 strokeWidth: 2, 103 strokeWidth: 2,
81 }, 104 },
82 }, 105 },
83 '.node-shadow[fill="white"]': { 106 '.node-shadow[fill="white"]': noEmbedIcons
84 fill: alpha( 107 ? {
85 theme.palette.text.primary, 108 // Inkscape can't handle opacity in exported SVG.
86 theme.palette.mode === 'dark' ? 0.32 : 0.24, 109 fill: theme.palette.text.primary,
87 ), 110 opacity: shadowAlapha,
88 }, 111 }
112 : {
113 fill: alpha(theme.palette.text.primary, shadowAlapha),
114 },
89 '.node-exists-UNKNOWN [stroke="black"]': { 115 '.node-exists-UNKNOWN [stroke="black"]': {
90 strokeDasharray: '5 2', 116 strokeDasharray: '5 2',
91 }, 117 },
@@ -104,30 +130,38 @@ export default styled('div', {
104 }, 130 },
105 ...createEdgeColor('UNKNOWN', theme.palette.text.secondary, 'none'), 131 ...createEdgeColor('UNKNOWN', theme.palette.text.secondary, 'none'),
106 ...createEdgeColor('ERROR', theme.palette.error.main), 132 ...createEdgeColor('ERROR', theme.palette.error.main),
107 '.icon': { 133 ...(noEmbedIcons
108 maskSize: '12px 12px', 134 ? {}
109 maskPosition: '50% 50%', 135 : {
110 maskRepeat: 'no-repeat', 136 '.icon': {
111 width: '100%', 137 maskSize: '12px 12px',
112 height: '100%', 138 maskPosition: '50% 50%',
113 }, 139 maskRepeat: 'no-repeat',
114 '.icon-TRUE': { 140 width: '100%',
115 maskImage: svgURL(labelSVG), 141 height: '100%',
116 background: theme.palette.text.primary, 142 },
117 }, 143 }),
118 '.icon-UNKNOWN': { 144 '.icon-TRUE': iconStyle(labelSVG, theme.palette.text.primary, noEmbedIcons),
119 maskImage: svgURL(labelOutlinedSVG), 145 '.icon-UNKNOWN': iconStyle(
120 background: theme.palette.text.secondary, 146 labelOutlinedSVG,
121 }, 147 theme.palette.text.secondary,
122 '.icon-ERROR': { 148 noEmbedIcons,
123 maskImage: svgURL(cancelSVG), 149 ),
124 background: theme.palette.error.main, 150 '.icon-ERROR': iconStyle(cancelSVG, theme.palette.error.main, noEmbedIcons),
125 },
126 'text.label-UNKNOWN': { 151 'text.label-UNKNOWN': {
127 fill: theme.palette.text.secondary, 152 fill: theme.palette.text.secondary,
128 }, 153 },
129 'text.label-ERROR': { 154 'text.label-ERROR': {
130 fill: theme.palette.error.main, 155 fill: theme.palette.error.main,
131 }, 156 },
157 };
158}
159
160export default styled('div', {
161 name: 'GraphTheme',
162})<{ colorNodes: boolean }>((args) => ({
163 '& svg': {
164 userSelect: 'none',
165 ...createGraphTheme(args),
132 }, 166 },
133})); 167}));
diff --git a/subprojects/frontend/src/graph/postProcessSVG.ts b/subprojects/frontend/src/graph/postProcessSVG.ts
index a580f5c6..f434f80b 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
7import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox'; 7import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox';
8 8
9const SVG_NS = 'http://www.w3.org/2000/svg'; 9export const SVG_NS = 'http://www.w3.org/2000/svg';
10const XLINK_NS = 'http://www.w3.org/1999/xlink'; 10export const XLINK_NS = 'http://www.w3.org/1999/xlink';
11 11
12function modifyAttribute(element: Element, attribute: string, change: number) { 12function modifyAttribute(element: Element, attribute: string, change: number) {
13 const valueString = element.getAttribute(attribute); 13 const valueString = element.getAttribute(attribute);
diff --git a/yarn.lock b/yarn.lock
index c55f95d4..98311d89 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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
@@ -2002,8 +2002,8 @@ __metadata:
2002 linkType: hard 2002 linkType: hard
2003 2003
2004"@mui/styled-engine@npm:^5.15.9": 2004"@mui/styled-engine@npm:^5.15.9":
2005 version: 5.15.9 2005 version: 5.15.11
2006 resolution: "@mui/styled-engine@npm:5.15.9" 2006 resolution: "@mui/styled-engine@npm:5.15.11"
2007 dependencies: 2007 dependencies:
2008 "@babel/runtime": "npm:^7.23.9" 2008 "@babel/runtime": "npm:^7.23.9"
2009 "@emotion/cache": "npm:^11.11.0" 2009 "@emotion/cache": "npm:^11.11.0"
@@ -2018,7 +2018,7 @@ __metadata:
2018 optional: true 2018 optional: true
2019 "@emotion/styled": 2019 "@emotion/styled":
2020 optional: true 2020 optional: true
2021 checksum: 10c0/47968ec55a5a36936dc8c245c1d62fe9be82bdaba800aeea08d111702b3aea8388bc3c2a46ee4ee315efe77b1c7ecf63308fc518afa69114b2fc90a203de48e0 2021 checksum: 10c0/b168565256c82ed34946f954e9088c339917c980451b5dd3d6dca9d36e6d39b5900ea85ffc5736b66d25f841dc761a8c2c70c111c42bec794309f2cafb3975ce
2022 languageName: node 2022 languageName: node
2023 linkType: hard 2023 linkType: hard
2024 2024
@@ -2166,8 +2166,11 @@ __metadata:
2166 "@codemirror/search": "npm:^6.5.6" 2166 "@codemirror/search": "npm:^6.5.6"
2167 "@codemirror/state": "npm:^6.4.0" 2167 "@codemirror/state": "npm:^6.4.0"
2168 "@codemirror/view": "npm:^6.24.0" 2168 "@codemirror/view": "npm:^6.24.0"
2169 "@emotion/cache": "npm:^11.11.0"
2169 "@emotion/react": "npm:^11.11.3" 2170 "@emotion/react": "npm:^11.11.3"
2171 "@emotion/serialize": "npm:^1.1.3"
2170 "@emotion/styled": "npm:^11.11.0" 2172 "@emotion/styled": "npm:^11.11.0"
2173 "@emotion/utils": "npm:^1.2.1"
2171 "@fontsource-variable/jetbrains-mono": "npm:^5.0.19" 2174 "@fontsource-variable/jetbrains-mono": "npm:^5.0.19"
2172 "@fontsource-variable/open-sans": "npm:^5.0.25" 2175 "@fontsource-variable/open-sans": "npm:^5.0.25"
2173 "@hpcc-js/wasm": "npm:^2.16.0" 2176 "@hpcc-js/wasm": "npm:^2.16.0"
@@ -2966,9 +2969,9 @@ __metadata:
2966 linkType: hard 2969 linkType: hard
2967 2970
2968"@types/parse-json@npm:^4.0.0": 2971"@types/parse-json@npm:^4.0.0":
2969 version: 4.0.0 2972 version: 4.0.2
2970 resolution: "@types/parse-json@npm:4.0.0" 2973 resolution: "@types/parse-json@npm:4.0.2"
2971 checksum: 10c0/1d3012ab2fcdad1ba313e1d065b737578f6506c8958e2a7a5bdbdef517c7e930796cb1599ee067d5dee942fb3a764df64b5eef7e9ae98548d776e86dcffba985 2974 checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1
2972 languageName: node 2975 languageName: node
2973 linkType: hard 2976 linkType: hard
2974 2977
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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 1SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
2 2
3SPDX-License-Identifier: CC0-1.0 3SPDX-License-Identifier: CC0-1.0