aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/graph/ExportPanel.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/graph/ExportPanel.tsx')
-rw-r--r--subprojects/frontend/src/graph/ExportPanel.tsx215
1 files changed, 215 insertions, 0 deletions
diff --git a/subprojects/frontend/src/graph/ExportPanel.tsx b/subprojects/frontend/src/graph/ExportPanel.tsx
new file mode 100644
index 00000000..2621deff
--- /dev/null
+++ b/subprojects/frontend/src/graph/ExportPanel.tsx
@@ -0,0 +1,215 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import ChevronRightIcon from '@mui/icons-material/ChevronRight';
8import ContentCopyIcon from '@mui/icons-material/ContentCopy';
9import DarkModeIcon from '@mui/icons-material/DarkMode';
10import ImageIcon from '@mui/icons-material/Image';
11import LightModeIcon from '@mui/icons-material/LightMode';
12import SaveAltIcon from '@mui/icons-material/SaveAlt';
13import ShapeLineIcon from '@mui/icons-material/ShapeLine';
14import Box from '@mui/material/Box';
15import Button from '@mui/material/Button';
16import FormControlLabel from '@mui/material/FormControlLabel';
17import Slider from '@mui/material/Slider';
18import Stack from '@mui/material/Stack';
19import Switch from '@mui/material/Switch';
20import ToggleButton from '@mui/material/ToggleButton';
21import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
22import Typography from '@mui/material/Typography';
23import { styled } from '@mui/material/styles';
24import { observer } from 'mobx-react-lite';
25import { useCallback } from 'react';
26
27import { useRootStore } from '../RootStoreProvider';
28import getLogger from '../utils/getLogger';
29
30import type GraphStore from './GraphStore';
31import SlideInPanel from './SlideInPanel';
32import exportDiagram from './exportDiagram';
33
34const log = getLogger('graph.ExportPanel');
35
36const SwitchButtonGroup = styled(ToggleButtonGroup, {
37 name: 'ExportPanel-SwitchButtonGroup',
38})(({ theme }) => ({
39 marginTop: theme.spacing(2),
40 marginInline: theme.spacing(2),
41 minWidth: '260px',
42 '.MuiToggleButton-root': {
43 width: '100%',
44 fontSize: '1rem',
45 lineHeight: '1.5',
46 },
47 '& svg': {
48 margin: '0 6px 0 0',
49 },
50}));
51
52function getLabel(value: number): string {
53 return `${value}%`;
54}
55
56const marks = [100, 200, 300, 400].map((value) => ({
57 value,
58 label: (
59 <Stack direction="column" alignItems="center">
60 <ImageIcon sx={{ width: `${11 + (value / 100) * 3}px` }} />
61 <Typography variant="caption">{getLabel(value)}</Typography>
62 </Stack>
63 ),
64}));
65
66function ExportPanel({
67 graph,
68 svgContainer,
69 dialog,
70}: {
71 graph: GraphStore;
72 svgContainer: HTMLElement | undefined;
73 dialog: boolean;
74}): JSX.Element {
75 const { exportSettingsStore } = useRootStore();
76
77 const icon = useCallback(
78 (show: boolean) =>
79 show && !dialog ? <ChevronRightIcon /> : <SaveAltIcon />,
80 [dialog],
81 );
82
83 const { format } = exportSettingsStore;
84 const emptyGraph = graph.semantics.nodes.length === 0;
85 const buttons = useCallback(
86 (close: () => void) => (
87 <>
88 <Button
89 color="inherit"
90 startIcon={<SaveAltIcon />}
91 disabled={emptyGraph}
92 onClick={() => {
93 exportDiagram(svgContainer, graph, exportSettingsStore, 'download')
94 .then(close)
95 .catch((error) => {
96 log.error('Failed to download diagram', error);
97 });
98 }}
99 >
100 Download
101 </Button>
102 {'write' in navigator.clipboard && format === 'png' && (
103 <Button
104 color="inherit"
105 startIcon={<ContentCopyIcon />}
106 disabled={emptyGraph}
107 onClick={() => {
108 exportDiagram(svgContainer, graph, exportSettingsStore, 'copy')
109 .then(close)
110 .catch((error) => {
111 log.error('Failed to copy diagram', error);
112 });
113 }}
114 >
115 Copy
116 </Button>
117 )}
118 </>
119 ),
120 [svgContainer, graph, exportSettingsStore, format, emptyGraph],
121 );
122
123 return (
124 <SlideInPanel
125 anchor="right"
126 dialog={dialog}
127 title="Export diagram"
128 icon={icon}
129 iconLabel="Show export panel"
130 buttons={buttons}
131 >
132 <SwitchButtonGroup size="small" className="rounded">
133 <ToggleButton
134 value="svg"
135 selected={exportSettingsStore.format === 'svg'}
136 onClick={() => exportSettingsStore.setFormat('svg')}
137 >
138 <ShapeLineIcon fontSize="small" /> SVG
139 </ToggleButton>
140 <ToggleButton
141 value="png"
142 selected={exportSettingsStore.format === 'png'}
143 onClick={() => exportSettingsStore.setFormat('png')}
144 >
145 <ImageIcon fontSize="small" /> PNG
146 </ToggleButton>
147 </SwitchButtonGroup>
148 <SwitchButtonGroup size="small" className="rounded">
149 <ToggleButton
150 value="svg"
151 selected={exportSettingsStore.theme === 'light'}
152 onClick={() => exportSettingsStore.setTheme('light')}
153 >
154 <LightModeIcon fontSize="small" /> Light
155 </ToggleButton>
156 <ToggleButton
157 value="png"
158 selected={exportSettingsStore.theme === 'dark'}
159 onClick={() => exportSettingsStore.setTheme('dark')}
160 >
161 <DarkModeIcon fontSize="small" /> Dark
162 </ToggleButton>
163 </SwitchButtonGroup>
164 <FormControlLabel
165 control={
166 <Switch
167 checked={exportSettingsStore.transparent}
168 onClick={() => exportSettingsStore.toggleTransparent()}
169 />
170 }
171 label="Transparent background"
172 />
173 {exportSettingsStore.format === 'svg' && (
174 <FormControlLabel
175 control={
176 <Switch
177 checked={exportSettingsStore.embedFonts}
178 onClick={() => exportSettingsStore.toggleEmbedFonts()}
179 />
180 }
181 label={
182 <Stack direction="column">
183 <Typography>Embed fonts</Typography>
184 <Typography variant="caption">
185 +75&thinsp;kB, only supported in browsers
186 </Typography>
187 </Stack>
188 }
189 />
190 )}
191 {exportSettingsStore.format === 'png' && (
192 <Box mx={4} mt={1} mb={2}>
193 <Slider
194 aria-label="Image scale"
195 value={exportSettingsStore.scale}
196 min={100}
197 max={400}
198 valueLabelFormat={getLabel}
199 getAriaValueText={getLabel}
200 step={50}
201 valueLabelDisplay="auto"
202 marks={marks}
203 onChange={(_, value) => {
204 if (typeof value === 'number') {
205 exportSettingsStore.setScale(value);
206 }
207 }}
208 />
209 </Box>
210 )}
211 </SlideInPanel>
212 );
213}
214
215export default observer(ExportPanel);