diff options
Diffstat (limited to 'subprojects/frontend/src/graph')
6 files changed, 211 insertions, 70 deletions
diff --git a/subprojects/frontend/src/graph/SlideInPanel.tsx b/subprojects/frontend/src/graph/SlideInPanel.tsx index 2c189b5b..47bbe0a6 100644 --- a/subprojects/frontend/src/graph/SlideInPanel.tsx +++ b/subprojects/frontend/src/graph/SlideInPanel.tsx | |||
@@ -8,6 +8,7 @@ import Dialog from '@mui/material/Dialog'; | |||
8 | import IconButton from '@mui/material/IconButton'; | 8 | import IconButton from '@mui/material/IconButton'; |
9 | import Paper from '@mui/material/Paper'; | 9 | import Paper from '@mui/material/Paper'; |
10 | import Slide from '@mui/material/Slide'; | 10 | import Slide from '@mui/material/Slide'; |
11 | import Tooltip from '@mui/material/Tooltip'; | ||
11 | import { styled } from '@mui/material/styles'; | 12 | import { styled } from '@mui/material/styles'; |
12 | import React, { useCallback, useId, useState } from 'react'; | 13 | import React, { useCallback, useId, useState } from 'react'; |
13 | 14 | ||
@@ -58,15 +59,19 @@ export default function SlideInPanel({ | |||
58 | 59 | ||
59 | return ( | 60 | return ( |
60 | <SlideInPanelRoot anchor={anchor}> | 61 | <SlideInPanelRoot anchor={anchor}> |
61 | <IconButton | 62 | <Tooltip |
62 | role="switch" | 63 | title={iconLabel} |
63 | aria-checked={show} | 64 | placement={anchor === 'left' ? 'right' : 'left'} |
64 | aria-controls={dialog ? undefined : id} | ||
65 | aria-label={iconLabel} | ||
66 | onClick={() => setShow(!show)} | ||
67 | > | 65 | > |
68 | {icon(show)} | 66 | <IconButton |
69 | </IconButton> | 67 | role="switch" |
68 | aria-checked={show} | ||
69 | aria-controls={dialog ? undefined : id} | ||
70 | onClick={() => setShow(!show)} | ||
71 | > | ||
72 | {icon(show)} | ||
73 | </IconButton> | ||
74 | </Tooltip> | ||
70 | {dialog ? ( | 75 | {dialog ? ( |
71 | <Dialog open={show} onClose={close} maxWidth="xl"> | 76 | <Dialog open={show} onClose={close} maxWidth="xl"> |
72 | <SlideInDialog close={close} dialog title={title} buttons={buttons}> | 77 | <SlideInDialog close={close} dialog title={title} buttons={buttons}> |
diff --git a/subprojects/frontend/src/graph/VisibilityPanel.tsx b/subprojects/frontend/src/graph/VisibilityPanel.tsx index 210ff5d5..8474b7be 100644 --- a/subprojects/frontend/src/graph/VisibilityPanel.tsx +++ b/subprojects/frontend/src/graph/VisibilityPanel.tsx | |||
@@ -199,7 +199,7 @@ function VisibilityPanel({ | |||
199 | dialog={dialog} | 199 | dialog={dialog} |
200 | title="Customize view" | 200 | title="Customize view" |
201 | icon={icon} | 201 | icon={icon} |
202 | iconLabel="Show filter panel" | 202 | iconLabel="Filter panel" |
203 | buttons={ | 203 | buttons={ |
204 | <> | 204 | <> |
205 | <Button | 205 | <Button |
diff --git a/subprojects/frontend/src/graph/ZoomButtons.tsx b/subprojects/frontend/src/graph/ZoomButtons.tsx index 83938cf4..b292a617 100644 --- a/subprojects/frontend/src/graph/ZoomButtons.tsx +++ b/subprojects/frontend/src/graph/ZoomButtons.tsx | |||
@@ -10,6 +10,7 @@ import RemoveIcon from '@mui/icons-material/Remove'; | |||
10 | import IconButton from '@mui/material/IconButton'; | 10 | import IconButton from '@mui/material/IconButton'; |
11 | import Stack from '@mui/material/Stack'; | 11 | import Stack from '@mui/material/Stack'; |
12 | import ToggleButton from '@mui/material/ToggleButton'; | 12 | import ToggleButton from '@mui/material/ToggleButton'; |
13 | import Tooltip from '@mui/material/Tooltip'; | ||
13 | 14 | ||
14 | import type { ChangeZoomCallback, SetFitZoomCallback } from './ZoomCanvas'; | 15 | import type { ChangeZoomCallback, SetFitZoomCallback } from './ZoomCanvas'; |
15 | 16 | ||
@@ -28,22 +29,27 @@ export default function ZoomButtons({ | |||
28 | p={1} | 29 | p={1} |
29 | sx={{ position: 'absolute', bottom: 0, right: 0 }} | 30 | sx={{ position: 'absolute', bottom: 0, right: 0 }} |
30 | > | 31 | > |
31 | <IconButton aria-label="Zoom in" onClick={() => changeZoom(2)}> | 32 | <Tooltip title="Zoom in" placement="left"> |
32 | <AddIcon fontSize="small" /> | 33 | <IconButton onClick={() => changeZoom(2)}> |
33 | </IconButton> | 34 | <AddIcon fontSize="small" /> |
34 | <IconButton aria-label="Zoom out" onClick={() => changeZoom(0.5)}> | 35 | </IconButton> |
35 | <RemoveIcon fontSize="small" /> | 36 | </Tooltip> |
36 | </IconButton> | 37 | <Tooltip title="Zoom out" placement="left"> |
37 | <ToggleButton | 38 | <IconButton onClick={() => changeZoom(0.5)}> |
38 | value="show-replace" | 39 | <RemoveIcon fontSize="small" /> |
39 | selected={fitZoom} | 40 | </IconButton> |
40 | onClick={() => setFitZoom(!fitZoom)} | 41 | </Tooltip> |
41 | aria-label="Fit screen" | 42 | <Tooltip title="Fit screen" placement="left"> |
42 | size="small" | 43 | <ToggleButton |
43 | className="iconOnly" | 44 | value="show-replace" |
44 | > | 45 | selected={fitZoom} |
45 | <CropFreeIcon fontSize="small" /> | 46 | onClick={() => setFitZoom(!fitZoom)} |
46 | </ToggleButton> | 47 | size="small" |
48 | className="iconOnly" | ||
49 | > | ||
50 | <CropFreeIcon fontSize="small" /> | ||
51 | </ToggleButton> | ||
52 | </Tooltip> | ||
47 | </Stack> | 53 | </Stack> |
48 | ); | 54 | ); |
49 | } | 55 | } |
diff --git a/subprojects/frontend/src/graph/export/ExportPanel.tsx b/subprojects/frontend/src/graph/export/ExportPanel.tsx index c93fa837..81bd9081 100644 --- a/subprojects/frontend/src/graph/export/ExportPanel.tsx +++ b/subprojects/frontend/src/graph/export/ExportPanel.tsx | |||
@@ -6,6 +6,7 @@ | |||
6 | 6 | ||
7 | import ChevronRightIcon from '@mui/icons-material/ChevronRight'; | 7 | import ChevronRightIcon from '@mui/icons-material/ChevronRight'; |
8 | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; | 8 | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; |
9 | import ContrastIcon from '@mui/icons-material/Contrast'; | ||
9 | import DarkModeIcon from '@mui/icons-material/DarkMode'; | 10 | import DarkModeIcon from '@mui/icons-material/DarkMode'; |
10 | import ImageIcon from '@mui/icons-material/Image'; | 11 | import ImageIcon from '@mui/icons-material/Image'; |
11 | import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; | 12 | import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; |
@@ -50,6 +51,13 @@ const SwitchButtonGroup = styled(ToggleButtonGroup, { | |||
50 | }, | 51 | }, |
51 | })); | 52 | })); |
52 | 53 | ||
54 | const AutoThemeMessage = styled(Typography, { | ||
55 | name: 'ExportPanel-AutoThemeMessage', | ||
56 | })(({ theme }) => ({ | ||
57 | width: '260px', | ||
58 | marginInline: theme.spacing(2), | ||
59 | })); | ||
60 | |||
53 | function getLabel(value: number): string { | 61 | function getLabel(value: number): string { |
54 | return `${value}%`; | 62 | return `${value}%`; |
55 | } | 63 | } |
@@ -127,7 +135,7 @@ function ExportPanel({ | |||
127 | dialog={dialog} | 135 | dialog={dialog} |
128 | title="Export diagram" | 136 | title="Export diagram" |
129 | icon={icon} | 137 | icon={icon} |
130 | iconLabel="Show export panel" | 138 | iconLabel={`Export image\u2026`} |
131 | buttons={buttons} | 139 | buttons={buttons} |
132 | > | 140 | > |
133 | <SwitchButtonGroup size="small" className="rounded"> | 141 | <SwitchButtonGroup size="small" className="rounded"> |
@@ -155,29 +163,40 @@ function ExportPanel({ | |||
155 | </SwitchButtonGroup> | 163 | </SwitchButtonGroup> |
156 | <SwitchButtonGroup size="small" className="rounded"> | 164 | <SwitchButtonGroup size="small" className="rounded"> |
157 | <ToggleButton | 165 | <ToggleButton |
158 | value="svg" | 166 | value="light" |
159 | selected={exportSettingsStore.theme === 'light'} | 167 | selected={exportSettingsStore.theme === 'light'} |
160 | onClick={() => exportSettingsStore.setTheme('light')} | 168 | onClick={() => exportSettingsStore.setTheme('light')} |
161 | > | 169 | > |
162 | <LightModeIcon fontSize="small" /> Light | 170 | <LightModeIcon fontSize="small" /> Light |
163 | </ToggleButton> | 171 | </ToggleButton> |
164 | <ToggleButton | 172 | <ToggleButton |
165 | value="png" | 173 | value="dark" |
166 | selected={exportSettingsStore.theme === 'dark'} | 174 | selected={exportSettingsStore.theme === 'dark'} |
167 | onClick={() => exportSettingsStore.setTheme('dark')} | 175 | onClick={() => exportSettingsStore.setTheme('dark')} |
168 | > | 176 | > |
169 | <DarkModeIcon fontSize="small" /> Dark | 177 | <DarkModeIcon fontSize="small" /> Dark |
170 | </ToggleButton> | 178 | </ToggleButton> |
179 | {exportSettingsStore.canSetDynamicTheme && ( | ||
180 | <ToggleButton | ||
181 | value="dynamic" | ||
182 | selected={exportSettingsStore.theme === 'dynamic'} | ||
183 | onClick={() => exportSettingsStore.setTheme('dynamic')} | ||
184 | > | ||
185 | <ContrastIcon fontSize="small" /> Auto | ||
186 | </ToggleButton> | ||
187 | )} | ||
171 | </SwitchButtonGroup> | 188 | </SwitchButtonGroup> |
172 | <FormControlLabel | 189 | {exportSettingsStore.canChangeTransparency && ( |
173 | control={ | 190 | <FormControlLabel |
174 | <Switch | 191 | control={ |
175 | checked={exportSettingsStore.transparent} | 192 | <Switch |
176 | onClick={() => exportSettingsStore.toggleTransparent()} | 193 | checked={exportSettingsStore.transparent} |
177 | /> | 194 | onClick={() => exportSettingsStore.toggleTransparent()} |
178 | } | 195 | /> |
179 | label="Transparent background" | 196 | } |
180 | /> | 197 | label="Transparent background" |
198 | /> | ||
199 | )} | ||
181 | {exportSettingsStore.canEmbedFonts && ( | 200 | {exportSettingsStore.canEmbedFonts && ( |
182 | <FormControlLabel | 201 | <FormControlLabel |
183 | control={ | 202 | control={ |
@@ -200,6 +219,17 @@ function ExportPanel({ | |||
200 | } | 219 | } |
201 | /> | 220 | /> |
202 | )} | 221 | )} |
222 | {exportSettingsStore.theme === 'dynamic' && ( | ||
223 | <> | ||
224 | <AutoThemeMessage mt={2}> | ||
225 | For embedding into HTML directly | ||
226 | </AutoThemeMessage> | ||
227 | <AutoThemeMessage variant="caption" mt={1}> | ||
228 | Set <code>data-theme="dark"</code> on a containing element | ||
229 | to use a dark theme | ||
230 | </AutoThemeMessage> | ||
231 | </> | ||
232 | )} | ||
203 | {exportSettingsStore.canScale && ( | 233 | {exportSettingsStore.canScale && ( |
204 | <Box mx={4} mt={1} mb={2}> | 234 | <Box mx={4} mt={1} mb={2}> |
205 | <Slider | 235 | <Slider |
diff --git a/subprojects/frontend/src/graph/export/ExportSettingsStore.ts b/subprojects/frontend/src/graph/export/ExportSettingsStore.ts index 53a161ab..7c691a7b 100644 --- a/subprojects/frontend/src/graph/export/ExportSettingsStore.ts +++ b/subprojects/frontend/src/graph/export/ExportSettingsStore.ts | |||
@@ -7,18 +7,21 @@ | |||
7 | import { makeAutoObservable } from 'mobx'; | 7 | import { makeAutoObservable } from 'mobx'; |
8 | 8 | ||
9 | export type ExportFormat = 'svg' | 'pdf' | 'png'; | 9 | export type ExportFormat = 'svg' | 'pdf' | 'png'; |
10 | export type ExportTheme = 'light' | 'dark'; | 10 | export type StaticTheme = 'light' | 'dark'; |
11 | export type ExportTheme = StaticTheme | 'dynamic'; | ||
11 | 12 | ||
12 | export default class ExportSettingsStore { | 13 | export default class ExportSettingsStore { |
13 | format: ExportFormat = 'svg'; | 14 | format: ExportFormat = 'svg'; |
14 | 15 | ||
15 | theme: ExportTheme = 'light'; | 16 | private staticTheme: StaticTheme = 'light'; |
16 | 17 | ||
17 | transparent = true; | 18 | private _theme: ExportTheme = 'light'; |
18 | 19 | ||
19 | embedSVGFonts = false; | 20 | private _transparent = true; |
20 | 21 | ||
21 | embedPDFFonts = true; | 22 | private embedSVGFonts = false; |
23 | |||
24 | private embedPDFFonts = true; | ||
22 | 25 | ||
23 | scale = 100; | 26 | scale = 100; |
24 | 27 | ||
@@ -31,11 +34,14 @@ export default class ExportSettingsStore { | |||
31 | } | 34 | } |
32 | 35 | ||
33 | setTheme(theme: ExportTheme): void { | 36 | setTheme(theme: ExportTheme): void { |
34 | this.theme = theme; | 37 | this._theme = theme; |
38 | if (theme !== 'dynamic') { | ||
39 | this.staticTheme = theme; | ||
40 | } | ||
35 | } | 41 | } |
36 | 42 | ||
37 | toggleTransparent(): void { | 43 | toggleTransparent(): void { |
38 | this.transparent = !this.transparent; | 44 | this._transparent = !this._transparent; |
39 | } | 45 | } |
40 | 46 | ||
41 | toggleEmbedFonts(): void { | 47 | toggleEmbedFonts(): void { |
@@ -46,7 +52,18 @@ export default class ExportSettingsStore { | |||
46 | this.scale = scale; | 52 | this.scale = scale; |
47 | } | 53 | } |
48 | 54 | ||
55 | get theme(): ExportTheme { | ||
56 | return this.format === 'svg' ? this._theme : this.staticTheme; | ||
57 | } | ||
58 | |||
59 | get transparent(): boolean { | ||
60 | return this.theme === 'dynamic' ? true : this._transparent; | ||
61 | } | ||
62 | |||
49 | get embedFonts(): boolean { | 63 | get embedFonts(): boolean { |
64 | if (this.theme === 'dynamic') { | ||
65 | return false; | ||
66 | } | ||
50 | return this.format === 'pdf' ? this.embedPDFFonts : this.embedSVGFonts; | 67 | return this.format === 'pdf' ? this.embedPDFFonts : this.embedSVGFonts; |
51 | } | 68 | } |
52 | 69 | ||
@@ -57,8 +74,19 @@ export default class ExportSettingsStore { | |||
57 | this.embedSVGFonts = embedFonts; | 74 | this.embedSVGFonts = embedFonts; |
58 | } | 75 | } |
59 | 76 | ||
77 | get canSetDynamicTheme(): boolean { | ||
78 | return this.format === 'svg'; | ||
79 | } | ||
80 | |||
81 | get canChangeTransparency(): boolean { | ||
82 | return this.theme !== 'dynamic'; | ||
83 | } | ||
84 | |||
60 | get canEmbedFonts(): boolean { | 85 | get canEmbedFonts(): boolean { |
61 | return this.format === 'svg' || this.format === 'pdf'; | 86 | return ( |
87 | (this.format === 'svg' || this.format === 'pdf') && | ||
88 | this.theme !== 'dynamic' | ||
89 | ); | ||
62 | } | 90 | } |
63 | 91 | ||
64 | get canScale(): boolean { | 92 | get canScale(): boolean { |
diff --git a/subprojects/frontend/src/graph/export/exportDiagram.tsx b/subprojects/frontend/src/graph/export/exportDiagram.tsx index 6abbcfdf..52d19aa0 100644 --- a/subprojects/frontend/src/graph/export/exportDiagram.tsx +++ b/subprojects/frontend/src/graph/export/exportDiagram.tsx | |||
@@ -16,6 +16,7 @@ import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; | |||
16 | import labelSVG from '@material-icons/svg/svg/label/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'; | 17 | import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; |
18 | import type { Theme } from '@mui/material/styles'; | 18 | import type { Theme } from '@mui/material/styles'; |
19 | import { nanoid } from 'nanoid'; | ||
19 | 20 | ||
20 | import { darkTheme, lightTheme } from '../../theme/ThemeProvider'; | 21 | import { darkTheme, lightTheme } from '../../theme/ThemeProvider'; |
21 | import { copyBlob, saveBlob } from '../../utils/fileIO'; | 22 | import { copyBlob, saveBlob } from '../../utils/fileIO'; |
@@ -48,6 +49,36 @@ importSVG(labelSVG, 'icon-TRUE'); | |||
48 | importSVG(labelOutlinedSVG, 'icon-UNKNOWN'); | 49 | importSVG(labelOutlinedSVG, 'icon-UNKNOWN'); |
49 | importSVG(cancelSVG, 'icon-ERROR'); | 50 | importSVG(cancelSVG, 'icon-ERROR'); |
50 | 51 | ||
52 | function fixIDs(id: string, svgDocument: XMLDocument) { | ||
53 | const idMap = new Map<string, string>(); | ||
54 | let i = 0; | ||
55 | svgDocument.querySelectorAll('[id]').forEach((node) => { | ||
56 | const oldId = node.getAttribute('id'); | ||
57 | if (oldId === null) { | ||
58 | return; | ||
59 | } | ||
60 | if (oldId.endsWith(',clip')) { | ||
61 | const newId = `refinery-${id}-clip-${i}`; | ||
62 | i += 1; | ||
63 | idMap.set(`url(#${oldId})`, `url(#${newId})`); | ||
64 | node.setAttribute('id', newId); | ||
65 | } else { | ||
66 | node.removeAttribute('id'); | ||
67 | } | ||
68 | }); | ||
69 | svgDocument.querySelectorAll('[clip-path]').forEach((node) => { | ||
70 | const oldPath = node.getAttribute('clip-path'); | ||
71 | if (oldPath === null) { | ||
72 | return; | ||
73 | } | ||
74 | const newPath = idMap.get(oldPath); | ||
75 | if (newPath === undefined) { | ||
76 | return; | ||
77 | } | ||
78 | node.setAttribute('clip-path', newPath); | ||
79 | }); | ||
80 | } | ||
81 | |||
51 | function addBackground( | 82 | function addBackground( |
52 | svgDocument: XMLDocument, | 83 | svgDocument: XMLDocument, |
53 | svg: SVGSVGElement, | 84 | svg: SVGSVGElement, |
@@ -142,40 +173,54 @@ async function fetchVariableFontCSS(): Promise<string> { | |||
142 | return variableFontCSS; | 173 | return variableFontCSS; |
143 | } | 174 | } |
144 | 175 | ||
176 | interface ThemeVariant { | ||
177 | selector: string; | ||
178 | theme: Theme; | ||
179 | } | ||
180 | |||
145 | function appendStyles( | 181 | function appendStyles( |
182 | id: string, | ||
146 | svgDocument: XMLDocument, | 183 | svgDocument: XMLDocument, |
147 | svg: SVGSVGElement, | 184 | svg: SVGSVGElement, |
148 | theme: Theme, | 185 | themes: ThemeVariant[], |
149 | colorNodes: boolean, | 186 | colorNodes: boolean, |
150 | hexTypeHashes: string[], | 187 | hexTypeHashes: string[], |
151 | fontsCSS: string, | 188 | fontsCSS: string, |
152 | ): void { | 189 | ): void { |
153 | const cache = createCache({ | 190 | const className = `refinery-${id}`; |
154 | key: 'refinery', | 191 | svg.classList.add(className); |
155 | container: svg, | ||
156 | prepend: true, | ||
157 | }); | ||
158 | // @ts-expect-error `CSSObject` types don't match up between `@mui/material` and | ||
159 | // `@emotion/serialize`, but they are compatible in practice. | ||
160 | const styles = serializeStyles([createGraphTheme], cache.registered, { | ||
161 | theme, | ||
162 | colorNodes, | ||
163 | hexTypeHashes, | ||
164 | noEmbedIcons: true, | ||
165 | }); | ||
166 | const rules: string[] = [fontsCSS]; | 192 | const rules: string[] = [fontsCSS]; |
167 | const sheet = { | 193 | themes.forEach(({ selector, theme }) => { |
168 | insert(rule) { | 194 | const cache = createCache({ |
169 | rules.push(rule); | 195 | key: 'refinery', |
170 | }, | 196 | container: svg, |
171 | } as StyleSheet; | 197 | prepend: true, |
172 | cache.insert('', styles, sheet, false); | 198 | }); |
199 | // @ts-expect-error `CSSObject` types don't match up between `@mui/material` and | ||
200 | // `@emotion/serialize`, but they are compatible in practice. | ||
201 | const styles = serializeStyles([createGraphTheme], cache.registered, { | ||
202 | theme, | ||
203 | colorNodes, | ||
204 | hexTypeHashes, | ||
205 | noEmbedIcons: true, | ||
206 | }); | ||
207 | const sheet = { | ||
208 | insert(rule) { | ||
209 | rules.push(rule); | ||
210 | }, | ||
211 | } as StyleSheet; | ||
212 | cache.insert(`${selector} .${className}`, styles, sheet, false); | ||
213 | }); | ||
173 | const styleElement = svgDocument.createElementNS(SVG_NS, 'style'); | 214 | const styleElement = svgDocument.createElementNS(SVG_NS, 'style'); |
174 | svg.prepend(styleElement); | 215 | svg.prepend(styleElement); |
175 | styleElement.innerHTML = rules.join(''); | 216 | styleElement.innerHTML = rules.join(''); |
176 | } | 217 | } |
177 | 218 | ||
178 | function fixForeignObjects(svgDocument: XMLDocument, svg: SVGSVGElement): void { | 219 | function fixForeignObjects( |
220 | id: string, | ||
221 | svgDocument: XMLDocument, | ||
222 | svg: SVGSVGElement, | ||
223 | ): void { | ||
179 | const foreignObjects: SVGForeignObjectElement[] = []; | 224 | const foreignObjects: SVGForeignObjectElement[] = []; |
180 | svg | 225 | svg |
181 | .querySelectorAll('foreignObject') | 226 | .querySelectorAll('foreignObject') |
@@ -197,7 +242,7 @@ function fixForeignObjects(svgDocument: XMLDocument, svg: SVGSVGElement): void { | |||
197 | object.children[0]?.classList?.forEach((className) => { | 242 | object.children[0]?.classList?.forEach((className) => { |
198 | useElement.classList.add(className); | 243 | useElement.classList.add(className); |
199 | if (ICONS.has(className)) { | 244 | if (ICONS.has(className)) { |
200 | useElement.setAttribute('href', `#${className}`); | 245 | useElement.setAttribute('href', `#refinery-${id}-${className}`); |
201 | } | 246 | } |
202 | }); | 247 | }); |
203 | object.replaceWith(useElement); | 248 | object.replaceWith(useElement); |
@@ -206,6 +251,7 @@ function fixForeignObjects(svgDocument: XMLDocument, svg: SVGSVGElement): void { | |||
206 | svg.prepend(defs); | 251 | svg.prepend(defs); |
207 | ICONS.forEach((value) => { | 252 | ICONS.forEach((value) => { |
208 | const importedValue = svgDocument.importNode(value, true); | 253 | const importedValue = svgDocument.importNode(value, true); |
254 | importedValue.id = `refinery-${id}-${importedValue.id}`; | ||
209 | defs.appendChild(importedValue); | 255 | defs.appendChild(importedValue); |
210 | }); | 256 | }); |
211 | } | 257 | } |
@@ -322,12 +368,37 @@ export default async function exportDiagram( | |||
322 | svgDocument.replaceChild(copyOfSVG, originalRoot); | 368 | svgDocument.replaceChild(copyOfSVG, originalRoot); |
323 | } | 369 | } |
324 | 370 | ||
325 | const theme = settings.theme === 'light' ? lightTheme : darkTheme; | 371 | const id = nanoid(); |
372 | fixIDs(id, svgDocument); | ||
373 | |||
374 | let theme: Theme; | ||
375 | let themes: ThemeVariant[]; | ||
376 | if (settings.theme === 'dynamic') { | ||
377 | theme = lightTheme; | ||
378 | themes = [ | ||
379 | { | ||
380 | selector: '', | ||
381 | theme: lightTheme, | ||
382 | }, | ||
383 | { | ||
384 | selector: '[data-theme="dark"]', | ||
385 | theme: darkTheme, | ||
386 | }, | ||
387 | ]; | ||
388 | } else { | ||
389 | theme = settings.theme === 'light' ? lightTheme : darkTheme; | ||
390 | themes = [ | ||
391 | { | ||
392 | selector: '', | ||
393 | theme, | ||
394 | }, | ||
395 | ]; | ||
396 | } | ||
326 | if (!settings.transparent) { | 397 | if (!settings.transparent) { |
327 | addBackground(svgDocument, copyOfSVG, theme); | 398 | addBackground(svgDocument, copyOfSVG, theme); |
328 | } | 399 | } |
329 | 400 | ||
330 | fixForeignObjects(svgDocument, copyOfSVG); | 401 | fixForeignObjects(id, svgDocument, copyOfSVG); |
331 | 402 | ||
332 | const { colorNodes } = graph; | 403 | const { colorNodes } = graph; |
333 | let fontsCSS = ''; | 404 | let fontsCSS = ''; |
@@ -339,9 +410,10 @@ export default async function exportDiagram( | |||
339 | fontsCSS = await fetchFontCSS(); | 410 | fontsCSS = await fetchFontCSS(); |
340 | } | 411 | } |
341 | appendStyles( | 412 | appendStyles( |
413 | id, | ||
342 | svgDocument, | 414 | svgDocument, |
343 | copyOfSVG, | 415 | copyOfSVG, |
344 | theme, | 416 | themes, |
345 | colorNodes, | 417 | colorNodes, |
346 | graph.hexTypeHashes, | 418 | graph.hexTypeHashes, |
347 | fontsCSS, | 419 | fontsCSS, |