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",
"version": "0.1.0",
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.5.0",
"@douyinfe/semi-ui": "^2.36.0",
"@lezer/highlight": "^1.1.5",
@ -2152,6 +2153,15 @@
"@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": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.5.2.tgz",
@ -3721,6 +3731,15 @@
"@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": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz",

View File

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

View File

@ -22,22 +22,40 @@ import {
Image,
Modal,
Spin,
Input,
} from "@douyinfe/semi-ui";
import { toPng, toJpeg, toSvg } from "html-to-image";
import { saveAs } from "file-saver";
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 { defaultTableTheme, defaultNoteTheme } from "../data/data";
import CodeMirror from "@uiw/react-codemirror";
import { json } from "@codemirror/lang-json";
export default function ControlPanel(props) {
const [visible, setVisible] = useState(false);
const [dataUrl, setDataUrl] = useState("");
const [filename, setFilename] = useState(
`diagram_${new Date().toISOString()}`
);
const [extension, setExtension] = useState("");
const MODAL = {
NONE: 0,
IMG: 1,
CODE: 2,
};
const [visible, setVisible] = useState(MODAL.NONE);
const [exportData, setExportData] = useState({
data: "",
filename: `diagram_${new Date().toISOString()}`,
extension: "",
});
const { layout, setLayout } = useContext(LayoutContext);
const { setSettings } = useContext(SettingsContext);
const { relationships, tables, setTables } = useContext(TableContext);
const { notes, setNotes } = useContext(NoteContext);
const { areas, setAreas } = useContext(AreaContext);
const menu = {
File: {
@ -74,37 +92,66 @@ export default function ControlPanel(props) {
{
PNG: () => {
toPng(document.getElementById("canvas")).then(function (dataUrl) {
setDataUrl(dataUrl);
setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "png",
}));
});
setVisible(true);
setExtension("png");
setVisible(MODAL.IMG);
},
},
{
JPEG: () => {
toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then(
function (dataUrl) {
setDataUrl(dataUrl);
setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "jpeg",
}));
}
);
setVisible(true);
setExtension("jpeg");
setVisible(MODAL.IMG);
},
},
{
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: () => {
const filter = (node) => node.tagName !== "i";
toSvg(document.getElementById("canvas"), { filter: filter }).then(
function (dataUrl) {
setDataUrl(dataUrl);
setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "svg",
}));
}
);
setVisible(true);
setExtension("svg");
setVisible(MODAL.IMG);
},
},
{ PDF: () => {} },
{ IFRAME: () => {} },
],
function: () => {},
},
@ -316,39 +363,94 @@ export default function ControlPanel(props) {
</button>
<Divider layout="vertical" margin="8px" />
<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"
>
<IconUndo size="large" />
</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"
>
<IconRedo size="large" />
</button>
<Divider layout="vertical" margin="8px" />
<div
className="py-1 px-2 hover:bg-slate-200 rounded"
<button
className="flex items-center py-1 px-2 hover:bg-slate-200 rounded"
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 />
</div>
<div
className="py-1 px-2 hover:bg-slate-200 rounded"
</button>
<button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
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 />
</div>
<div
className="py-1 px-2 hover:bg-slate-200 rounded"
</button>
<button
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
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 />
</div>
</button>
<Divider layout="vertical" margin="8px" />
<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"
>
<IconSaveStroked size="extra-large" />
@ -364,50 +466,71 @@ export default function ControlPanel(props) {
onClick={(e) =>
setLayout((prev) => ({ ...prev, header: !prev.header }))
}
className="flex items-center"
>
{layout.header ? <IconChevronUp /> : <IconChevronDown />}
</button>
</div>
<Modal
title="Export diagram"
visible={visible}
onOk={() => saveAs(dataUrl, `${filename}.${extension}`)}
afterClose={() => {
setFilename(`diagram_${new Date().toISOString()}`);
setDataUrl("");
visible={visible !== MODAL.NONE}
onOk={() => {
if (visible === MODAL.IMG) {
saveAs(
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
closeOnEsc={true}
okText="Export"
cancelText="Cancel"
width={470}
width={520}
>
{dataUrl !== "" || dataUrl ? (
<Image src={dataUrl} alt="Diagram" width={420}></Image>
{exportData.data !== "" || exportData.data ? (
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">
<Spin tip="Loading..." size="large"></Spin>
<Spin tip="Loading..." size="large" />
</div>
)}
<Form
labelPosition="left"
labelAlign="right"
onChange={(value) => {
setFilename((prev) =>
value.values["filename"] !== undefined
? value.values["filename"]
: prev
);
}}
>
<Form.Input
field="filename"
label="Filename"
suffix={<div className="p-2">{`.${extension}`}</div>}
initValue={filename}
/>
</Form>
<div className="text-sm font-semibold mt-2">Filename : </div>
<Input
value={exportData.filename}
placeholder="Filename"
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
onChange={(value) =>
setExportData((prev) => ({ ...prev, filename: value }))
}
field="filename"
/>
</Modal>
</div>
);