Skip to content

Commit

Permalink
[2296] Add "export to SVG" action in the panel of react-flow prototype
Browse files Browse the repository at this point in the history
Bug: #2296
Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
  • Loading branch information
AxelRICHARD committed Aug 22, 2023
1 parent 59ed279 commit ba8f183
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -48,6 +48,7 @@ All of its remaining content was only relevant inside of Sirius Web and it has t

- [releng] Add some automatic checks to simplify code reviews
- [releng] Make some cypress tests more robust
- https://github.com/eclipse-sirius/sirius-web/issues/2296[#2296] [diagram] Add "export to SVG" action in the diagram panel of the react-flow prototype


== v2023.8.0
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -49,12 +49,13 @@
"@vitejs/plugin-react": "4.0.4",
"@vitest/coverage-v8": "0.34.2",
"elkjs": "0.8.2",
"jsdom": "16.7.0",
"graphql": "16.8.0",
"html-to-image": "1.11.11",
"jsdom": "16.7.0",
"prettier": "2.7.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"reactflow": "11.8.1",
"prettier": "2.7.1",
"rollup-plugin-peer-deps-external": "2.2.4",
"typescript": "5.1.6",
"vite": "4.4.9",
Expand Down
Expand Up @@ -19,19 +19,28 @@ import FullscreenIcon from '@material-ui/icons/Fullscreen';
import FullscreenExitIcon from '@material-ui/icons/FullscreenExit';
import GridOffIcon from '@material-ui/icons/GridOff';
import GridOnIcon from '@material-ui/icons/GridOn';
import ImageIcon from '@material-ui/icons/Image';
import ShareIcon from '@material-ui/icons/Share';
import TonalityIcon from '@material-ui/icons/Tonality';
import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
import ZoomInIcon from '@material-ui/icons/ZoomIn';
import ZoomOutIcon from '@material-ui/icons/ZoomOut';

import { toSvg } from 'html-to-image';
import { useState } from 'react';
import { Panel, useReactFlow } from 'reactflow';
import { Panel, Rect, Transform, getRectOfNodes, getTransformForBounds, useReactFlow } from 'reactflow';
import { DiagramPanelProps, DiagramPanelState } from './DiagramPanel.types';
import { ShareDiagramDialog } from './ShareDiagramDialog';
import { useFadeDiagramElements } from './fade/useFadeDiagramElements';
import { useHideDiagramElements } from './hide/useHideDiagramElements';

const downloadImage = (dataUrl: string) => {
const a: HTMLAnchorElement = document.createElement('a');
a.setAttribute('download', 'diagram.svg');
a.setAttribute('href', dataUrl);
a.click();
};

export const DiagramPanel = ({
fullscreen,
onFullscreen,
Expand All @@ -50,18 +59,35 @@ export const DiagramPanel = ({
const handleShare = () => setState((prevState) => ({ ...prevState, dialogOpen: 'Share' }));
const handleCloseDialog = () => setState((prevState) => ({ ...prevState, dialogOpen: null }));

const reactFlowInstance = useReactFlow();
const { fadeDiagramElements } = useFadeDiagramElements();
const { hideDiagramElements } = useHideDiagramElements();

const onUnfadeAll = () => fadeDiagramElements([...getAllElementsIds()], false);
const onUnhideAll = () => hideDiagramElements([...getAllElementsIds()], false);

const handleExport = () => {
const imageWidth: number = window.screen.width;
const imageHeight: number = window.screen.height;
const nodesBounds: Rect = getRectOfNodes(reactFlow.getNodes());
const transform: Transform = getTransformForBounds(nodesBounds, imageWidth, imageHeight, 0.5, 2);

const viewport: HTMLElement | null = document.querySelector<HTMLElement>('.react-flow__viewport');
if (viewport) {
toSvg(viewport, {
backgroundColor: '#ffffff',
width: imageWidth,
height: imageHeight,
style: {
width: imageWidth.toString(),
height: imageHeight.toString(),
transform: `translate(${transform[0]}px, ${transform[1]}px) scale(${transform[2]})`,
},
}).then(downloadImage);
}
};

const getAllElementsIds = () => {
return [
...reactFlowInstance.getNodes().map((elem) => elem.id),
...reactFlowInstance.getEdges().map((elem) => elem.id),
];
return [...reactFlow.getNodes().map((elem) => elem.id), ...reactFlow.getEdges().map((elem) => elem.id)];
};

return (
Expand Down Expand Up @@ -89,6 +115,14 @@ export const DiagramPanel = ({
<IconButton size="small" onClick={handleShare}>
<ShareIcon />
</IconButton>
<IconButton
size="small"
aria-label="export to svg"
title="Export to SVG"
onClick={handleExport}
data-testid="export-diagram-to-svg">
<ImageIcon />
</IconButton>
{snapToGrid ? (
<IconButton size="small" onClick={() => onSnapToGrid(false)}>
<GridOffIcon />
Expand Down

0 comments on commit ba8f183

Please sign in to comment.