diff options
author | Kristóf Marussy <kristof@marussy.com> | 2024-02-22 21:09:46 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2024-02-22 21:09:46 +0100 |
commit | 76144b74991110e8f655ce6289b2a2a85d1d9019 (patch) | |
tree | 4aa3727d17ec007559a96488f233eb0c26c59753 | |
parent | Merge pull request #53 from kris7t/imports (diff) | |
download | refinery-76144b74991110e8f655ce6289b2a2a85d1d9019.tar.gz refinery-76144b74991110e8f655ce6289b2a2a85d1d9019.tar.zst refinery-76144b74991110e8f655ce6289b2a2a85d1d9019.zip |
feat(web): SVG export
-rw-r--r-- | subprojects/frontend/package.json | 5 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/DotGraphVisualizer.tsx | 5 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/ExportButton.tsx | 168 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/GraphArea.tsx | 14 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/GraphTheme.tsx | 94 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/postProcessSVG.ts | 6 | ||||
-rw-r--r-- | yarn.lock | 39 | ||||
-rw-r--r-- | yarn.lock.license | 2 |
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 | ||
162 | export default observer(DotGraphVisualizer); | 167 | export 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 | |||
7 | import createCache from '@emotion/cache'; | ||
8 | import { serializeStyles } from '@emotion/serialize'; | ||
9 | import type { StyleSheet } from '@emotion/utils'; | ||
10 | import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; | ||
11 | import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw'; | ||
12 | import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; | ||
13 | import SaveAltIcon from '@mui/icons-material/SaveAlt'; | ||
14 | import IconButton from '@mui/material/IconButton'; | ||
15 | import { styled, useTheme, type Theme } from '@mui/material/styles'; | ||
16 | import { useCallback } from 'react'; | ||
17 | |||
18 | import { createGraphTheme } from './GraphTheme'; | ||
19 | import { SVG_NS } from './postProcessSVG'; | ||
20 | |||
21 | const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'; | ||
22 | |||
23 | const 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 | |||
36 | const ICONS: Map<string, Element> = new Map(); | ||
37 | |||
38 | function 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 | |||
50 | importSVG(labelSVG, 'icon-TRUE'); | ||
51 | importSVG(labelOutlinedSVG, 'icon-UNKNOWN'); | ||
52 | importSVG(cancelSVG, 'icon-ERROR'); | ||
53 | |||
54 | function 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 | |||
83 | function 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 | |||
118 | function 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 | |||
133 | export 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 @@ | |||
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'; |
14 | import ExportButton from './ExportButton'; | ||
13 | import type GraphStore from './GraphStore'; | 15 | import type GraphStore from './GraphStore'; |
14 | import VisibilityPanel from './VisibilityPanel'; | 16 | import VisibilityPanel from './VisibilityPanel'; |
15 | import ZoomCanvas from './ZoomCanvas'; | 17 | import 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 | ||
55 | export default styled('div', { | 55 | function 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 | |||
71 | export 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 | |||
160 | export 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 | ||
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); |
@@ -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 @@ | |||
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 |