export as image

dev
Shahriar 2024-01-20 01:06:55 +03:30
parent 8336ba449e
commit 18bc292879
5 changed files with 158 additions and 5 deletions

View File

@ -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 (
<ModalLayout onClose={() => setShowModal(false)}>
<ModalHeader>
<Typography fontWeight="bold" textColor="neutral800" as="h2" id="title">
Export Diagram
</Typography>
</ModalHeader>
<ModalBody>
<SingleSelect
label="Format"
onClear={() => {
setFormat(undefined);
}}
value={format}
onChange={setFormat}
>
<SingleSelectOption value="svg">SVG</SingleSelectOption>
<SingleSelectOption value="png">PNG</SingleSelectOption>
<SingleSelectOption value="jpeg">JPEG</SingleSelectOption>
</SingleSelect>
<span style={{ height: "16px", display: "block" }} />
{format == "jpeg" && (
<NumberInput
label="Quality"
name="quality"
hint="0.0 - 1.0"
onValueChange={(value) => setQuality(value)}
value={quality}
/>
)}
</ModalBody>
<ModalFooter
endActions={<Button onClick={exportDiagram}>Export</Button>}
/>
</ModalLayout>
);
}

View File

@ -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 (
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
<HeaderLayout
title="Content-Type Explorer"
primaryAction={
<Button variant="secondary" startIcon={<Download />}>
<Button
variant="secondary"
startIcon={<Download />}
onClick={() => setShowModal(true)}
>
Export Diagram
</Button>
}
secondaryAction={
<Button variant="primary" startIcon={<Refresh />} onClick={refresh}>
Draw Again
<Button variant="primary" startIcon={<Refresh />} onClick={regenrate}>
Regenrate
</Button>
}
/>
<OptionsBar />
<div
ref={ref}
style={{
height: "100%",
borderTop: `1px solid ${theme.colors.neutral150}`,
@ -141,6 +151,7 @@ const HomePage = () => {
color={getBackgroundColor(options.backgroundPattern, theme)}
/>
</ReactFlow>
{showModal && <ExportModal imageRef={ref} />}
</div>
</div>
);

View File

@ -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,

6
package-lock.json generated
View File

@ -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",

View File

@ -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"
},