Parse diagram to json

This commit is contained in:
1ilit 2023-09-19 15:49:20 +03:00
parent 70df6dd2a9
commit 974c254e48
3 changed files with 201 additions and 58 deletions

19
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.5.0", "@codemirror/lang-sql": "^6.5.0",
"@douyinfe/semi-ui": "^2.36.0", "@douyinfe/semi-ui": "^2.36.0",
"@lezer/highlight": "^1.1.5", "@lezer/highlight": "^1.1.5",
@ -2152,6 +2153,15 @@
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0"
}
},
"node_modules/@codemirror/lang-sql": { "node_modules/@codemirror/lang-sql": {
"version": "6.5.2", "version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.5.2.tgz",
@ -3721,6 +3731,15 @@
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@lezer/json": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.1.tgz",
"integrity": "sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw==",
"dependencies": {
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.5.0", "@codemirror/lang-sql": "^6.5.0",
"@douyinfe/semi-ui": "^2.36.0", "@douyinfe/semi-ui": "^2.36.0",
"@lezer/highlight": "^1.1.5", "@lezer/highlight": "^1.1.5",

View File

@ -22,22 +22,40 @@ import {
Image, Image,
Modal, Modal,
Spin, Spin,
Input,
} from "@douyinfe/semi-ui"; } from "@douyinfe/semi-ui";
import { toPng, toJpeg, toSvg } from "html-to-image"; import { toPng, toJpeg, toSvg } from "html-to-image";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { enterFullscreen, exitFullscreen } from "../utils"; import { enterFullscreen, exitFullscreen } from "../utils";
import { LayoutContext, SettingsContext } from "../pages/editor"; import {
AreaContext,
LayoutContext,
NoteContext,
SettingsContext,
TableContext,
} from "../pages/editor";
import { AddTable, AddArea, AddNote } from "./custom_icons"; import { AddTable, AddArea, AddNote } from "./custom_icons";
import { defaultTableTheme, defaultNoteTheme } from "../data/data";
import CodeMirror from "@uiw/react-codemirror";
import { json } from "@codemirror/lang-json";
export default function ControlPanel(props) { export default function ControlPanel(props) {
const [visible, setVisible] = useState(false); const MODAL = {
const [dataUrl, setDataUrl] = useState(""); NONE: 0,
const [filename, setFilename] = useState( IMG: 1,
`diagram_${new Date().toISOString()}` CODE: 2,
); };
const [extension, setExtension] = useState(""); const [visible, setVisible] = useState(MODAL.NONE);
const [exportData, setExportData] = useState({
data: "",
filename: `diagram_${new Date().toISOString()}`,
extension: "",
});
const { layout, setLayout } = useContext(LayoutContext); const { layout, setLayout } = useContext(LayoutContext);
const { setSettings } = useContext(SettingsContext); const { setSettings } = useContext(SettingsContext);
const { relationships, tables, setTables } = useContext(TableContext);
const { notes, setNotes } = useContext(NoteContext);
const { areas, setAreas } = useContext(AreaContext);
const menu = { const menu = {
File: { File: {
@ -74,37 +92,66 @@ export default function ControlPanel(props) {
{ {
PNG: () => { PNG: () => {
toPng(document.getElementById("canvas")).then(function (dataUrl) { toPng(document.getElementById("canvas")).then(function (dataUrl) {
setDataUrl(dataUrl); setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "png",
}));
}); });
setVisible(true); setVisible(MODAL.IMG);
setExtension("png");
}, },
}, },
{ {
JPEG: () => { JPEG: () => {
toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then( toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then(
function (dataUrl) { function (dataUrl) {
setDataUrl(dataUrl); setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "jpeg",
}));
} }
); );
setVisible(true); setVisible(MODAL.IMG);
setExtension("jpeg"); },
},
{
JSON: () => {
setVisible(MODAL.CODE);
const result = JSON.stringify(
{
tables: tables,
relationships: relationships,
notes: notes,
"subject areas": areas,
},
null,
2
);
setExportData((prev) => ({
...prev,
data: result,
extension: "json",
}));
}, },
}, },
{ XML: () => {} },
{ {
SVG: () => { SVG: () => {
const filter = (node) => node.tagName !== "i"; const filter = (node) => node.tagName !== "i";
toSvg(document.getElementById("canvas"), { filter: filter }).then( toSvg(document.getElementById("canvas"), { filter: filter }).then(
function (dataUrl) { function (dataUrl) {
setDataUrl(dataUrl); setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "svg",
}));
} }
); );
setVisible(true); setVisible(MODAL.IMG);
setExtension("svg");
}, },
}, },
{ PDF: () => {} }, { IFRAME: () => {} },
], ],
function: () => {}, function: () => {},
}, },
@ -316,39 +363,94 @@ export default function ControlPanel(props) {
</button> </button>
<Divider layout="vertical" margin="8px" /> <Divider layout="vertical" margin="8px" />
<button <button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center justify-center" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Undo" title="Undo"
> >
<IconUndo size="large" /> <IconUndo size="large" />
</button> </button>
<button <button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center justify-center" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Redo" title="Redo"
> >
<IconRedo size="large" /> <IconRedo size="large" />
</button> </button>
<Divider layout="vertical" margin="8px" /> <Divider layout="vertical" margin="8px" />
<div <button
className="py-1 px-2 hover:bg-slate-200 rounded" className="flex items-center py-1 px-2 hover:bg-slate-200 rounded"
title="Add new table" title="Add new table"
onClick={() =>
setTables((prev) => [
...prev,
{
id: prev.length,
name: `table_${prev.length}`,
x: 0,
y: 0,
fields: [
{
name: "id",
type: "UUID",
default: "",
check: "",
primary: true,
unique: true,
notNull: true,
increment: true,
comment: "",
},
],
comment: "",
indices: [],
color: defaultTableTheme,
},
])
}
> >
<AddTable /> <AddTable />
</div> </button>
<div <button
className="py-1 px-2 hover:bg-slate-200 rounded" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Add subject area" title="Add subject area"
onClick={() =>
setAreas((prev) => [
...prev,
{
id: prev.length,
name: `area_${prev.length}`,
x: 0,
y: 0,
width: 200,
height: 200,
color: defaultTableTheme,
},
])
}
> >
<AddArea /> <AddArea />
</div> </button>
<div <button
className="py-1 px-2 hover:bg-slate-200 rounded" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Add new note" title="Add new note"
onClick={() =>
setNotes((prev) => [
...prev,
{
id: prev.length,
x: 0,
y: 0,
title: `note_${prev.length}`,
content: "",
color: defaultNoteTheme,
height: 88,
},
])
}
> >
<AddNote /> <AddNote />
</div> </button>
<Divider layout="vertical" margin="8px" /> <Divider layout="vertical" margin="8px" />
<button <button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center justify-center" className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
title="Save" title="Save"
> >
<IconSaveStroked size="extra-large" /> <IconSaveStroked size="extra-large" />
@ -364,50 +466,71 @@ export default function ControlPanel(props) {
onClick={(e) => onClick={(e) =>
setLayout((prev) => ({ ...prev, header: !prev.header })) setLayout((prev) => ({ ...prev, header: !prev.header }))
} }
className="flex items-center"
> >
{layout.header ? <IconChevronUp /> : <IconChevronDown />} {layout.header ? <IconChevronUp /> : <IconChevronDown />}
</button> </button>
</div> </div>
<Modal <Modal
title="Export diagram" title="Export diagram"
visible={visible} visible={visible !== MODAL.NONE}
onOk={() => saveAs(dataUrl, `${filename}.${extension}`)} onOk={() => {
afterClose={() => { if (visible === MODAL.IMG) {
setFilename(`diagram_${new Date().toISOString()}`); saveAs(
setDataUrl(""); exportData.data,
`${exportData.filename}.${exportData.extension}`
);
} else if (visible === MODAL.CODE) {
const blob = new Blob([exportData.data], {
type: "text/plain;charset=utf-8",
});
saveAs(blob, `${exportData.filename}.${exportData.extension}`);
}
}} }}
onCancel={() => setVisible(false)} afterClose={() => {
setExportData((prev) => ({
data: "",
extension: "",
filename: `diagram_${new Date().toISOString()}`,
}));
}}
onCancel={() => setVisible(MODAL.NONE)}
centered centered
closeOnEsc={true} closeOnEsc={true}
okText="Export" okText="Export"
cancelText="Cancel" cancelText="Cancel"
width={470} width={520}
> >
{dataUrl !== "" || dataUrl ? ( {exportData.data !== "" || exportData.data ? (
<Image src={dataUrl} alt="Diagram" width={420}></Image> visible === MODAL.IMG ? (
<Image src={exportData.data} alt="Diagram" height={220} />
) : (
<div className="max-h-[400px] overflow-auto border border-gray-200">
<CodeMirror
value={exportData.data}
extensions={[json()]}
style={{
width: "100%",
height: "100%",
}}
/>
</div>
)
) : ( ) : (
<div className="text-center my-3"> <div className="text-center my-3">
<Spin tip="Loading..." size="large"></Spin> <Spin tip="Loading..." size="large" />
</div> </div>
)} )}
<Form <div className="text-sm font-semibold mt-2">Filename : </div>
labelPosition="left" <Input
labelAlign="right" value={exportData.filename}
onChange={(value) => { placeholder="Filename"
setFilename((prev) => suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
value.values["filename"] !== undefined onChange={(value) =>
? value.values["filename"] setExportData((prev) => ({ ...prev, filename: value }))
: prev }
); field="filename"
}} />
>
<Form.Input
field="filename"
label="Filename"
suffix={<div className="p-2">{`.${extension}`}</div>}
initValue={filename}
/>
</Form>
</Modal> </Modal>
</div> </div>
); );