2023-09-19 20:48:46 +08:00
|
|
|
import { React, useContext, useState } from "react";
|
2023-09-19 20:47:16 +08:00
|
|
|
import {
|
2023-09-19 20:48:20 +08:00
|
|
|
IconCaretdown,
|
|
|
|
IconChevronRight,
|
|
|
|
IconShareStroked,
|
|
|
|
IconChevronUp,
|
|
|
|
IconChevronDown,
|
2023-09-19 20:48:22 +08:00
|
|
|
IconCheckboxTick,
|
2023-09-19 20:49:18 +08:00
|
|
|
IconSaveStroked,
|
|
|
|
IconUndo,
|
|
|
|
IconRedo,
|
2023-09-19 20:48:20 +08:00
|
|
|
} from "@douyinfe/semi-icons";
|
|
|
|
import { Link } from "react-router-dom";
|
|
|
|
import icon from "../assets/icon_dark_64.png";
|
|
|
|
import {
|
|
|
|
Avatar,
|
|
|
|
AvatarGroup,
|
|
|
|
Button,
|
|
|
|
Divider,
|
|
|
|
Dropdown,
|
2023-09-19 20:48:22 +08:00
|
|
|
Form,
|
2023-09-19 20:48:26 +08:00
|
|
|
Image,
|
|
|
|
Modal,
|
2023-09-19 20:48:53 +08:00
|
|
|
Spin,
|
2023-09-19 20:49:20 +08:00
|
|
|
Input,
|
2023-09-19 20:48:20 +08:00
|
|
|
} from "@douyinfe/semi-ui";
|
2023-09-19 20:48:28 +08:00
|
|
|
import { toPng, toJpeg, toSvg } from "html-to-image";
|
2023-09-19 20:48:26 +08:00
|
|
|
import { saveAs } from "file-saver";
|
2023-09-19 20:48:35 +08:00
|
|
|
import { enterFullscreen, exitFullscreen } from "../utils";
|
2023-09-19 20:49:20 +08:00
|
|
|
import {
|
|
|
|
AreaContext,
|
|
|
|
LayoutContext,
|
|
|
|
NoteContext,
|
|
|
|
SettingsContext,
|
|
|
|
TableContext,
|
|
|
|
} from "../pages/editor";
|
2023-09-19 20:49:18 +08:00
|
|
|
import { AddTable, AddArea, AddNote } from "./custom_icons";
|
2023-09-19 20:49:20 +08:00
|
|
|
import { defaultTableTheme, defaultNoteTheme } from "../data/data";
|
|
|
|
import CodeMirror from "@uiw/react-codemirror";
|
|
|
|
import { json } from "@codemirror/lang-json";
|
2023-09-19 20:49:24 +08:00
|
|
|
import jsPDF from "jspdf";
|
2023-09-19 20:46:48 +08:00
|
|
|
|
2023-09-19 20:48:34 +08:00
|
|
|
export default function ControlPanel(props) {
|
2023-09-19 20:49:20 +08:00
|
|
|
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: "",
|
|
|
|
});
|
2023-09-19 20:48:53 +08:00
|
|
|
const { layout, setLayout } = useContext(LayoutContext);
|
2023-09-19 20:49:14 +08:00
|
|
|
const { setSettings } = useContext(SettingsContext);
|
2023-09-19 20:49:20 +08:00
|
|
|
const { relationships, tables, setTables } = useContext(TableContext);
|
|
|
|
const { notes, setNotes } = useContext(NoteContext);
|
|
|
|
const { areas, setAreas } = useContext(AreaContext);
|
2023-09-19 20:48:26 +08:00
|
|
|
|
|
|
|
const menu = {
|
|
|
|
File: {
|
|
|
|
New: {
|
|
|
|
children: [],
|
|
|
|
function: () => console.log("New"),
|
|
|
|
},
|
|
|
|
"New window": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Save: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Save as": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Share: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Rename: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Import: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Export as": {
|
|
|
|
children: [
|
|
|
|
{
|
2023-09-19 20:48:28 +08:00
|
|
|
PNG: () => {
|
2023-09-19 20:48:26 +08:00
|
|
|
toPng(document.getElementById("canvas")).then(function (dataUrl) {
|
2023-09-19 20:49:20 +08:00
|
|
|
setExportData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
data: dataUrl,
|
|
|
|
extension: "png",
|
|
|
|
}));
|
2023-09-19 20:48:26 +08:00
|
|
|
});
|
2023-09-19 20:49:20 +08:00
|
|
|
setVisible(MODAL.IMG);
|
2023-09-19 20:48:27 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2023-09-19 20:48:28 +08:00
|
|
|
JPEG: () => {
|
2023-09-19 20:48:27 +08:00
|
|
|
toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then(
|
|
|
|
function (dataUrl) {
|
2023-09-19 20:49:20 +08:00
|
|
|
setExportData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
data: dataUrl,
|
|
|
|
extension: "jpeg",
|
|
|
|
}));
|
2023-09-19 20:48:27 +08:00
|
|
|
}
|
|
|
|
);
|
2023-09-19 20:49:20 +08:00
|
|
|
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",
|
|
|
|
}));
|
2023-09-19 20:48:26 +08:00
|
|
|
},
|
|
|
|
},
|
2023-09-19 20:48:28 +08:00
|
|
|
{
|
|
|
|
SVG: () => {
|
|
|
|
const filter = (node) => node.tagName !== "i";
|
|
|
|
toSvg(document.getElementById("canvas"), { filter: filter }).then(
|
|
|
|
function (dataUrl) {
|
2023-09-19 20:49:20 +08:00
|
|
|
setExportData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
data: dataUrl,
|
|
|
|
extension: "svg",
|
|
|
|
}));
|
2023-09-19 20:48:28 +08:00
|
|
|
}
|
|
|
|
);
|
2023-09-19 20:49:20 +08:00
|
|
|
setVisible(MODAL.IMG);
|
2023-09-19 20:48:28 +08:00
|
|
|
},
|
|
|
|
},
|
2023-09-19 20:49:24 +08:00
|
|
|
{
|
|
|
|
PDF: () => {
|
|
|
|
const canvas = document.getElementById("canvas");
|
|
|
|
toJpeg(canvas).then(
|
|
|
|
function (dataUrl) {
|
|
|
|
const doc = new jsPDF("l", "px", [
|
|
|
|
canvas.offsetWidth,
|
|
|
|
canvas.offsetHeight,
|
|
|
|
]);
|
|
|
|
doc.addImage(
|
|
|
|
dataUrl,
|
|
|
|
"jpeg",
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
canvas.offsetWidth,
|
|
|
|
canvas.offsetHeight
|
|
|
|
);
|
|
|
|
doc.save(`${exportData.filename}.pdf`);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
2023-09-19 20:48:26 +08:00
|
|
|
],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Export source": {
|
|
|
|
children: [
|
|
|
|
{ MySQL: () => {} },
|
|
|
|
{ PostgreSQL: () => {} },
|
|
|
|
{ DBML: () => {} },
|
|
|
|
],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Properties: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Close: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Edit: {
|
|
|
|
Undo: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Redo: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Cut: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Copy: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Copy as image": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Paste: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Delete: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Edit table": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
View: {
|
|
|
|
Toolbar: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Grid: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Sidebar: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Editor: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Strict mode": {
|
|
|
|
children: [],
|
2023-09-19 20:49:14 +08:00
|
|
|
function: () => {
|
|
|
|
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
|
|
|
|
},
|
2023-09-19 20:48:26 +08:00
|
|
|
},
|
2023-09-19 20:49:16 +08:00
|
|
|
"Field summary": {
|
|
|
|
children: [],
|
|
|
|
function: () => {
|
2023-09-19 20:49:18 +08:00
|
|
|
setSettings((prev) => ({
|
|
|
|
...prev,
|
|
|
|
showFieldSummary: !prev.showFieldSummary,
|
|
|
|
}));
|
2023-09-19 20:49:16 +08:00
|
|
|
},
|
|
|
|
},
|
2023-09-19 20:48:26 +08:00
|
|
|
"Reset view": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"View schema": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Theme: {
|
|
|
|
children: [{ Light: () => {} }, { Dark: () => {} }],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Zoom in": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Zoom out": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
Fullscreen: {
|
|
|
|
children: [],
|
2023-09-19 20:48:35 +08:00
|
|
|
function: enterFullscreen,
|
2023-09-19 20:48:26 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Logs: {
|
|
|
|
"Open logs": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Commit changes": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Revert changes": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"View commits": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Help: {
|
|
|
|
Shortcuts: {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Ask us on discord": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Tweet us": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
"Found a bug": {
|
|
|
|
children: [],
|
|
|
|
function: () => {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-09-19 20:46:48 +08:00
|
|
|
return (
|
2023-09-19 20:48:38 +08:00
|
|
|
<div>
|
2023-09-19 20:48:46 +08:00
|
|
|
{layout.header && header()}
|
2023-09-19 20:49:18 +08:00
|
|
|
<div className="py-1 px-5 flex justify-between items-center rounded-xl bg-slate-100 my-1 sm:mx-1 md:mx-6 text-slate-700 select-none overflow-x-hidden">
|
2023-09-19 20:48:20 +08:00
|
|
|
<div className="flex justify-start items-center">
|
2023-09-19 20:48:34 +08:00
|
|
|
{layoutDropdown()}
|
2023-09-19 20:48:22 +08:00
|
|
|
<Divider layout="vertical" margin="8px" />
|
|
|
|
<Dropdown
|
|
|
|
style={{ width: "180px" }}
|
|
|
|
position="bottomLeft"
|
|
|
|
render={
|
|
|
|
<Dropdown.Menu>
|
|
|
|
<Dropdown.Item>Fit window</Dropdown.Item>
|
|
|
|
<Dropdown.Divider />
|
|
|
|
{[
|
|
|
|
"25%",
|
|
|
|
"50%",
|
|
|
|
"75%",
|
|
|
|
"100%",
|
|
|
|
"125%",
|
|
|
|
"150%",
|
|
|
|
"200%",
|
|
|
|
"300%",
|
|
|
|
].map((e, i) => (
|
|
|
|
<Dropdown.Item key={i}>{e}</Dropdown.Item>
|
|
|
|
))}
|
|
|
|
<Dropdown.Divider />
|
|
|
|
<Dropdown.Item>
|
|
|
|
<Form>
|
|
|
|
<Form.InputNumber
|
|
|
|
field="zoom"
|
|
|
|
label="Custom zoom"
|
|
|
|
placeholder="Zoom"
|
|
|
|
suffix={<div className="p-1">%</div>}
|
|
|
|
/>
|
|
|
|
</Form>
|
|
|
|
</Dropdown.Item>
|
|
|
|
</Dropdown.Menu>
|
|
|
|
}
|
|
|
|
trigger="click"
|
|
|
|
>
|
2023-09-19 20:49:18 +08:00
|
|
|
<div className="py-1 px-2 hover:bg-slate-200 rounded flex items-center justify-center">
|
2023-09-19 20:48:38 +08:00
|
|
|
<div>zoom</div>
|
2023-09-19 20:49:18 +08:00
|
|
|
<div>
|
|
|
|
<IconCaretdown />
|
|
|
|
</div>
|
2023-09-19 20:48:22 +08:00
|
|
|
</div>
|
|
|
|
</Dropdown>
|
2023-09-19 20:48:29 +08:00
|
|
|
<button
|
2023-09-19 20:49:18 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded text-lg"
|
2023-09-19 20:48:29 +08:00
|
|
|
title="Zoom in"
|
|
|
|
>
|
2023-09-19 20:47:16 +08:00
|
|
|
<i className="fa-solid fa-magnifying-glass-plus"></i>
|
|
|
|
</button>
|
2023-09-19 20:48:29 +08:00
|
|
|
<button
|
2023-09-19 20:49:18 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded text-lg"
|
2023-09-19 20:48:29 +08:00
|
|
|
title="Zoom out"
|
|
|
|
>
|
2023-09-19 20:47:16 +08:00
|
|
|
<i className="fa-solid fa-magnifying-glass-minus"></i>
|
|
|
|
</button>
|
2023-09-19 20:48:22 +08:00
|
|
|
<Divider layout="vertical" margin="8px" />
|
2023-09-19 20:49:18 +08:00
|
|
|
<button
|
2023-09-19 20:49:20 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Undo"
|
|
|
|
>
|
|
|
|
<IconUndo size="large" />
|
2023-09-19 20:47:16 +08:00
|
|
|
</button>
|
2023-09-19 20:49:18 +08:00
|
|
|
<button
|
2023-09-19 20:49:20 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Redo"
|
|
|
|
>
|
|
|
|
<IconRedo size="large" />
|
2023-09-19 20:47:16 +08:00
|
|
|
</button>
|
2023-09-19 20:48:22 +08:00
|
|
|
<Divider layout="vertical" margin="8px" />
|
2023-09-19 20:49:20 +08:00
|
|
|
<button
|
|
|
|
className="flex items-center py-1 px-2 hover:bg-slate-200 rounded"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Add new table"
|
2023-09-19 20:49:20 +08:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
2023-09-19 20:48:22 +08:00
|
|
|
>
|
2023-09-19 20:49:18 +08:00
|
|
|
<AddTable />
|
2023-09-19 20:49:20 +08:00
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Add subject area"
|
2023-09-19 20:49:20 +08:00
|
|
|
onClick={() =>
|
|
|
|
setAreas((prev) => [
|
|
|
|
...prev,
|
|
|
|
{
|
|
|
|
id: prev.length,
|
|
|
|
name: `area_${prev.length}`,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: 200,
|
|
|
|
height: 200,
|
|
|
|
color: defaultTableTheme,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
2023-09-19 20:48:29 +08:00
|
|
|
>
|
2023-09-19 20:49:18 +08:00
|
|
|
<AddArea />
|
2023-09-19 20:49:20 +08:00
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Add new note"
|
2023-09-19 20:49:20 +08:00
|
|
|
onClick={() =>
|
|
|
|
setNotes((prev) => [
|
|
|
|
...prev,
|
|
|
|
{
|
|
|
|
id: prev.length,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
title: `note_${prev.length}`,
|
|
|
|
content: "",
|
|
|
|
color: defaultNoteTheme,
|
|
|
|
height: 88,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
2023-09-19 20:49:18 +08:00
|
|
|
>
|
|
|
|
<AddNote />
|
2023-09-19 20:49:20 +08:00
|
|
|
</button>
|
2023-09-19 20:48:22 +08:00
|
|
|
<Divider layout="vertical" margin="8px" />
|
2023-09-19 20:49:18 +08:00
|
|
|
<button
|
2023-09-19 20:49:20 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded flex items-center"
|
2023-09-19 20:49:18 +08:00
|
|
|
title="Save"
|
|
|
|
>
|
|
|
|
<IconSaveStroked size="extra-large" />
|
2023-09-19 20:47:16 +08:00
|
|
|
</button>
|
2023-09-19 20:48:29 +08:00
|
|
|
<button
|
2023-09-19 20:49:18 +08:00
|
|
|
className="py-1 px-2 hover:bg-slate-200 rounded text-xl"
|
2023-09-19 20:48:29 +08:00
|
|
|
title="Commit"
|
|
|
|
>
|
2023-09-19 20:48:22 +08:00
|
|
|
<i className="fa-solid fa-code-branch"></i>
|
2023-09-19 20:47:16 +08:00
|
|
|
</button>
|
|
|
|
</div>
|
2023-09-19 20:48:34 +08:00
|
|
|
<button
|
|
|
|
onClick={(e) =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({ ...prev, header: !prev.header }))
|
2023-09-19 20:48:34 +08:00
|
|
|
}
|
2023-09-19 20:49:20 +08:00
|
|
|
className="flex items-center"
|
2023-09-19 20:48:34 +08:00
|
|
|
>
|
2023-09-19 20:48:46 +08:00
|
|
|
{layout.header ? <IconChevronUp /> : <IconChevronDown />}
|
2023-09-19 20:48:20 +08:00
|
|
|
</button>
|
|
|
|
</div>
|
2023-09-19 20:48:26 +08:00
|
|
|
<Modal
|
|
|
|
title="Export diagram"
|
2023-09-19 20:49:20 +08:00
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
}}
|
2023-09-19 20:48:27 +08:00
|
|
|
afterClose={() => {
|
2023-09-19 20:49:20 +08:00
|
|
|
setExportData((prev) => ({
|
|
|
|
data: "",
|
|
|
|
extension: "",
|
|
|
|
filename: `diagram_${new Date().toISOString()}`,
|
|
|
|
}));
|
2023-09-19 20:48:27 +08:00
|
|
|
}}
|
2023-09-19 20:49:20 +08:00
|
|
|
onCancel={() => setVisible(MODAL.NONE)}
|
2023-09-19 20:48:26 +08:00
|
|
|
centered
|
|
|
|
closeOnEsc={true}
|
|
|
|
okText="Export"
|
|
|
|
cancelText="Cancel"
|
2023-09-19 20:49:20 +08:00
|
|
|
width={520}
|
2023-09-19 20:48:26 +08:00
|
|
|
>
|
2023-09-19 20:49:20 +08:00
|
|
|
{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>
|
|
|
|
)
|
2023-09-19 20:48:53 +08:00
|
|
|
) : (
|
|
|
|
<div className="text-center my-3">
|
2023-09-19 20:49:20 +08:00
|
|
|
<Spin tip="Loading..." size="large" />
|
2023-09-19 20:48:53 +08:00
|
|
|
</div>
|
|
|
|
)}
|
2023-09-19 20:49:20 +08:00
|
|
|
<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"
|
|
|
|
/>
|
2023-09-19 20:48:26 +08:00
|
|
|
</Modal>
|
2023-09-19 20:48:38 +08:00
|
|
|
</div>
|
2023-09-19 20:46:48 +08:00
|
|
|
);
|
2023-09-19 20:48:34 +08:00
|
|
|
|
|
|
|
function header() {
|
2023-09-19 20:48:35 +08:00
|
|
|
return (
|
|
|
|
<nav className="flex justify-between pt-1 items-center whitespace-nowrap">
|
|
|
|
<div className="flex justify-start items-center text-slate-800">
|
|
|
|
<Link to="/">
|
|
|
|
<img
|
|
|
|
width={54}
|
|
|
|
src={icon}
|
|
|
|
alt="logo"
|
|
|
|
className="ms-8 min-w-[54px]"
|
|
|
|
/>
|
|
|
|
</Link>
|
|
|
|
<div className="ms-1 mt-1">
|
|
|
|
<div className="text-xl ms-3">Project1 / Untitled</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<div className="flex justify-start text-md select-none me-2">
|
|
|
|
{Object.keys(menu).map((category) => (
|
|
|
|
<Dropdown
|
|
|
|
key={category}
|
|
|
|
position="bottomLeft"
|
|
|
|
style={{ width: "200px" }}
|
|
|
|
render={
|
|
|
|
<Dropdown.Menu>
|
|
|
|
{Object.keys(menu[category]).map((item, index) => {
|
|
|
|
if (menu[category][item].children.length > 0) {
|
|
|
|
return (
|
|
|
|
<Dropdown
|
|
|
|
style={{ width: "120px" }}
|
|
|
|
key={item}
|
|
|
|
position={"rightTop"}
|
|
|
|
render={
|
|
|
|
<Dropdown.Menu>
|
|
|
|
{menu[category][item].children.map(
|
|
|
|
(e, i) => (
|
|
|
|
<Dropdown.Item
|
|
|
|
key={i}
|
|
|
|
onClick={Object.values(e)[0]}
|
|
|
|
>
|
|
|
|
{Object.keys(e)[0]}
|
|
|
|
</Dropdown.Item>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</Dropdown.Menu>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<Dropdown.Item
|
|
|
|
style={{
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "space-between",
|
|
|
|
alignItems: "center",
|
|
|
|
}}
|
|
|
|
onClick={menu[category][item].function}
|
|
|
|
>
|
|
|
|
{item}
|
|
|
|
<IconChevronRight />
|
|
|
|
</Dropdown.Item>
|
|
|
|
</Dropdown>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
2023-09-19 20:48:34 +08:00
|
|
|
<Dropdown.Item
|
2023-09-19 20:48:35 +08:00
|
|
|
key={index}
|
2023-09-19 20:48:34 +08:00
|
|
|
onClick={menu[category][item].function}
|
|
|
|
>
|
|
|
|
{item}
|
|
|
|
</Dropdown.Item>
|
2023-09-19 20:48:35 +08:00
|
|
|
);
|
|
|
|
})}
|
|
|
|
</Dropdown.Menu>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div className="px-3 py-1 hover:bg-gray-100 rounded">
|
|
|
|
{category}
|
|
|
|
</div>
|
|
|
|
</Dropdown>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
<Button size="small" type="tertiary">
|
|
|
|
Last saved {new Date().toISOString()}
|
|
|
|
</Button>
|
2023-09-19 20:48:34 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-09-19 20:48:35 +08:00
|
|
|
<div className="flex justify-around items-center text-md me-8">
|
|
|
|
<AvatarGroup maxCount={3} size="default">
|
|
|
|
<Avatar color="red" alt="Lisa LeBlanc">
|
|
|
|
LL
|
|
|
|
</Avatar>
|
|
|
|
<Avatar color="green" alt="Caroline Xiao">
|
|
|
|
CX
|
|
|
|
</Avatar>
|
|
|
|
<Avatar color="amber" alt="Rafal Matin">
|
|
|
|
RM
|
|
|
|
</Avatar>
|
|
|
|
<Avatar alt="Zank Lance">ZL</Avatar>
|
|
|
|
<Avatar alt="Youself Zhang">YZ</Avatar>
|
|
|
|
</AvatarGroup>
|
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
style={{
|
|
|
|
fontSize: "16px",
|
|
|
|
marginLeft: "12px",
|
|
|
|
marginRight: "12px",
|
|
|
|
}}
|
|
|
|
size="large"
|
|
|
|
icon={<IconShareStroked />}
|
|
|
|
>
|
|
|
|
Share
|
|
|
|
</Button>
|
|
|
|
<Avatar size="default" alt="Buni Zhang">
|
|
|
|
BZ
|
2023-09-19 20:48:34 +08:00
|
|
|
</Avatar>
|
2023-09-19 20:48:35 +08:00
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
);
|
2023-09-19 20:48:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function layoutDropdown() {
|
|
|
|
return (
|
|
|
|
<Dropdown
|
|
|
|
position="bottomLeft"
|
|
|
|
style={{ width: "180px" }}
|
|
|
|
render={
|
|
|
|
<Dropdown.Menu>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.header ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
header: !prev.header,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Header
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown
|
|
|
|
position={"rightTop"}
|
|
|
|
render={
|
|
|
|
<Dropdown.Menu>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.tables ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
tables: !prev.tables,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Tables
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.relationships ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
relationships: !prev.relationships,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Relationships
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.issues ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
issues: !prev.issues,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Issues
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:55 +08:00
|
|
|
layout.areas ? (
|
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
|
|
|
setLayout((prev) => ({
|
|
|
|
...prev,
|
|
|
|
areas: !prev.areas,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Subject areas
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.editor ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
editor: !prev.editor,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Editor
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:49:09 +08:00
|
|
|
layout.notes ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
2023-09-19 20:49:09 +08:00
|
|
|
notes: !prev.notes,
|
2023-09-19 20:48:34 +08:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
2023-09-19 20:49:09 +08:00
|
|
|
Notes
|
2023-09-19 20:48:34 +08:00
|
|
|
</Dropdown.Item>
|
|
|
|
</Dropdown.Menu>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.sidebar ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
sidebar: !prev.sidebar,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Sidebar
|
|
|
|
</Dropdown.Item>
|
|
|
|
</Dropdown>
|
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.services ? (
|
2023-09-19 20:48:34 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() =>
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:34 +08:00
|
|
|
...prev,
|
|
|
|
services: !prev.services,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Services
|
|
|
|
</Dropdown.Item>
|
|
|
|
<Dropdown.Divider />
|
2023-09-19 20:48:35 +08:00
|
|
|
<Dropdown.Item
|
|
|
|
icon={
|
2023-09-19 20:48:46 +08:00
|
|
|
layout.fullscreen ? (
|
2023-09-19 20:48:35 +08:00
|
|
|
<IconCheckboxTick />
|
|
|
|
) : (
|
|
|
|
<div className="px-2"></div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onClick={() => {
|
2023-09-19 20:48:46 +08:00
|
|
|
if (layout.fullscreen) {
|
2023-09-19 20:48:35 +08:00
|
|
|
exitFullscreen();
|
|
|
|
} else {
|
|
|
|
enterFullscreen();
|
|
|
|
}
|
2023-09-19 20:48:46 +08:00
|
|
|
setLayout((prev) => ({
|
2023-09-19 20:48:35 +08:00
|
|
|
...prev,
|
|
|
|
fullscreen: !prev.fullscreen,
|
|
|
|
}));
|
|
|
|
}}
|
|
|
|
>
|
2023-09-19 20:48:34 +08:00
|
|
|
Fullscreen
|
|
|
|
</Dropdown.Item>
|
|
|
|
</Dropdown.Menu>
|
|
|
|
}
|
|
|
|
trigger="click"
|
|
|
|
>
|
2023-09-19 20:49:18 +08:00
|
|
|
<div className="py-1 px-2 hover:bg-slate-200 rounded flex items-center justify-center">
|
|
|
|
<div>
|
|
|
|
<i className="fa-solid fa-table-list text-xl me-1"></i>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<IconCaretdown />
|
|
|
|
</div>
|
2023-09-19 20:48:34 +08:00
|
|
|
</div>
|
|
|
|
</Dropdown>
|
|
|
|
);
|
|
|
|
}
|
2023-09-19 20:46:48 +08:00
|
|
|
}
|