drawDB/src/components/control_panel.jsx

690 lines
20 KiB
React
Raw Normal View History

2023-09-19 20:47:16 +08:00
import { React, useState } from "react";
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: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: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:46:48 +08:00
2023-09-19 20:48:34 +08:00
export default function ControlPanel(props) {
2023-09-19 20:48:26 +08:00
const [visible, setVisible] = useState(false);
const [dataUrl, setDataUrl] = useState("");
const [filename, setFilename] = useState(
`diagram_${new Date().toISOString()}`
);
2023-09-19 20:48:27 +08:00
const [extension, setExtension] = useState("");
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) {
setDataUrl(dataUrl);
});
setVisible(true);
2023-09-19 20:48:27 +08:00
setExtension("png");
},
},
{
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) {
setDataUrl(dataUrl);
}
);
setVisible(true);
setExtension("jpeg");
2023-09-19 20:48:26 +08:00
},
},
2023-09-19 20:48:28 +08:00
{ XML: () => {} },
{
SVG: () => {
const filter = (node) => node.tagName !== "i";
toSvg(document.getElementById("canvas"), { filter: filter }).then(
function (dataUrl) {
setDataUrl(dataUrl);
}
);
setVisible(true);
setExtension("svg");
},
},
{ 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: [],
function: () => {},
},
"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:35 +08:00
{props.layout.header && header()}
2023-09-19 20:48:38 +08:00
<div className="p-2 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:48:38 +08:00
<div className="py-1 px-2 hover:bg-slate-200 rounded flex">
<div>zoom</div>
<IconCaretdown />
2023-09-19 20:48:22 +08:00
</div>
</Dropdown>
2023-09-19 20:48:29 +08:00
<button
2023-09-19 20:48:32 +08:00
className="py-1 px-2 hover:bg-slate-200 rounded"
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:48:32 +08:00
className="py-1 px-2 hover:bg-slate-200 rounded"
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:48:32 +08:00
<button className="py-1 px-2 hover:bg-slate-200 rounded" title="Undo">
2023-09-19 20:47:16 +08:00
<i className="fa-solid fa-rotate-left "></i>
</button>
2023-09-19 20:48:32 +08:00
<button className="py-1 px-2 hover:bg-slate-200 rounded" title="Redo">
2023-09-19 20:47:16 +08:00
<i className="fa-solid fa-rotate-right"></i>
</button>
2023-09-19 20:48:22 +08:00
<Divider layout="vertical" margin="8px" />
<Dropdown
position="bottomLeft"
style={{ width: "180px" }}
render={
<Dropdown.Menu>
<Dropdown.Item>Table</Dropdown.Item>
<Dropdown.Item>Note</Dropdown.Item>
<Dropdown.Item>Subject area</Dropdown.Item>
<Dropdown.Item>Text</Dropdown.Item>
</Dropdown.Menu>
}
trigger="click"
>
2023-09-19 20:48:38 +08:00
<div className="py-1 px-2 hover:bg-slate-200 flex">
<i className="fa-solid fa-plus"></i>
<IconCaretdown />
2023-09-19 20:48:22 +08:00
</div>
</Dropdown>
2023-09-19 20:48:32 +08:00
<button className="py-1 px-2 hover:bg-slate-200 rounded" title="Edit">
2023-09-19 20:47:16 +08:00
<i className="fa-solid fa-pen-to-square"></i>
</button>
2023-09-19 20:48:29 +08:00
<button
2023-09-19 20:48:32 +08:00
className="py-1 px-2 hover:bg-slate-200 rounded"
2023-09-19 20:48:29 +08:00
title="Delete"
>
2023-09-19 20:47:16 +08:00
<i className="fa-solid fa-trash"></i>
</button>
2023-09-19 20:48:22 +08:00
<Divider layout="vertical" margin="8px" />
2023-09-19 20:48:32 +08:00
<button className="py-1 px-2 hover:bg-slate-200 rounded" title="Save">
2023-09-19 20:48:22 +08:00
<i className="fa-regular fa-floppy-disk"></i>
2023-09-19 20:47:16 +08:00
</button>
2023-09-19 20:48:29 +08:00
<button
2023-09-19 20:48:32 +08:00
className="py-1 px-2 hover:bg-slate-200 rounded"
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) =>
props.setLayout((prev) => ({ ...prev, header: !prev.header }))
}
>
{props.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"
visible={visible}
2023-09-19 20:48:27 +08:00
onOk={() => saveAs(dataUrl, `${filename}.${extension}`)}
afterClose={() => {
setFilename(`diagram_${new Date().toISOString()}`);
setDataUrl("");
}}
2023-09-19 20:48:26 +08:00
onCancel={() => setVisible(false)}
centered
closeOnEsc={true}
okText="Export"
cancelText="Cancel"
width={470}
>
<Image src={dataUrl} alt="Diagram" width={420}></Image>
<Form
labelPosition="left"
labelAlign="right"
onChange={(value) => {
2023-09-19 20:48:27 +08:00
setFilename((prev) =>
value.values["filename"] !== undefined
? value.values["filename"]
: prev
);
2023-09-19 20:48:26 +08:00
}}
>
<Form.Input
field="filename"
label="Filename"
2023-09-19 20:48:27 +08:00
suffix={<div className="p-2">{`.${extension}`}</div>}
2023-09-19 20:48:26 +08:00
initValue={filename}
/>
</Form>
</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={
props.layout.header ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
header: !prev.header,
}))
}
>
Header
</Dropdown.Item>
<Dropdown
position={"rightTop"}
render={
<Dropdown.Menu>
<Dropdown.Item
icon={
props.layout.tables ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
tables: !prev.tables,
}))
}
>
Tables
</Dropdown.Item>
<Dropdown.Item
icon={
props.layout.relationships ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
relationships: !prev.relationships,
}))
}
>
Relationships
</Dropdown.Item>
<Dropdown.Item
icon={
props.layout.issues ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
issues: !prev.issues,
}))
}
>
Issues
</Dropdown.Item>
<Dropdown.Item
icon={
props.layout.editor ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
editor: !prev.editor,
}))
}
>
Editor
</Dropdown.Item>
<Dropdown.Item
icon={
props.layout.shapes ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
shapes: !prev.shapes,
}))
}
>
Shapes
</Dropdown.Item>
</Dropdown.Menu>
}
>
<Dropdown.Item
icon={
props.layout.sidebar ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
sidebar: !prev.sidebar,
}))
}
>
Sidebar
</Dropdown.Item>
</Dropdown>
<Dropdown.Item
icon={
props.layout.services ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() =>
props.setLayout((prev) => ({
...prev,
services: !prev.services,
}))
}
>
Services
</Dropdown.Item>
<Dropdown.Divider />
2023-09-19 20:48:35 +08:00
<Dropdown.Item
icon={
props.layout.fullscreen ? (
<IconCheckboxTick />
) : (
<div className="px-2"></div>
)
}
onClick={() => {
if (props.layout.fullscreen) {
exitFullscreen();
} else {
enterFullscreen();
}
props.setLayout((prev) => ({
...prev,
fullscreen: !prev.fullscreen,
}));
}}
>
2023-09-19 20:48:34 +08:00
Fullscreen
</Dropdown.Item>
</Dropdown.Menu>
}
trigger="click"
>
2023-09-19 20:48:38 +08:00
<div className="py-1 px-2 hover:bg-slate-200 rounded flex items-center align-middle">
2023-09-19 20:48:34 +08:00
<i className="fa-solid fa-table-list"></i> <IconCaretdown />
</div>
</Dropdown>
);
}
2023-09-19 20:46:48 +08:00
}