diff --git a/admin/src/components/ExportModal.js b/admin/src/components/ExportModal.js
new file mode 100644
index 0000000..8543bbc
--- /dev/null
+++ b/admin/src/components/ExportModal.js
@@ -0,0 +1,129 @@
+import {
+ ModalLayout,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ Button,
+ Typography,
+ SingleSelect,
+ SingleSelectOption,
+ NumberInput,
+} from "@strapi/design-system";
+import { toJpeg, toPng, toSvg } from "html-to-image";
+import { useCallback, useState } from "react";
+import { useDigramStore } from "../store";
+import { useTheme } from "styled-components";
+
+export function ExportModal({ imageRef }) {
+ const theme = useTheme();
+
+ const { setShowModal } = useDigramStore();
+
+ const [format, setFormat] = useState("png");
+ const [quality, setQuality] = useState(0.95);
+
+ function downloadImage(dataUrl, fileExtension) {
+ const link = document.createElement("a");
+ link.download = `strapi-diagram-${new Date()
+ .toISOString()
+ .replace(/[-:T.]/g, "")
+ .slice(0, -5)}.${fileExtension}`;
+ link.href = dataUrl;
+ link.click();
+ }
+
+ const exportDiagram = useCallback(() => {
+ if (imageRef.current === null) {
+ return;
+ }
+
+ const filter = (node) => {
+ const exclusionClasses = ["cte-plugin-controls"];
+ return !exclusionClasses.some((classname) =>
+ node.classList?.contains(classname)
+ );
+ };
+
+ if (format == "png") {
+ toPng(imageRef.current, {
+ cacheBust: true,
+ filter: filter,
+ style: {
+ background: theme.colors.neutral100,
+ },
+ })
+ .then((dataUrl) => {
+ downloadImage(dataUrl, "png");
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ } else if (format == "svg") {
+ toSvg(imageRef.current, {
+ cacheBust: true,
+ filter: filter,
+ style: {
+ background: theme.colors.neutral100,
+ },
+ })
+ .then((dataUrl) => {
+ downloadImage(dataUrl, "svg");
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ } else if (format == "jpeg") {
+ toJpeg(imageRef.current, {
+ cacheBust: true,
+ filter: filter,
+ quality: quality,
+ style: {
+ background: theme.colors.neutral100,
+ },
+ })
+ .then((dataUrl) => {
+ downloadImage(dataUrl, "jpeg");
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ }
+ }, [imageRef, quality, format]);
+
+ return (
+ setShowModal(false)}>
+
+
+ Export Diagram
+
+
+
+ {
+ setFormat(undefined);
+ }}
+ value={format}
+ onChange={setFormat}
+ >
+ SVG
+ PNG
+ JPEG
+
+
+ {format == "jpeg" && (
+ setQuality(value)}
+ value={quality}
+ />
+ )}
+
+ Export}
+ />
+
+ );
+}
diff --git a/admin/src/pages/HomePage/index.js b/admin/src/pages/HomePage/index.js
index ccbcfe5..7243f92 100644
--- a/admin/src/pages/HomePage/index.js
+++ b/admin/src/pages/HomePage/index.js
@@ -15,6 +15,7 @@ import OptionsBar from "../../components/OptionsBar";
import "reactflow/dist/style.css";
import "./styles.css";
import { useDigramStore } from "../../store";
+import { ExportModal } from "../../components/ExportModal";
const useEffectSkipInitial = (func, deps) => {
const didMount = useRef(false);
@@ -40,6 +41,8 @@ const HomePage = () => {
toggleOption,
options,
setData,
+ showModal,
+ setShowModal,
} = useDigramStore();
const nodeTypes = useMemo(() => ({ special: CustomNode }), []);
@@ -52,14 +55,14 @@ const HomePage = () => {
[]
);
- const refresh = async () => {
+ const regenrate = async () => {
const { data } = await get(`/strapi-content-type-explorer/get-types`);
setData(data);
drawDiagram();
};
useEffectSkipInitial(() => {
- refresh();
+ regenrate();
}, [options.showAdminTypes, options.showPluginTypes]);
useEffect(() => {
@@ -75,23 +78,30 @@ const HomePage = () => {
options.showDefaultFields,
]);
+ const ref = useRef(null);
+
return (
}>
+
}
+ onClick={() => setShowModal(true)}
+ >
Export Diagram
}
secondaryAction={
-
} onClick={refresh}>
- Draw Again
+
} onClick={regenrate}>
+ Regenrate
}
/>
{
color={getBackgroundColor(options.backgroundPattern, theme)}
/>
+ {showModal && }
);
diff --git a/admin/src/store/index.js b/admin/src/store/index.js
index f7ba188..9dc4cf4 100644
--- a/admin/src/store/index.js
+++ b/admin/src/store/index.js
@@ -14,6 +14,7 @@ export const useDigramStore = create(
nodes: [],
edges: [],
data: [],
+ showModal: false,
options: {
snapToGrid: false,
showTypes: true,
@@ -32,6 +33,11 @@ export const useDigramStore = create(
data: contentTypesData,
});
},
+ setShowModal: (bool) => {
+ set({
+ showModal: bool,
+ });
+ },
toggleOption: (optionName, optionValue = null) => {
let newOptions = {
...get().options,
diff --git a/package-lock.json b/package-lock.json
index 646ab0d..47ea74b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@tisoap/react-flow-smart-edge": "^3.0.0",
+ "html-to-image": "^1.11.11",
"reactflow": "^11.10.2",
"zustand": "^4.4.7"
},
@@ -8927,6 +8928,11 @@
"node": ">=12"
}
},
+ "node_modules/html-to-image": {
+ "version": "1.11.11",
+ "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
+ "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA=="
+ },
"node_modules/html-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
diff --git a/package.json b/package.json
index a1dd0a3..566d37c 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
},
"dependencies": {
"@tisoap/react-flow-smart-edge": "^3.0.0",
+ "html-to-image": "^1.11.11",
"reactflow": "^11.10.2",
"zustand": "^4.4.7"
},