Skip to content

Commit

Permalink
feat: re-enable download functionality
Browse files Browse the repository at this point in the history
Now uses `html-to-image` and utils from React Flow to more reliably
screenshot the nodes viewport. Mostly just straight copied from
the React Flow [example].

Closes #26.

[example]: https://reactflow.dev/examples/misc/download-image
  • Loading branch information
Ovyerus committed Nov 21, 2023
1 parent 97239dc commit 3758d6f
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 100 deletions.
97 changes: 43 additions & 54 deletions components/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,58 @@
import downloadIcon from "@iconify/icons-gg/software-download";
import { Icon } from "@iconify/react";
import html2canvas from "html2canvas";
import { toPng } from "html-to-image";
import React from "react";
import { ControlButton } from "reactflow";

const PADDING = 25;

const generateImage = async () => {
// Find the HTML-Elements
const element = document.querySelector(".react-flow");
const viewportElement = document.querySelector(".react-flow__viewport");
const controlsElement = document.querySelector(".react-flow__controls");
const attributionElement = document.querySelector(".react-flow__attribution");
const backgroundElement = document.querySelector(
"svg.react-flow__background",
);
const edgesElement = document.querySelector("svg.react-flow__edges");

// Those two are required but should always be there anyway
if (!element || !viewportElement) return;

// Setting temporary CSS-Styles
element.setAttribute("style", "overflow: visible; !important");
const viewportOld = viewportElement.getAttribute("style");
viewportElement.removeAttribute("style");
controlsElement?.setAttribute("style", "display:none;");
attributionElement?.setAttribute("style", "display:none;");
backgroundElement?.setAttribute(
"style",
`width:${element.scrollWidth}px; height:${element.scrollHeight}px;`,
);
edgesElement?.setAttribute(
"style",
`z-index: 0; width:${element.scrollWidth}px; height:${element.scrollHeight}px;`,
);

// Generate the Image-Data from HTML-Element
const canvas = await html2canvas(element as HTMLElement, {
width: element.scrollWidth + PADDING,
height: element.scrollHeight + PADDING,
});
const data = canvas.toDataURL();

// Resetting the CSS-Styles
element.removeAttribute("style");
viewportElement.setAttribute("style", viewportOld || "");
controlsElement?.removeAttribute("style");
attributionElement?.removeAttribute("style");
backgroundElement?.setAttribute("style", "width:100%; height:100%;");
edgesElement?.setAttribute("style", "z-index: 0;");

// Downloading the Image
import {
ControlButton,
useReactFlow,
getRectOfNodes,
getViewportForBounds,
} from "reactflow";

const downloadImage = (dataUrl: string) => {
const a = document.createElement("a");

a.setAttribute("download", "prismaliser.png");
a.setAttribute("href", data);
a.setAttribute("href", dataUrl);
a.click();
};

// Mostly a copy from the React Flow example: https://reactflow.dev/examples/misc/download-image
const DownloadButton = () => {
const download = async () => {
await generateImage();
const { getNodes } = useReactFlow();
const onClick = () => {
// we calculate a transform for the nodes so that all nodes are visible
// we then overwrite the transform of the `.react-flow__viewport` element
// with the style option of the html-to-image library
const nodesBounds = getRectOfNodes(getNodes());
const { height: imageHeight, width: imageWidth } = nodesBounds;
const transform = getViewportForBounds(
nodesBounds,
imageWidth,
imageHeight,
0.5,
2,
);
const viewport: HTMLDivElement = document.querySelector(
".react-flow__viewport",
)!;

toPng(viewport, {
backgroundColor: "#e5e7eb",
width: imageWidth,
height: imageHeight,
style: {
width: imageWidth as any,
height: imageHeight as any,
transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.zoom})`,
},
})
.then(downloadImage)
.catch(console.error);
};

return (
<ControlButton title="Download as PNG" onClick={download}>
<ControlButton title="Download as PNG" onClick={onClick}>
<Icon icon={downloadIcon} />
</ControlButton>
);
Expand Down
4 changes: 2 additions & 2 deletions components/FlowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReactFlow, {
OnNodesChange,
} from "reactflow";

// import DownloadButton from "./DownloadButton";
import DownloadButton from "./DownloadButton";

import EnumNode from "~/components/EnumNode";
import ModelNode from "~/components/ModelNode";
Expand Down Expand Up @@ -78,7 +78,7 @@ const FlowView = ({ dmmf }: FlowViewProps) => {
<ControlButton title="Disperse nodes" onClick={refreshLayout}>
<Icon icon={listTree} />
</ControlButton>
{/* <DownloadButton /> */}
<DownloadButton />
</Controls>
</ReactFlow>
<svg width="0" height="0">
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@prisma/internals": "^5.6.0",
"classcat": "^5.0.4",
"elkjs": "^0.8.2",
"html2canvas": "^1.4.1",
"html-to-image": "^1.11.11",
"next": "^13.5.6",
"rambda": "^8.5.0",
"react": "^18.2.0",
Expand Down
1 change: 0 additions & 1 deletion util/prismaToFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ const positionNodes = (
const positionedNode = layout?.children?.find(
(layoutNode) => layoutNode.id === n.name,
);
console.log(positionedNode, n.name);
const previousNode = previousNodes.find((prev) => prev.id === n.name);

return {
Expand Down
47 changes: 5 additions & 42 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1559,13 +1559,6 @@ __metadata:
languageName: node
linkType: hard

"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
checksum: 15e6400d2d028bf18be4ed97702b11418f8f8779fb8c743251c863b726638d52f69571d4cc1843224da7838abef0949c670bde46936663c45ad078e89fee5c62
languageName: node
linkType: hard

"big-integer@npm:^1.6.44":
version: 1.6.51
resolution: "big-integer@npm:1.6.51"
Expand Down Expand Up @@ -1963,15 +1956,6 @@ __metadata:
languageName: node
linkType: hard

"css-line-break@npm:^2.1.0":
version: 2.1.0
resolution: "css-line-break@npm:2.1.0"
dependencies:
utrie: ^1.0.2
checksum: 37b1fe632b03be7a287cd394cef8b5285666343443125c510df9cfb6a4734a2c71e154ec8f7bbff72d7c339e1e5872989b1c52d86162aed27d6cc114725bb4d0
languageName: node
linkType: hard

"css-tree@npm:^1.1.2":
version: 1.1.3
resolution: "css-tree@npm:1.1.3"
Expand Down Expand Up @@ -3473,13 +3457,10 @@ __metadata:
languageName: node
linkType: hard

"html2canvas@npm:^1.4.1":
version: 1.4.1
resolution: "html2canvas@npm:1.4.1"
dependencies:
css-line-break: ^2.1.0
text-segmentation: ^1.0.3
checksum: c134324af57f3262eecf982e436a4843fded3c6cf61954440ffd682527e4dd350e0c2fafd217c0b6f9a455fe345d0c67b4505689796ab160d4ca7c91c3766739
"html-to-image@npm:^1.11.11":
version: 1.11.11
resolution: "html-to-image@npm:1.11.11"
checksum: b453beca72a697bf06fae4945e5460d1d9b1751e8569a0d721dda9485df1dde093938cc9bd9172b8df5fc23133a53a4d619777b3d22f7211cd8a67e3197ab4e8
languageName: node
linkType: hard

Expand Down Expand Up @@ -5254,7 +5235,7 @@ __metadata:
eslint: ^8.54.0
eslint-config-clarity: 1.0.6
eslint-import-resolver-typescript: ^3.6.1
html2canvas: ^1.4.1
html-to-image: ^1.11.11
monaco-editor: ^0.44.0
next: ^13.5.6
postcss: ^8.4.31
Expand Down Expand Up @@ -6440,15 +6421,6 @@ __metadata:
languageName: node
linkType: hard

"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
dependencies:
utrie: ^1.0.2
checksum: 2e24632d59567c55ab49ac324815e2f7a8043e63e26b109636322ac3e30692cee8679a448fd5d0f0598a345f407afd0e34ba612e22524cf576d382d84058c013
languageName: node
linkType: hard

"text-table@npm:^0.2.0":
version: 0.2.0
resolution: "text-table@npm:0.2.0"
Expand Down Expand Up @@ -6820,15 +6792,6 @@ __metadata:
languageName: node
linkType: hard

"utrie@npm:^1.0.2":
version: 1.0.2
resolution: "utrie@npm:1.0.2"
dependencies:
base64-arraybuffer: ^1.0.2
checksum: c96fbb7d4d8855a154327da0b18e39b7511cc70a7e4bcc3658e24f424bb884312d72b5ba777500b8858e34d365dc6b1a921dc5ca2f0d341182519c6b78e280a5
languageName: node
linkType: hard

"uuid@npm:9.0.0":
version: 9.0.0
resolution: "uuid@npm:9.0.0"
Expand Down

0 comments on commit 3758d6f

Please sign in to comment.