drawDB/src/components/EditorHeader/ControlPanel.jsx

1609 lines
50 KiB
React
Raw Normal View History

2024-03-15 22:00:23 +08:00
import { useState } from "react";
2023-09-19 20:47:16 +08:00
import {
2023-09-19 20:48:20 +08:00
IconCaretdown,
IconChevronRight,
IconChevronUp,
IconChevronDown,
2023-09-19 20:49:18 +08:00
IconSaveStroked,
IconUndo,
IconRedo,
2023-10-22 00:45:13 +08:00
IconEdit,
2023-09-19 20:48:20 +08:00
} from "@douyinfe/semi-icons";
2024-03-15 02:30:22 +08:00
import { Link, useNavigate } from "react-router-dom";
2024-04-02 00:44:50 +08:00
import icon from "../../assets/icon_dark_64.png";
2023-09-19 20:48:20 +08:00
import {
Button,
Divider,
Dropdown,
2023-09-19 20:49:36 +08:00
InputNumber,
2023-09-19 20:51:18 +08:00
Tooltip,
2023-09-19 20:48:53 +08:00
Spin,
2023-09-19 20:49:37 +08:00
Toast,
2024-04-11 15:30:42 +08:00
Popconfirm,
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:49:28 +08:00
import {
2023-09-19 20:51:26 +08:00
jsonToMySQL,
jsonToPostgreSQL,
2024-02-05 19:40:59 +08:00
jsonToSQLite,
2024-02-06 06:42:11 +08:00
jsonToMariaDB,
2024-02-13 03:05:21 +08:00
jsonToSQLServer,
2024-06-10 20:23:57 +08:00
} from "../../utils/exportSQL/generic";
import {
ObjectType,
Action,
Tab,
State,
MODAL,
SIDESHEET,
2024-06-10 20:23:57 +08:00
DB,
} from "../../data/constants";
2023-09-19 20:49:24 +08:00
import jsPDF from "jspdf";
2023-09-19 20:50:22 +08:00
import { useHotkeys } from "react-hotkeys-hook";
2023-09-19 20:50:32 +08:00
import { Validator } from "jsonschema";
2024-04-02 00:44:50 +08:00
import { areaSchema, noteSchema, tableSchema } from "../../data/schemas";
import { db } from "../../data/db";
2024-04-05 10:12:50 +08:00
import {
useLayout,
useSettings,
useTransform,
useTables,
useUndoRedo,
useSelect,
} from "../../hooks";
import { enterFullscreen } from "../../utils/fullscreen";
2024-04-02 00:44:50 +08:00
import { dataURItoBlob } from "../../utils/utils";
import useAreas from "../../hooks/useAreas";
import useNotes from "../../hooks/useNotes";
import useTypes from "../../hooks/useTypes";
import useSaveState from "../../hooks/useSaveState";
2024-04-05 09:19:36 +08:00
import { IconAddArea, IconAddNote, IconAddTable } from "../../icons";
import LayoutDropdown from "./LayoutDropdown";
import Sidesheet from "./SideSheet/Sidesheet";
import Modal from "./Modal/Modal";
import { useTranslation } from "react-i18next";
2024-06-10 20:23:57 +08:00
import { exportSQL } from "../../utils/exportSQL";
2023-09-19 20:46:48 +08:00
export default function ControlPanel({
diagramId,
setDiagramId,
title,
setTitle,
2023-11-25 00:28:39 +08:00
lastSaved,
}) {
const [modal, setModal] = useState(MODAL.NONE);
2024-01-14 08:05:07 +08:00
const [sidesheet, setSidesheet] = useState(SIDESHEET.NONE);
2023-10-22 00:45:13 +08:00
const [prevTitle, setPrevTitle] = useState(title);
const [showEditName, setShowEditName] = useState(false);
2024-06-14 06:00:47 +08:00
const [importDb, setImportDb] = useState("");
2023-09-19 20:49:20 +08:00
const [exportData, setExportData] = useState({
2023-09-19 20:50:39 +08:00
data: null,
filename: `${title}_${new Date().toISOString()}`,
2023-09-19 20:49:20 +08:00
extension: "",
});
2024-03-15 22:00:23 +08:00
const { saveState, setSaveState } = useSaveState();
2024-03-10 02:35:04 +08:00
const { layout, setLayout } = useLayout();
2024-03-10 04:39:46 +08:00
const { settings, setSettings } = useSettings();
2023-09-19 20:49:57 +08:00
const {
relationships,
tables,
setTables,
addTable,
2023-09-19 20:50:15 +08:00
updateTable,
deleteField,
2023-09-19 20:49:57 +08:00
deleteTable,
2023-09-19 20:50:04 +08:00
updateField,
2023-09-19 20:49:57 +08:00
setRelationships,
addRelationship,
deleteRelationship,
2024-06-10 07:17:43 +08:00
database,
} = useTables();
2024-03-13 07:27:42 +08:00
const { types, addType, deleteType, updateType, setTypes } = useTypes();
const { notes, setNotes, updateNote, addNote, deleteNote } = useNotes();
2024-03-12 05:59:04 +08:00
const { areas, setAreas, updateArea, addArea, deleteArea } = useAreas();
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
const { selectedElement, setSelectedElement } = useSelect();
2024-03-11 01:24:19 +08:00
const { transform, setTransform } = useTransform();
const { t } = useTranslation();
2024-03-15 02:30:22 +08:00
const navigate = useNavigate();
2023-09-19 20:49:46 +08:00
const invertLayout = (component) =>
setLayout((prev) => ({ ...prev, [component]: !prev[component] }));
const undo = () => {
if (undoStack.length === 0) return;
2024-01-12 10:10:49 +08:00
const a = undoStack[undoStack.length - 1];
2024-02-13 03:05:21 +08:00
setUndoStack((prev) => prev.filter((e, i) => i !== prev.length - 1));
2023-09-19 20:49:46 +08:00
if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) {
2023-09-19 20:49:57 +08:00
deleteTable(tables[tables.length - 1].id, false);
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.AREA) {
2023-09-19 20:49:57 +08:00
deleteArea(areas[areas.length - 1].id, false);
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.NOTE) {
2023-09-19 20:49:57 +08:00
deleteNote(notes[notes.length - 1].id, false);
2023-09-19 20:49:52 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
2023-09-19 20:49:57 +08:00
deleteRelationship(a.data.id, false);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
deleteType(types.length - 1, false);
2023-09-19 20:49:46 +08:00
}
setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) {
setRedoStack((prev) => [
...prev,
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
updateTable(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.AREA) {
setRedoStack((prev) => [
...prev,
{ ...a, x: areas[a.id].x, y: areas[a.id].y },
]);
2023-09-19 20:50:15 +08:00
updateArea(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.NOTE) {
setRedoStack((prev) => [
...prev,
{ ...a, x: notes[a.id].x, y: notes[a.id].y },
]);
2023-09-19 20:50:15 +08:00
updateNote(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
}
2023-09-19 20:49:52 +08:00
} else if (a.action === Action.DELETE) {
if (a.element === ObjectType.TABLE) {
a.data.relationship.forEach((x) => addRelationship(x, false));
addTable(a.data.table, false);
2023-09-19 20:49:52 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
addRelationship(a.data, false);
2023-09-19 20:49:57 +08:00
} else if (a.element === ObjectType.NOTE) {
addNote(a.data, false);
2023-09-19 20:49:57 +08:00
} else if (a.element === ObjectType.AREA) {
addArea(a.data, false);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
addType({ id: a.id, ...a.data }, false);
2023-09-19 20:49:52 +08:00
}
setRedoStack((prev) => [...prev, a]);
2023-09-19 20:50:00 +08:00
} else if (a.action === Action.EDIT) {
if (a.element === ObjectType.AREA) {
2023-09-19 20:50:18 +08:00
updateArea(a.aid, a.undo);
2023-09-19 20:50:09 +08:00
} else if (a.element === ObjectType.NOTE) {
2023-09-19 20:50:15 +08:00
updateNote(a.nid, a.undo);
2023-09-19 20:50:04 +08:00
} else if (a.element === ObjectType.TABLE) {
if (a.component === "field") {
2023-09-19 20:50:15 +08:00
updateField(a.tid, a.fid, a.undo);
2023-09-19 20:50:04 +08:00
} else if (a.component === "field_delete") {
2023-12-27 10:45:23 +08:00
setRelationships((prev) => {
let temp = [...prev];
a.data.relationship.forEach((r) => {
temp.splice(r.id, 0, r);
});
temp = temp.map((e, i) => {
const recoveredRel = a.data.relationship.find(
(x) =>
(x.startTableId === e.startTableId &&
x.startFieldId === e.startFieldId) ||
(x.endTableId === e.endTableId &&
x.endFieldId === a.endFieldId),
);
if (
e.startTableId === a.tid &&
e.startFieldId >= a.data.field.id &&
!recoveredRel
) {
2023-12-27 10:45:23 +08:00
return {
...e,
id: i,
2023-12-27 10:45:23 +08:00
startFieldId: e.startFieldId + 1,
};
}
if (
e.endTableId === a.tid &&
e.endFieldId >= a.data.field.id &&
!recoveredRel
) {
2023-12-27 10:45:23 +08:00
return {
...e,
id: i,
2023-12-27 10:45:23 +08:00
endFieldId: e.endFieldId + 1,
};
}
return { ...e, id: i };
2023-12-27 10:45:23 +08:00
});
return temp;
2023-12-27 10:45:23 +08:00
});
2023-09-19 20:50:04 +08:00
setTables((prev) =>
2023-12-16 11:39:13 +08:00
prev.map((t) => {
2023-09-19 20:50:04 +08:00
if (t.id === a.tid) {
const temp = t.fields.slice();
temp.splice(a.data.field.id, 0, a.data.field);
2023-09-19 20:50:04 +08:00
return { ...t, fields: temp.map((t, i) => ({ ...t, id: i })) };
}
return t;
}),
2023-09-19 20:50:04 +08:00
);
} else if (a.component === "field_add") {
2023-09-19 20:50:16 +08:00
updateTable(a.tid, {
fields: tables[a.tid].fields
.filter((e) => e.id !== tables[a.tid].fields.length - 1)
.map((t, i) => ({ ...t, id: i })),
});
2023-09-19 20:50:04 +08:00
} else if (a.component === "index_add") {
2023-09-19 20:50:46 +08:00
updateTable(a.tid, {
indices: tables[a.tid].indices
.filter((e) => e.id !== tables[a.tid].indices.length - 1)
.map((t, i) => ({ ...t, id: i })),
});
2023-09-19 20:50:04 +08:00
} else if (a.component === "index") {
2023-09-19 20:50:15 +08:00
updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) =>
index.id === a.iid
? {
2024-02-13 03:05:21 +08:00
...index,
...a.undo,
}
: index,
2023-09-19 20:50:15 +08:00
),
});
2023-09-19 20:50:04 +08:00
} else if (a.component === "index_delete") {
setTables((prev) =>
2023-12-16 11:39:13 +08:00
prev.map((table) => {
2023-09-19 20:50:04 +08:00
if (table.id === a.tid) {
const temp = table.indices.slice();
temp.splice(a.data.id, 0, a.data);
return {
...table,
indices: temp.map((t, i) => ({ ...t, id: i })),
};
}
return table;
}),
2023-09-19 20:50:04 +08:00
);
2023-09-19 20:50:12 +08:00
} else if (a.component === "self") {
2023-09-19 20:50:15 +08:00
updateTable(a.tid, a.undo);
2023-09-19 20:50:02 +08:00
}
2023-09-19 20:50:46 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) =>
prev.map((e, idx) => (idx === a.rid ? { ...e, ...a.undo } : e)),
2023-09-19 20:50:46 +08:00
);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
if (a.component === "field_add") {
updateType(a.tid, {
fields: types[a.tid].fields.filter(
(e, i) => i !== types[a.tid].fields.length - 1,
2023-09-19 20:51:28 +08:00
),
});
}
if (a.component === "field") {
updateType(a.tid, {
fields: types[a.tid].fields.map((e, i) =>
i === a.fid ? { ...e, ...a.undo } : e,
2023-09-19 20:51:28 +08:00
),
});
} else if (a.component === "field_delete") {
setTypes((prev) =>
prev.map((t, i) => {
if (i === a.tid) {
const temp = t.fields.slice();
temp.splice(a.fid, 0, a.data);
return { ...t, fields: temp };
}
return t;
}),
2023-09-19 20:51:28 +08:00
);
} else if (a.component === "self") {
updateType(a.tid, a.undo);
}
2023-09-19 20:50:00 +08:00
}
setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.PAN) {
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({
2023-09-19 20:50:00 +08:00
...prev,
2023-09-19 20:50:15 +08:00
pan: a.undo,
2023-09-19 20:50:00 +08:00
}));
setRedoStack((prev) => [...prev, a]);
2023-09-19 20:49:46 +08:00
}
};
const redo = () => {
if (redoStack.length === 0) return;
2024-01-12 10:10:49 +08:00
const a = redoStack[redoStack.length - 1];
2024-02-13 03:05:21 +08:00
setRedoStack((prev) => prev.filter((e, i) => i !== prev.length - 1));
2023-09-19 20:49:46 +08:00
if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) {
addTable(null, false);
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.AREA) {
addArea(null, false);
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.NOTE) {
addNote(null, false);
2023-09-19 20:49:52 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
addRelationship(a.data, false);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
addType(null, false);
2023-09-19 20:49:46 +08:00
}
setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) {
setUndoStack((prev) => [
...prev,
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
updateTable(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.AREA) {
setUndoStack((prev) => [
...prev,
{ ...a, x: areas[a.id].x, y: areas[a.id].y },
]);
2023-09-19 20:50:15 +08:00
updateArea(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
} else if (a.element === ObjectType.NOTE) {
setUndoStack((prev) => [
...prev,
{ ...a, x: notes[a.id].x, y: notes[a.id].y },
]);
2023-09-19 20:50:15 +08:00
updateNote(a.id, { x: a.x, y: a.y });
2023-09-19 20:49:46 +08:00
}
2023-09-19 20:49:52 +08:00
} else if (a.action === Action.DELETE) {
if (a.element === ObjectType.TABLE) {
deleteTable(a.data.table.id, false);
2023-09-19 20:49:52 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
2023-09-19 20:49:57 +08:00
deleteRelationship(a.data.id, false);
} else if (a.element === ObjectType.NOTE) {
deleteNote(a.data.id, false);
} else if (a.element === ObjectType.AREA) {
deleteArea(a.data.id, false);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
deleteType(a.id, false);
2023-09-19 20:49:52 +08:00
}
setUndoStack((prev) => [...prev, a]);
2023-09-19 20:50:00 +08:00
} else if (a.action === Action.EDIT) {
if (a.element === ObjectType.AREA) {
2023-09-19 20:50:18 +08:00
updateArea(a.aid, a.redo);
2023-09-19 20:50:09 +08:00
} else if (a.element === ObjectType.NOTE) {
2023-09-19 20:50:15 +08:00
updateNote(a.nid, a.redo);
2023-09-19 20:50:04 +08:00
} else if (a.element === ObjectType.TABLE) {
if (a.component === "field") {
2023-09-19 20:50:15 +08:00
updateField(a.tid, a.fid, a.redo);
2023-09-19 20:50:04 +08:00
} else if (a.component === "field_delete") {
deleteField(a.data.field, a.tid, false);
2023-09-19 20:50:04 +08:00
} else if (a.component === "field_add") {
2023-09-19 20:50:15 +08:00
updateTable(a.tid, {
fields: [
...tables[a.tid].fields,
{
name: "",
type: "",
default: "",
check: "",
primary: false,
unique: false,
notNull: false,
increment: false,
comment: "",
id: tables[a.tid].fields.length,
},
],
});
2023-09-19 20:50:04 +08:00
} else if (a.component === "index_add") {
setTables((prev) =>
prev.map((table) => {
if (table.id === a.tid) {
return {
...table,
indices: [
...table.indices,
{
id: table.indices.length,
name: `index_${table.indices.length}`,
fields: [],
},
],
};
}
return table;
}),
2023-09-19 20:50:04 +08:00
);
} else if (a.component === "index") {
2023-09-19 20:50:15 +08:00
updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) =>
index.id === a.iid
? {
2024-02-13 03:05:21 +08:00
...index,
...a.redo,
}
: index,
2023-09-19 20:50:15 +08:00
),
});
2023-09-19 20:50:04 +08:00
} else if (a.component === "index_delete") {
2023-09-19 20:50:15 +08:00
updateTable(a.tid, {
indices: tables[a.tid].indices
.filter((e) => e.id !== a.data.id)
.map((t, i) => ({ ...t, id: i })),
});
2023-09-19 20:50:12 +08:00
} else if (a.component === "self") {
2023-09-19 20:50:52 +08:00
updateTable(a.tid, a.redo, false);
2023-09-19 20:50:02 +08:00
}
2023-09-19 20:50:46 +08:00
} else if (a.element === ObjectType.RELATIONSHIP) {
setRelationships((prev) =>
prev.map((e, idx) => (idx === a.rid ? { ...e, ...a.redo } : e)),
2023-09-19 20:50:46 +08:00
);
2023-09-19 20:51:28 +08:00
} else if (a.element === ObjectType.TYPE) {
if (a.component === "field_add") {
updateType(a.tid, {
fields: [
...types[a.tid].fields,
{
name: "",
type: "",
},
],
});
} else if (a.component === "field") {
updateType(a.tid, {
fields: types[a.tid].fields.map((e, i) =>
i === a.fid ? { ...e, ...a.redo } : e,
2023-09-19 20:51:28 +08:00
),
});
} else if (a.component === "field_delete") {
updateType(a.tid, {
fields: types[a.tid].fields.filter((field, i) => i !== a.fid),
});
} else if (a.component === "self") {
updateType(a.tid, a.redo);
}
2023-09-19 20:50:00 +08:00
}
setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.PAN) {
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({
2023-09-19 20:50:00 +08:00
...prev,
2023-09-19 20:50:15 +08:00
pan: a.redo,
2023-09-19 20:50:00 +08:00
}));
setUndoStack((prev) => [...prev, a]);
2023-09-19 20:49:46 +08:00
}
};
const fileImport = () => setModal(MODAL.IMPORT);
2023-09-19 20:50:22 +08:00
const viewGrid = () =>
setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid }));
const zoomIn = () =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 }));
2023-09-19 20:50:22 +08:00
const zoomOut = () =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }));
2023-09-19 20:50:22 +08:00
const viewStrictMode = () => {
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
};
const viewFieldSummary = () => {
setSettings((prev) => ({
...prev,
showFieldSummary: !prev.showFieldSummary,
}));
};
const copyAsImage = () => {
toPng(document.getElementById("canvas")).then(function (dataUrl) {
const blob = dataURItoBlob(dataUrl);
navigator.clipboard
.write([new ClipboardItem({ "image/png": blob })])
.then(() => {
Toast.success(t("copied_to_clipboard"));
2023-09-19 20:50:22 +08:00
})
2023-12-16 11:39:13 +08:00
.catch(() => {
Toast.error(t("oops_smth_went_wrong"));
2023-09-19 20:50:22 +08:00
});
});
};
const resetView = () =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: 1, pan: { x: 0, y: 0 } }));
2023-09-19 20:50:24 +08:00
const fitWindow = () => {
const diagram = document.getElementById("diagram").getBoundingClientRect();
const canvas = document.getElementById("canvas").getBoundingClientRect();
const scaleX = canvas.width / diagram.width;
const scaleY = canvas.height / diagram.height;
const scale = Math.min(scaleX, scaleY);
2024-02-20 17:32:19 +08:00
const translateX = canvas.left;
const translateY = canvas.top;
2023-09-19 20:50:24 +08:00
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({
2023-09-19 20:50:24 +08:00
...prev,
2024-02-20 17:32:19 +08:00
zoom: scale - 0.01,
2023-09-19 20:50:24 +08:00
pan: { x: translateX, y: translateY },
}));
};
2023-09-19 20:50:28 +08:00
const edit = () => {
if (selectedElement.element === ObjectType.TABLE) {
if (!layout.sidebar) {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
open: true,
}));
2023-09-19 20:50:28 +08:00
} else {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
open: true,
2024-03-15 22:37:22 +08:00
currentTab: Tab.TABLES,
2024-03-14 02:39:16 +08:00
}));
2024-03-15 22:37:22 +08:00
if (selectedElement.currentTab !== Tab.TABLES) return;
2023-09-19 20:50:28 +08:00
document
.getElementById(`scroll_table_${selectedElement.id}`)
.scrollIntoView({ behavior: "smooth" });
}
2023-09-19 20:50:30 +08:00
} else if (selectedElement.element === ObjectType.AREA) {
2023-09-19 20:50:28 +08:00
if (layout.sidebar) {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
2024-03-15 22:37:22 +08:00
currentTab: Tab.AREAS,
2024-03-14 02:39:16 +08:00
}));
2024-03-15 22:37:22 +08:00
if (selectedElement.currentTab !== Tab.AREAS) return;
2023-09-19 20:50:28 +08:00
document
.getElementById(`scroll_area_${selectedElement.id}`)
.scrollIntoView({ behavior: "smooth" });
} else {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
open: true,
editFromToolbar: true,
}));
2023-09-19 20:50:28 +08:00
}
2023-09-19 20:50:30 +08:00
} else if (selectedElement.element === ObjectType.NOTE) {
2023-09-19 20:50:28 +08:00
if (layout.sidebar) {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
2024-03-15 22:37:22 +08:00
currentTab: Tab.NOTES,
2024-03-14 02:39:16 +08:00
open: false,
}));
2024-03-15 22:37:22 +08:00
if (selectedElement.currentTab !== Tab.NOTES) return;
2023-09-19 20:50:28 +08:00
document
.getElementById(`scroll_note_${selectedElement.id}`)
.scrollIntoView({ behavior: "smooth" });
} else {
2024-03-14 02:39:16 +08:00
setSelectedElement((prev) => ({
...prev,
open: true,
editFromToolbar: true,
}));
2023-09-19 20:50:28 +08:00
}
}
};
2023-09-19 20:50:30 +08:00
const del = () => {
switch (selectedElement.element) {
case ObjectType.TABLE:
deleteTable(selectedElement.id);
2023-09-19 20:50:30 +08:00
break;
case ObjectType.NOTE:
deleteNote(selectedElement.id);
2023-09-19 20:50:30 +08:00
break;
case ObjectType.AREA:
deleteArea(selectedElement.id);
2023-09-19 20:50:30 +08:00
break;
default:
break;
}
};
const duplicate = () => {
switch (selectedElement.element) {
case ObjectType.TABLE:
addTable({
2023-09-19 20:50:30 +08:00
...tables[selectedElement.id],
x: tables[selectedElement.id].x + 20,
y: tables[selectedElement.id].y + 20,
id: tables.length,
});
break;
case ObjectType.NOTE:
addNote({
2023-09-19 20:50:30 +08:00
...notes[selectedElement.id],
x: notes[selectedElement.id].x + 20,
y: notes[selectedElement.id].y + 20,
id: notes.length,
});
break;
case ObjectType.AREA:
addArea({
2023-09-19 20:50:30 +08:00
...areas[selectedElement.id],
x: areas[selectedElement.id].x + 20,
y: areas[selectedElement.id].y + 20,
id: areas.length,
});
break;
default:
break;
}
};
2023-09-19 20:50:32 +08:00
const copy = () => {
switch (selectedElement.element) {
case ObjectType.TABLE:
navigator.clipboard
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
.catch(() => Toast.error(t("oops_smth_went_wrong")));
2023-09-19 20:50:32 +08:00
break;
case ObjectType.NOTE:
navigator.clipboard
.writeText(JSON.stringify({ ...notes[selectedElement.id] }))
.catch(() => Toast.error(t("oops_smth_went_wrong")));
2023-09-19 20:50:32 +08:00
break;
case ObjectType.AREA:
navigator.clipboard
.writeText(JSON.stringify({ ...areas[selectedElement.id] }))
.catch(() => Toast.error(t("oops_smth_went_wrong")));
2023-09-19 20:50:32 +08:00
break;
default:
break;
}
};
const paste = () => {
navigator.clipboard.readText().then((text) => {
let obj = null;
try {
obj = JSON.parse(text);
} catch (error) {
return;
}
const v = new Validator();
if (v.validate(obj, tableSchema).valid) {
addTable({
2023-09-19 20:50:32 +08:00
...obj,
x: obj.x + 20,
y: obj.y + 20,
id: tables.length,
});
} else if (v.validate(obj, areaSchema).valid) {
addArea({
2023-09-19 20:50:32 +08:00
...obj,
x: obj.x + 20,
y: obj.y + 20,
id: areas.length,
});
} else if (v.validate(obj, noteSchema)) {
addNote({
2023-09-19 20:50:32 +08:00
...obj,
x: obj.x + 20,
y: obj.y + 20,
id: notes.length,
});
}
});
};
const cut = () => {
copy();
del();
};
2024-03-15 22:00:23 +08:00
const save = () => setSaveState(State.SAVING);
const open = () => setModal(MODAL.OPEN);
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
2023-09-19 20:50:22 +08:00
2023-09-19 20:48:26 +08:00
const menu = {
file: {
new: {
function: () => setModal(MODAL.NEW),
2023-09-19 20:48:26 +08:00
},
new_window: {
function: () => {
const newWindow = window.open("/editor", "_blank");
newWindow.name = window.name;
},
2023-09-19 20:48:26 +08:00
},
open: {
2023-10-27 01:26:13 +08:00
function: open,
shortcut: "Ctrl+O",
2023-10-22 14:01:43 +08:00
},
save: {
2023-10-26 21:34:50 +08:00
function: save,
shortcut: "Ctrl+S",
2023-09-19 20:48:26 +08:00
},
save_as: {
2023-10-28 07:11:31 +08:00
function: saveDiagramAs,
shortcut: "Ctrl+Shift+S",
2023-09-19 20:48:26 +08:00
},
save_as_template: {
2023-12-19 10:36:10 +08:00
function: () => {
db.templates
.add({
title: title,
tables: tables,
relationships: relationships,
types: types,
notes: notes,
subjectAreas: areas,
custom: 1,
})
.then(() => {
Toast.success(t("template_saved"));
2023-12-19 10:36:10 +08:00
});
},
},
rename: {
2023-10-22 00:45:13 +08:00
function: () => {
setModal(MODAL.RENAME);
2023-10-22 00:45:13 +08:00
setPrevTitle(title);
},
2023-09-19 20:48:26 +08:00
},
delete_diagram: {
2024-04-11 15:30:42 +08:00
warning: {
title: t("delete_diagram"),
message: t("are_you_sure_delete_diagram"),
2024-04-11 15:30:42 +08:00
},
2023-10-28 07:11:31 +08:00
function: async () => {
await db.diagrams
.delete(diagramId)
.then(() => {
setDiagramId(0);
setTitle("Untitled diagram");
setTables([]);
setRelationships([]);
setAreas([]);
setNotes([]);
2023-11-02 22:45:11 +08:00
setTypes([]);
2023-10-28 07:11:31 +08:00
setUndoStack([]);
setRedoStack([]);
})
.catch(() => Toast.error(t("oops_smth_went_wrong")));
2023-10-28 07:11:31 +08:00
},
},
import_diagram: {
2023-09-19 20:50:22 +08:00
function: fileImport,
shortcut: "Ctrl+I",
2023-09-19 20:48:26 +08:00
},
import_from_source: {
...(database === DB.GENERIC && {
children: [
{
2024-06-14 06:00:47 +08:00
MySQL: () => {
setModal(MODAL.IMPORT_SRC);
setImportDb(DB.MYSQL);
},
},
// {
// PostgreSQL: () => setModal(MODAL.IMPORT_SRC),
// },
{
2024-06-14 06:00:47 +08:00
SQLite: () => {
setModal(MODAL.IMPORT_SRC);
setImportDb(DB.SQLITE);
},
},
{
MariaDB: () => {
setModal(MODAL.IMPORT_SRC);
setImportDb(DB.MARIADB);
},
},
// {
// MSSQL: () => setModal(MODAL.IMPORT_SRC),
// },
],
}),
function: () => {
if (database === DB.GENERIC) return;
2024-06-14 06:00:47 +08:00
setModal(MODAL.IMPORT_SRC);
},
2024-01-18 18:45:58 +08:00
},
export_source: {
...(database === DB.GENERIC && {
children: [
{
MySQL: () => {
setModal(MODAL.CODE);
const src = jsonToMySQL({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
{
PostgreSQL: () => {
setModal(MODAL.CODE);
const src = jsonToPostgreSQL({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
{
SQLite: () => {
setModal(MODAL.CODE);
const src = jsonToSQLite({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
{
MariaDB: () => {
setModal(MODAL.CODE);
const src = jsonToMariaDB({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
{
MSSQL: () => {
setModal(MODAL.CODE);
const src = jsonToSQLServer({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
],
}),
function: () => {
if (database === DB.GENERIC) return;
setModal(MODAL.CODE);
const src = exportSQL({
tables: tables,
references: relationships,
types: types,
database: database,
});
setExportData((prev) => ({
...prev,
data: src,
extension: "sql",
}));
},
},
export_as: {
2023-09-19 20:48:26 +08:00
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
});
setModal(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
);
setModal(MODAL.IMG);
2023-09-19 20:49:20 +08:00
},
},
{
JSON: () => {
setModal(MODAL.CODE);
2023-09-19 20:49:20 +08:00
const result = JSON.stringify(
{
tables: tables,
relationships: relationships,
notes: notes,
2023-09-19 20:49:28 +08:00
subjectAreas: areas,
2023-09-19 20:51:29 +08:00
types: types,
2024-02-29 05:03:40 +08:00
title: title,
2023-09-19 20:49:20 +08:00
},
null,
2,
2023-09-19 20:49:20 +08:00
);
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
);
setModal(MODAL.IMG);
2023-09-19 20:48:28 +08:00
},
},
2023-09-19 20:49:24 +08:00
{
PDF: () => {
const canvas = document.getElementById("canvas");
2023-09-19 20:49:28 +08:00
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,
2023-09-19 20:49:28 +08:00
);
doc.save(`${exportData.filename}.pdf`);
});
2023-09-19 20:49:24 +08:00
},
},
2023-09-19 20:49:30 +08:00
{
DRAWDB: () => {
const result = JSON.stringify(
{
author: "Unnamed",
2024-02-29 05:03:40 +08:00
title: title,
2023-09-19 20:49:31 +08:00
date: new Date().toISOString(),
2023-09-19 20:49:30 +08:00
tables: tables,
relationships: relationships,
notes: notes,
subjectAreas: areas,
2023-09-19 20:51:36 +08:00
types: types,
2023-09-19 20:49:30 +08:00
},
null,
2,
2023-09-19 20:49:30 +08:00
);
const blob = new Blob([result], {
type: "text/plain;charset=utf-8",
});
saveAs(blob, `${exportData.filename}.ddb`);
},
},
2023-09-19 20:48:26 +08:00
],
2024-02-13 03:05:21 +08:00
function: () => {},
2023-09-19 20:48:26 +08:00
},
exit: {
2024-03-15 02:30:22 +08:00
function: () => {
save();
2024-03-15 22:00:23 +08:00
if (saveState === State.SAVED) navigate("/");
2024-03-15 02:30:22 +08:00
},
2023-09-19 20:48:26 +08:00
},
},
edit: {
undo: {
2023-09-19 20:49:46 +08:00
function: undo,
2023-09-19 20:50:22 +08:00
shortcut: "Ctrl+Z",
2023-09-19 20:48:26 +08:00
},
redo: {
2023-09-19 20:49:46 +08:00
function: redo,
2023-09-19 20:50:22 +08:00
shortcut: "Ctrl+Y",
2023-09-19 20:48:26 +08:00
},
clear: {
warning: {
title: t("clear"),
message: t("are_you_sure_clear"),
},
2023-09-19 20:49:39 +08:00
function: () => {
setTables([]);
setRelationships([]);
setAreas([]);
setNotes([]);
2023-09-19 20:50:22 +08:00
setUndoStack([]);
setRedoStack([]);
2023-09-19 20:49:39 +08:00
},
},
edit: {
2023-09-19 20:50:28 +08:00
function: edit,
shortcut: "Ctrl+E",
2023-09-19 20:49:46 +08:00
},
cut: {
2023-09-19 20:50:32 +08:00
function: cut,
2023-09-19 20:50:28 +08:00
shortcut: "Ctrl+X",
2023-09-19 20:48:26 +08:00
},
copy: {
2023-09-19 20:50:32 +08:00
function: copy,
2023-09-19 20:50:28 +08:00
shortcut: "Ctrl+C",
2023-09-19 20:48:26 +08:00
},
paste: {
2023-09-19 20:50:32 +08:00
function: paste,
2023-09-19 20:50:28 +08:00
shortcut: "Ctrl+V",
2023-09-19 20:48:26 +08:00
},
duplicate: {
2023-09-19 20:50:30 +08:00
function: duplicate,
2023-09-19 20:50:28 +08:00
shortcut: "Ctrl+D",
2023-09-19 20:49:46 +08:00
},
delete: {
2023-09-19 20:50:30 +08:00
function: del,
2023-09-19 20:50:28 +08:00
shortcut: "Del",
2023-09-19 20:49:46 +08:00
},
copy_as_image: {
2023-09-19 20:50:22 +08:00
function: copyAsImage,
shortcut: "Ctrl+Alt+C",
2023-09-19 20:48:26 +08:00
},
},
view: {
header: {
state: layout.header ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-09-19 20:49:46 +08:00
function: () =>
setLayout((prev) => ({ ...prev, header: !prev.header })),
2023-09-19 20:48:26 +08:00
},
sidebar: {
state: layout.sidebar ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-09-19 20:49:37 +08:00
function: () =>
2023-09-19 20:49:46 +08:00
setLayout((prev) => ({ ...prev, sidebar: !prev.sidebar })),
2023-09-19 20:48:26 +08:00
},
issues: {
state: layout.issues ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-09-19 20:49:46 +08:00
function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
2023-09-19 20:48:26 +08:00
},
strict_mode: {
state: settings.strictMode ? (
<i className="bi bi-toggle-off" />
) : (
<i className="bi bi-toggle-on" />
),
2023-09-19 20:50:22 +08:00
function: viewStrictMode,
shortcut: "Ctrl+Shift+M",
2023-09-19 20:48:26 +08:00
},
presentation_mode: {
2024-02-03 19:48:07 +08:00
function: () => {
2024-02-13 03:05:21 +08:00
setLayout((prev) => ({
2024-02-03 19:48:07 +08:00
...prev,
2024-02-05 19:40:59 +08:00
header: false,
2024-02-03 19:48:07 +08:00
sidebar: false,
toolbar: false,
}));
enterFullscreen();
2024-02-13 03:05:21 +08:00
},
2024-02-03 19:48:07 +08:00
},
field_details: {
state: settings.showFieldSummary ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-09-19 20:50:22 +08:00
function: viewFieldSummary,
shortcut: "Ctrl+Shift+F",
2023-09-19 20:49:16 +08:00
},
reset_view: {
2023-09-19 20:50:22 +08:00
function: resetView,
shortcut: "Ctrl+R",
2023-09-19 20:48:26 +08:00
},
show_grid: {
state: settings.showGrid ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-09-19 20:50:22 +08:00
function: viewGrid,
shortcut: "Ctrl+Shift+G",
2023-09-19 20:49:46 +08:00
},
show_cardinality: {
state: settings.showCardinality ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2023-12-30 04:38:14 +08:00
function: () =>
setSettings((prev) => ({
...prev,
showCardinality: !prev.showCardinality,
})),
},
theme: {
2023-09-19 20:51:08 +08:00
children: [
{
light: () => {
2023-09-19 20:51:08 +08:00
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "light");
}
2023-09-19 20:51:43 +08:00
localStorage.setItem("theme", "light");
2023-09-19 20:51:08 +08:00
setSettings((prev) => ({ ...prev, mode: "light" }));
},
},
{
dark: () => {
2023-09-19 20:51:08 +08:00
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "dark");
}
2023-09-19 20:51:43 +08:00
localStorage.setItem("theme", "dark");
2023-09-19 20:51:08 +08:00
setSettings((prev) => ({ ...prev, mode: "dark" }));
},
},
],
2024-02-13 03:05:21 +08:00
function: () => {},
2023-09-19 20:48:26 +08:00
},
zoom_in: {
2023-09-19 20:50:22 +08:00
function: zoomIn,
shortcut: "Ctrl+Up/Wheel",
2023-09-19 20:48:26 +08:00
},
zoom_out: {
2023-09-19 20:50:22 +08:00
function: zoomOut,
shortcut: "Ctrl+Down/Wheel",
2023-09-19 20:48:26 +08:00
},
fullscreen: {
2023-09-19 20:48:35 +08:00
function: enterFullscreen,
2023-09-19 20:48:26 +08:00
},
},
settings: {
show_timeline: {
2024-02-19 20:14:32 +08:00
function: () => setSidesheet(SIDESHEET.TIMELINE),
},
autosave: {
state: settings.autosave ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2024-01-29 15:53:05 +08:00
function: () =>
setSettings((prev) => ({ ...prev, autosave: !prev.autosave })),
2024-01-29 15:53:05 +08:00
},
panning: {
state: settings.panning ? (
<i className="bi bi-toggle-on" />
) : (
<i className="bi bi-toggle-off" />
),
2024-01-29 15:53:05 +08:00
function: () =>
setSettings((prev) => ({ ...prev, panning: !prev.panning })),
2024-01-29 15:53:05 +08:00
},
table_width: {
function: () => setModal(MODAL.TABLE_WIDTH),
},
language: {
function: () => setModal(MODAL.LANGUAGE),
},
flush_storage: {
2024-04-11 15:30:42 +08:00
warning: {
title: t("flush_storage"),
message: t("are_you_sure_flush_storage"),
2024-04-11 15:30:42 +08:00
},
2024-01-29 15:53:05 +08:00
function: async () => {
db.delete()
.then(() => {
Toast.success(t("storage_flushed"));
2024-01-29 15:53:05 +08:00
window.location.reload(false);
})
.catch(() => {
Toast.error(t("oops_smth_went_wrong"));
2024-01-29 15:53:05 +08:00
});
},
2024-01-14 08:43:22 +08:00
},
},
help: {
shortcuts: {
2023-09-19 20:51:44 +08:00
function: () => window.open("/shortcuts", "_blank"),
shortcut: "Ctrl+H",
2023-09-19 20:48:26 +08:00
},
ask_on_discord: {
2024-04-07 05:20:24 +08:00
function: () => window.open("https://discord.gg/BrjZgNrmR6", "_blank"),
2023-09-19 20:48:26 +08:00
},
report_bug: {
2024-04-06 10:19:13 +08:00
function: () => window.open("/bug-report", "_blank"),
2023-09-19 20:50:39 +08:00
},
feedback: {
2023-10-13 04:17:23 +08:00
function: () => window.open("/survey", "_blank"),
2023-09-19 20:48:26 +08:00
},
},
};
2023-09-19 20:50:22 +08:00
useHotkeys("ctrl+i, meta+i", fileImport, { preventDefault: true });
useHotkeys("ctrl+z, meta+z", undo, { preventDefault: true });
useHotkeys("ctrl+y, meta+y", redo, { preventDefault: true });
2023-10-27 01:26:13 +08:00
useHotkeys("ctrl+s, meta+s", save, { preventDefault: true });
useHotkeys("ctrl+o, meta+o", open, { preventDefault: true });
2023-09-19 20:50:28 +08:00
useHotkeys("ctrl+e, meta+e", edit, { preventDefault: true });
2023-09-19 20:50:30 +08:00
useHotkeys("ctrl+d, meta+d", duplicate, { preventDefault: true });
2023-09-19 20:50:32 +08:00
useHotkeys("ctrl+c, meta+c", copy, { preventDefault: true });
useHotkeys("ctrl+v, meta+v", paste, { preventDefault: true });
useHotkeys("ctrl+x, meta+x", cut, { preventDefault: true });
2023-09-19 20:50:30 +08:00
useHotkeys("delete", del, { preventDefault: true });
2023-09-19 20:50:22 +08:00
useHotkeys("ctrl+shift+g, meta+shift+g", viewGrid, { preventDefault: true });
useHotkeys("ctrl+up, meta+up", zoomIn, { preventDefault: true });
useHotkeys("ctrl+down, meta+down", zoomOut, { preventDefault: true });
useHotkeys("ctrl+shift+m, meta+shift+m", viewStrictMode, {
preventDefault: true,
});
useHotkeys("ctrl+shift+f, meta+shift+f", viewFieldSummary, {
preventDefault: true,
});
2023-10-28 07:11:31 +08:00
useHotkeys("ctrl+shift+s, meta+shift+s", saveDiagramAs, {
preventDefault: true,
});
2023-09-19 20:50:22 +08:00
useHotkeys("ctrl+alt+c, meta+alt+c", copyAsImage, { preventDefault: true });
useHotkeys("ctrl+r, meta+r", resetView, { preventDefault: true });
2023-09-19 20:51:44 +08:00
useHotkeys("ctrl+h, meta+h", () => window.open("/shortcuts", "_blank"), {
preventDefault: true,
});
2023-09-19 20:50:24 +08:00
useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true });
2023-09-19 20:50:22 +08:00
2023-09-19 20:46:48 +08:00
return (
2023-09-19 20:51:18 +08:00
<>
2023-09-19 20:48:46 +08:00
{layout.header && header()}
2024-02-03 19:48:07 +08:00
{layout.toolbar && toolbar()}
2023-09-19 20:48:26 +08:00
<Modal
modal={modal}
exportData={exportData}
setExportData={setExportData}
title={title}
setTitle={setTitle}
setPrevTitle={setPrevTitle}
setDiagramId={setDiagramId}
setModal={setModal}
prevTitle={prevTitle}
2024-06-14 06:00:47 +08:00
importDb={importDb}
/>
<Sidesheet
type={sidesheet}
onClose={() => setSidesheet(SIDESHEET.NONE)}
/>
2023-09-19 20:51:18 +08:00
</>
2023-09-19 20:46:48 +08:00
);
2023-09-19 20:48:34 +08:00
2023-09-19 20:51:18 +08:00
function toolbar() {
return (
2024-02-03 19:48:07 +08:00
<div className="py-1.5 px-5 flex justify-between items-center rounded-xl my-1 sm:mx-1 xl:mx-6 select-none overflow-hidden toolbar-theme">
2023-09-19 20:51:18 +08:00
<div className="flex justify-start items-center">
<LayoutDropdown />
2023-09-19 20:51:18 +08:00
<Divider layout="vertical" margin="8px" />
<Dropdown
style={{ width: "240px" }}
position="bottomLeft"
render={
<Dropdown.Menu>
<Dropdown.Item
onClick={fitWindow}
style={{ display: "flex", justifyContent: "space-between" }}
>
<div>{t("fit_window_reset")}</div>
2023-09-19 20:51:18 +08:00
<div className="text-gray-400">Ctrl+Alt+W</div>
</Dropdown.Item>
<Dropdown.Divider />
{[0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0].map((e, i) => (
<Dropdown.Item
key={i}
onClick={() => {
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: e }));
2023-09-19 20:51:18 +08:00
}}
>
{Math.floor(e * 100)}%
</Dropdown.Item>
))}
<Dropdown.Divider />
<Dropdown.Item>
<InputNumber
field="zoom"
label={t("zoom")}
placeholder={t("zoom")}
2023-09-19 20:51:18 +08:00
suffix={<div className="p-1">%</div>}
onChange={(v) =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({
2023-09-19 20:51:18 +08:00
...prev,
zoom: parseFloat(v) * 0.01,
}))
}
/>
</Dropdown.Item>
</Dropdown.Menu>
}
trigger="click"
>
<div className="py-1 px-2 hover-2 rounded flex items-center justify-center">
2024-03-10 02:35:04 +08:00
<div className="w-[40px]">
{Math.floor(transform.zoom * 100)}%
</div>
2023-09-19 20:51:18 +08:00
<div>
<IconCaretdown />
</div>
</div>
</Dropdown>
<Tooltip content={t("zoom_in")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded text-lg"
onClick={() =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 }))
2023-09-19 20:51:18 +08:00
}
>
2024-03-16 08:23:10 +08:00
<i className="fa-solid fa-magnifying-glass-plus" />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
<Tooltip content={t("zoom_out")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded text-lg"
onClick={() =>
2024-03-10 02:35:04 +08:00
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }))
2023-09-19 20:51:18 +08:00
}
>
2024-03-16 08:23:10 +08:00
<i className="fa-solid fa-magnifying-glass-minus" />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
<Divider layout="vertical" margin="8px" />
<Tooltip content={t("undo")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded flex items-center"
onClick={undo}
>
<IconUndo
size="large"
style={{ color: undoStack.length === 0 ? "#9598a6" : "" }}
/>
</button>
</Tooltip>
<Tooltip content={t("redo")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded flex items-center"
onClick={redo}
>
<IconRedo
size="large"
style={{ color: redoStack.length === 0 ? "#9598a6" : "" }}
/>
</button>
</Tooltip>
<Divider layout="vertical" margin="8px" />
<Tooltip content={t("add_table")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="flex items-center py-1 px-2 hover-2 rounded"
onClick={() => addTable()}
>
2024-04-05 09:19:36 +08:00
<IconAddTable />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
<Tooltip content={t("add_area")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded flex items-center"
onClick={() => addArea()}
>
2024-04-05 09:19:36 +08:00
<IconAddArea />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
<Tooltip content={t("add_note")} position="bottom">
2023-09-19 20:51:18 +08:00
<button
className="py-1 px-2 hover-2 rounded flex items-center"
onClick={() => addNote()}
>
2024-04-05 09:19:36 +08:00
<IconAddNote />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
<Divider layout="vertical" margin="8px" />
<Tooltip content={t("save")} position="bottom">
2023-10-26 21:34:50 +08:00
<button
className="py-1 px-2 hover-2 rounded flex items-center"
onClick={save}
>
2023-09-19 20:51:18 +08:00
<IconSaveStroked size="extra-large" />
</button>
</Tooltip>
<Tooltip content={t("to_do")} position="bottom">
2024-02-13 03:05:21 +08:00
<button
2024-03-01 01:06:08 +08:00
className="py-1 px-2 hover-2 rounded text-xl -mt-0.5"
2024-02-13 03:05:21 +08:00
onClick={() => setSidesheet(SIDESHEET.TODO)}
>
2024-03-16 08:23:10 +08:00
<i className="fa-regular fa-calendar-check" />
2023-09-19 20:51:18 +08:00
</button>
</Tooltip>
2024-04-11 12:48:17 +08:00
<Divider layout="vertical" margin="8px" />
<Tooltip content={t("theme")} position="bottom">
2024-04-11 12:48:17 +08:00
<button
className="py-1 px-2 hover-2 rounded text-xl -mt-0.5"
onClick={() => {
const body = document.body;
if (body.hasAttribute("theme-mode")) {
if (body.getAttribute("theme-mode") === "light") {
menu["view"]["theme"].children[1]["dark"]();
2024-04-11 12:48:17 +08:00
} else {
menu["view"]["theme"].children[0]["light"]();
2024-04-11 12:48:17 +08:00
}
}
}}
>
<i className="fa-solid fa-circle-half-stroke" />
</button>
</Tooltip>
2023-09-19 20:51:18 +08:00
</div>
<button
onClick={() => invertLayout("header")}
className="flex items-center"
>
{layout.header ? <IconChevronUp /> : <IconChevronDown />}
</button>
</div>
);
}
2023-11-25 00:28:39 +08:00
function getState() {
2024-03-15 22:00:23 +08:00
switch (saveState) {
2023-11-25 00:28:39 +08:00
case State.NONE:
return t("no_changes");
2023-11-25 00:28:39 +08:00
case State.LOADING:
return t("loading");
2023-11-25 00:28:39 +08:00
case State.SAVED:
return `${t("last_saved")} ${lastSaved}`;
2023-11-25 00:28:39 +08:00
case State.SAVING:
return t("saving");
2023-12-25 05:26:14 +08:00
case State.ERROR:
return t("failed_to_save");
2023-11-25 00:28:39 +08:00
default:
return "";
}
}
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">
2023-09-19 20:51:08 +08:00
<div className="flex justify-start items-center">
2023-09-19 20:48:35 +08:00
<Link to="/">
<img
width={54}
src={icon}
alt="logo"
className="ms-8 min-w-[54px]"
/>
</Link>
<div className="ms-1 mt-1">
2023-10-22 00:45:13 +08:00
<div className="flex items-center">
<div
className="text-xl ms-3 me-1"
onMouseEnter={() => setShowEditName(true)}
onMouseLeave={() => setShowEditName(false)}
onClick={() => setModal(MODAL.RENAME)}
2023-10-22 00:45:13 +08:00
>
2023-12-25 05:26:14 +08:00
{window.name.split(" ")[0] === "t" ? "Templates/" : "Diagrams/"}
2023-10-22 00:45:13 +08:00
{title}
</div>
{(showEditName || modal === MODAL.RENAME) && <IconEdit />}
2023-10-22 00:45:13 +08:00
</div>
2023-09-19 20:48:35 +08:00
<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: "240px" }}
2023-09-19 20:48:35 +08:00
render={
<Dropdown.Menu>
{Object.keys(menu[category]).map((item, index) => {
2023-09-19 20:50:22 +08:00
if (menu[category][item].children) {
2023-09-19 20:48:35 +08:00
return (
<Dropdown
style={{ width: "120px" }}
key={item}
2024-04-11 15:30:42 +08:00
position="rightTop"
2023-09-19 20:48:35 +08:00
render={
<Dropdown.Menu>
{menu[category][item].children.map(
(e, i) => (
<Dropdown.Item
key={i}
onClick={Object.values(e)[0]}
>
{t(Object.keys(e)[0])}
2023-09-19 20:48:35 +08:00
</Dropdown.Item>
),
2023-09-19 20:48:35 +08:00
)}
</Dropdown.Menu>
}
>
<Dropdown.Item
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
onClick={menu[category][item].function}
>
{t(item)}
2023-09-19 20:48:35 +08:00
<IconChevronRight />
</Dropdown.Item>
</Dropdown>
);
}
2024-04-11 15:30:42 +08:00
if (menu[category][item].warning) {
return (
<Popconfirm
key={index}
title={menu[category][item].warning.title}
content={menu[category][item].warning.message}
onConfirm={menu[category][item].function}
position="right"
okText={t("confirm")}
cancelText={t("cancel")}
2024-04-11 15:30:42 +08:00
>
<Dropdown.Item>{t(item)}</Dropdown.Item>
2024-04-11 15:30:42 +08:00
</Popconfirm>
);
}
2023-09-19 20:48:35 +08:00
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}
2023-09-19 20:50:22 +08:00
style={
menu[category][item].shortcut && {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}
}
2023-09-19 20:48:34 +08:00
>
<div className="w-full flex items-center justify-between">
<div>{t(item)}</div>
<div className="flex items-center gap-1">
{menu[category][item].shortcut && (
<div className="text-gray-400">
{menu[category][item].shortcut}
</div>
)}
{menu[category][item].state &&
menu[category][item].state}
</div>
</div>
2023-09-19 20:48:34 +08:00
</Dropdown.Item>
2023-09-19 20:48:35 +08:00
);
})}
</Dropdown.Menu>
}
>
<div className="px-3 py-1 hover-2 rounded">
{t(category)}
</div>
2023-09-19 20:48:35 +08:00
</Dropdown>
))}
</div>
2023-11-25 00:28:39 +08:00
<Button
size="small"
type="tertiary"
icon={
2024-03-15 22:00:23 +08:00
saveState === State.LOADING || saveState === State.SAVING ? (
2023-11-25 00:28:39 +08:00
<Spin size="small" />
) : null
}
>
{getState()}
2023-09-19 20:48:35 +08:00
</Button>
2023-09-19 20:48:34 +08:00
</div>
</div>
</div>
2023-09-19 20:48:35 +08:00
</nav>
);
2023-09-19 20:48:34 +08:00
}
2023-09-19 20:46:48 +08:00
}