mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-05-24 10:29:11 +00:00
Refactor structure of modal component
This commit is contained in:
parent
bf744630c0
commit
fc018dcca6
@ -21,15 +21,8 @@ import {
|
||||
Toast,
|
||||
Popconfirm,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||
import { toPng, toJpeg } from "html-to-image";
|
||||
import { saveAs } from "file-saver";
|
||||
import {
|
||||
jsonToMySQL,
|
||||
jsonToPostgreSQL,
|
||||
jsonToSQLite,
|
||||
jsonToMariaDB,
|
||||
jsonToSQLServer,
|
||||
} from "../../utils/toSQL";
|
||||
import {
|
||||
ObjectType,
|
||||
Action,
|
||||
@ -60,8 +53,8 @@ import useSaveState from "../../hooks/useSaveState";
|
||||
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";
|
||||
import ModalManager from "./ModalManager";
|
||||
|
||||
export default function ControlPanel({
|
||||
diagramId,
|
||||
@ -72,13 +65,7 @@ export default function ControlPanel({
|
||||
}) {
|
||||
const [modal, setModal] = useState(MODAL.NONE);
|
||||
const [sidesheet, setSidesheet] = useState(SIDESHEET.NONE);
|
||||
const [prevTitle, setPrevTitle] = useState(title);
|
||||
const [showEditName, setShowEditName] = useState(false);
|
||||
const [exportData, setExportData] = useState({
|
||||
data: null,
|
||||
filename: `${title}_${new Date().toISOString()}`,
|
||||
extension: "",
|
||||
});
|
||||
const { saveState, setSaveState } = useSaveState();
|
||||
const { layout, setLayout } = useLayout();
|
||||
const { settings, setSettings } = useSettings();
|
||||
@ -452,7 +439,7 @@ export default function ControlPanel({
|
||||
}
|
||||
};
|
||||
|
||||
const fileImport = () => setModal(MODAL.IMPORT);
|
||||
const importDiagram = () => setModal(MODAL.IMPORT_DIAGRAM);
|
||||
const viewGrid = () =>
|
||||
setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid }));
|
||||
const zoomIn = () =>
|
||||
@ -704,7 +691,6 @@ export default function ControlPanel({
|
||||
rename: {
|
||||
function: () => {
|
||||
setModal(MODAL.RENAME);
|
||||
setPrevTitle(title);
|
||||
},
|
||||
},
|
||||
delete_diagram: {
|
||||
@ -730,7 +716,7 @@ export default function ControlPanel({
|
||||
},
|
||||
},
|
||||
import_diagram: {
|
||||
function: fileImport,
|
||||
function: importDiagram,
|
||||
shortcut: "Ctrl+I",
|
||||
},
|
||||
import_from_source: {
|
||||
@ -739,66 +725,8 @@ export default function ControlPanel({
|
||||
export_as: {
|
||||
children: [
|
||||
{
|
||||
PNG: () => {
|
||||
toPng(document.getElementById("canvas")).then(function (dataUrl) {
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: dataUrl,
|
||||
extension: "png",
|
||||
}));
|
||||
});
|
||||
setModal(MODAL.IMG);
|
||||
},
|
||||
},
|
||||
{
|
||||
JPEG: () => {
|
||||
toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then(
|
||||
function (dataUrl) {
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: dataUrl,
|
||||
extension: "jpeg",
|
||||
}));
|
||||
},
|
||||
);
|
||||
setModal(MODAL.IMG);
|
||||
},
|
||||
},
|
||||
{
|
||||
JSON: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const result = JSON.stringify(
|
||||
{
|
||||
tables: tables,
|
||||
relationships: relationships,
|
||||
notes: notes,
|
||||
subjectAreas: areas,
|
||||
types: types,
|
||||
title: title,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: result,
|
||||
extension: "json",
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
SVG: () => {
|
||||
const filter = (node) => node.tagName !== "i";
|
||||
toSvg(document.getElementById("canvas"), { filter: filter }).then(
|
||||
function (dataUrl) {
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: dataUrl,
|
||||
extension: "svg",
|
||||
}));
|
||||
},
|
||||
);
|
||||
setModal(MODAL.IMG);
|
||||
IMAGE: () => {
|
||||
setModal(MODAL.EXPORT_IMG);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -817,10 +745,20 @@ export default function ControlPanel({
|
||||
canvas.offsetWidth,
|
||||
canvas.offsetHeight,
|
||||
);
|
||||
doc.save(`${exportData.filename}.pdf`);
|
||||
doc.save(`${title}_${new Date().toISOString()}.pdf`);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
SQL: () => {
|
||||
setModal(MODAL.EXPORT_SQL);
|
||||
},
|
||||
},
|
||||
{
|
||||
JSON: () => {
|
||||
setModal(MODAL.EXPORT_JSON);
|
||||
},
|
||||
},
|
||||
{
|
||||
DRAWDB: () => {
|
||||
const result = JSON.stringify(
|
||||
@ -840,87 +778,7 @@ export default function ControlPanel({
|
||||
const blob = new Blob([result], {
|
||||
type: "text/plain;charset=utf-8",
|
||||
});
|
||||
saveAs(blob, `${exportData.filename}.ddb`);
|
||||
},
|
||||
},
|
||||
],
|
||||
function: () => {},
|
||||
},
|
||||
export_source: {
|
||||
children: [
|
||||
{
|
||||
MySQL: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const src = jsonToMySQL({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: src,
|
||||
extension: "sql",
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
PostgreSQL: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const src = jsonToPostgreSQL({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: src,
|
||||
extension: "sql",
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
SQLite: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const src = jsonToSQLite({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: src,
|
||||
extension: "sql",
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
MariaDB: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const src = jsonToMariaDB({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: src,
|
||||
extension: "sql",
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
MSSQL: () => {
|
||||
setModal(MODAL.CODE);
|
||||
const src = jsonToSQLServer({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: src,
|
||||
extension: "sql",
|
||||
}));
|
||||
saveAs(blob, `${title}_${new Date().toISOString()}.ddb`);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -1166,7 +1024,7 @@ export default function ControlPanel({
|
||||
},
|
||||
};
|
||||
|
||||
useHotkeys("ctrl+i, meta+i", fileImport, { preventDefault: true });
|
||||
useHotkeys("ctrl+i, meta+i", importDiagram, { preventDefault: true });
|
||||
useHotkeys("ctrl+z, meta+z", undo, { preventDefault: true });
|
||||
useHotkeys("ctrl+y, meta+y", redo, { preventDefault: true });
|
||||
useHotkeys("ctrl+s, meta+s", save, { preventDefault: true });
|
||||
@ -1196,20 +1054,21 @@ export default function ControlPanel({
|
||||
});
|
||||
useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true });
|
||||
|
||||
const hideModal = () => {
|
||||
setModal(MODAL.NONE);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{layout.header && header()}
|
||||
{layout.toolbar && toolbar()}
|
||||
<Modal
|
||||
<ModalManager
|
||||
modal={modal}
|
||||
exportData={exportData}
|
||||
setExportData={setExportData}
|
||||
hideModal={hideModal}
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
setPrevTitle={setPrevTitle}
|
||||
setDiagramId={setDiagramId}
|
||||
setModal={setModal}
|
||||
prevTitle={prevTitle}
|
||||
/>
|
||||
<Sidesheet
|
||||
type={sidesheet}
|
||||
|
34
src/components/EditorHeader/Modal/BaseModal.jsx
Normal file
34
src/components/EditorHeader/Modal/BaseModal.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Modal as SemiUIModal } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
modalTitle,
|
||||
okText,
|
||||
onOk,
|
||||
onCancel,
|
||||
okBtnDisabled,
|
||||
width,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SemiUIModal
|
||||
title={modalTitle || ""}
|
||||
visible={true}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
centered
|
||||
closeOnEsc={true}
|
||||
okText={okText || t("confirm")}
|
||||
okButtonProps={{
|
||||
disabled: okBtnDisabled,
|
||||
}}
|
||||
cancelText={t("cancel")}
|
||||
width={width || 600}
|
||||
// bodyStyle={{ maxHeight: window.innerHeight - 280, overflow: "auto" }}
|
||||
>
|
||||
{children}
|
||||
</SemiUIModal>
|
||||
);
|
||||
}
|
80
src/components/EditorHeader/Modal/ExportImage.jsx
Normal file
80
src/components/EditorHeader/Modal/ExportImage.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||
import { Image, Select, Spin } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IMAGE_TYPES } from "../../../data/constants";
|
||||
import ExportModal from "./ExportModal";
|
||||
|
||||
export default function ExportImage({ title, hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const [exportData, setExportData] = useState({
|
||||
data: null,
|
||||
filename: `${title}_${new Date().toISOString()}`,
|
||||
extension: IMAGE_TYPES[0],
|
||||
});
|
||||
|
||||
const changeType = async (type) => {
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: null,
|
||||
extension: type,
|
||||
}));
|
||||
|
||||
const canvasElm = document.getElementById("canvas");
|
||||
|
||||
let dataUrl;
|
||||
switch (type) {
|
||||
case "png":
|
||||
dataUrl = await toPng(canvasElm);
|
||||
break;
|
||||
case "jpeg":
|
||||
dataUrl = await toJpeg(canvasElm, { quality: 0.95 });
|
||||
break;
|
||||
case "svg":
|
||||
dataUrl = await toSvg(canvasElm, {
|
||||
filter: (node) => node.tagName !== "i",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: dataUrl,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
changeType(IMAGE_TYPES[0]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ExportModal
|
||||
modalTitle={t("export_image")}
|
||||
onCancel={hideModal}
|
||||
exportData={exportData}
|
||||
setExportData={setExportData}
|
||||
>
|
||||
<div className="font-semibold mb-1">{t("format")}:</div>
|
||||
<Select
|
||||
className="w-full mb-2"
|
||||
optionList={IMAGE_TYPES.map((type) => ({
|
||||
label: type.toUpperCase(),
|
||||
value: type,
|
||||
}))}
|
||||
value={exportData.extension}
|
||||
onChange={changeType}
|
||||
/>
|
||||
<div className="text-center my-3 h-[280px] flex flex-col justify-center items-center">
|
||||
{exportData.data ? (
|
||||
<Image
|
||||
src={exportData.data}
|
||||
alt="Diagram"
|
||||
className="overflow-auto"
|
||||
/>
|
||||
) : (
|
||||
<Spin size="large" />
|
||||
)}
|
||||
</div>
|
||||
</ExportModal>
|
||||
);
|
||||
}
|
59
src/components/EditorHeader/Modal/ExportJson.jsx
Normal file
59
src/components/EditorHeader/Modal/ExportJson.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||
import {
|
||||
useAreas,
|
||||
useNotes,
|
||||
useTables,
|
||||
useTypes,
|
||||
useSettings,
|
||||
} from "../../../hooks";
|
||||
import ExportModal from "./ExportModal";
|
||||
|
||||
export default function ExportJson({ title, hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const { settings } = useSettings();
|
||||
const { tables, relationships } = useTables();
|
||||
const { notes } = useNotes();
|
||||
const { areas } = useAreas();
|
||||
const { types } = useTypes();
|
||||
|
||||
const rawData = JSON.stringify(
|
||||
{
|
||||
tables: tables,
|
||||
relationships: relationships,
|
||||
notes: notes,
|
||||
subjectAreas: areas,
|
||||
types: types,
|
||||
title: title,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
const [exportData, setExportData] = useState({
|
||||
data: new Blob([rawData], { type: "application/json" }),
|
||||
filename: `${title}_${new Date().toISOString()}`,
|
||||
extension: "json",
|
||||
});
|
||||
|
||||
return (
|
||||
<ExportModal
|
||||
modalTitle={t("export_json")}
|
||||
onCancel={hideModal}
|
||||
exportData={exportData}
|
||||
setExportData={setExportData}
|
||||
>
|
||||
<CodeMirror
|
||||
value={rawData}
|
||||
height="360px"
|
||||
extensions={[json()]}
|
||||
onChange={() => {}}
|
||||
editable={false}
|
||||
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
||||
/>
|
||||
</ExportModal>
|
||||
);
|
||||
}
|
41
src/components/EditorHeader/Modal/ExportModal.jsx
Normal file
41
src/components/EditorHeader/Modal/ExportModal.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function ExportModal({
|
||||
children,
|
||||
modalTitle,
|
||||
exportData,
|
||||
setExportData,
|
||||
onCancel,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onOk = () => {
|
||||
saveAs(exportData.data, `${exportData.filename}.${exportData.extension}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
modalTitle={modalTitle}
|
||||
okText={t("export")}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
okBtnDisabled={!exportData.data}
|
||||
>
|
||||
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
|
||||
<Input
|
||||
className="mb-1"
|
||||
value={exportData.filename}
|
||||
placeholder={t("filename")}
|
||||
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
|
||||
onChange={(value) =>
|
||||
setExportData((prev) => ({ ...prev, filename: value }))
|
||||
}
|
||||
field="filename"
|
||||
/>
|
||||
{children}
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
87
src/components/EditorHeader/Modal/ExportSql.jsx
Normal file
87
src/components/EditorHeader/Modal/ExportSql.jsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { sql } from "@codemirror/lang-sql";
|
||||
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||
import { Select } from "@douyinfe/semi-ui";
|
||||
import { DATABASE_TYPES } from "../../../data/constants";
|
||||
import toSQL from "../../../utils/toSQL";
|
||||
import { useTables, useSettings, useTypes } from "../../../hooks";
|
||||
import ExportModal from "./ExportModal";
|
||||
|
||||
export default function ExportSql({ title, hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const { settings } = useSettings();
|
||||
const { tables, relationships } = useTables();
|
||||
const { types } = useTypes();
|
||||
|
||||
const [options, setOptions] = useState({
|
||||
format: DATABASE_TYPES[0],
|
||||
});
|
||||
|
||||
const [exportData, setExportData] = useState({
|
||||
data: null,
|
||||
rawData: "",
|
||||
filename: `${title}_${new Date().toISOString()}`,
|
||||
extension: "sql",
|
||||
});
|
||||
|
||||
const changeOptions = (options) => {
|
||||
setOptions((prev) => ({
|
||||
...prev,
|
||||
...options,
|
||||
}));
|
||||
};
|
||||
|
||||
const changeData = useCallback(
|
||||
(options) => {
|
||||
const rawData = toSQL[`jsonTo${options.format}`]({
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
types: types,
|
||||
});
|
||||
|
||||
setExportData((prev) => ({
|
||||
...prev,
|
||||
data: new Blob([rawData], {
|
||||
type: "application/json",
|
||||
}),
|
||||
rawData: rawData,
|
||||
}));
|
||||
},
|
||||
[tables, relationships, types],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
changeData(options);
|
||||
}, [options, changeData]);
|
||||
|
||||
return (
|
||||
<ExportModal
|
||||
modalTitle={t("export_source")}
|
||||
onCancel={hideModal}
|
||||
exportData={exportData}
|
||||
setExportData={setExportData}
|
||||
>
|
||||
<div className="font-semibold mb-1">{t("format")}:</div>
|
||||
<Select
|
||||
optionList={DATABASE_TYPES.map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={options.format}
|
||||
className="w-full"
|
||||
onChange={(value) => changeOptions({ format: value })}
|
||||
/>
|
||||
<CodeMirror
|
||||
value={exportData.rawData}
|
||||
height="360px"
|
||||
extensions={[sql()]}
|
||||
onChange={() => {}}
|
||||
editable={false}
|
||||
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
||||
/>
|
||||
</ExportModal>
|
||||
);
|
||||
}
|
@ -1,17 +1,33 @@
|
||||
import { Upload, Banner } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ddbDiagramIsValid,
|
||||
jsonDiagramIsValid,
|
||||
} from "../../../utils/validateSchema";
|
||||
import { Upload, Banner } from "@douyinfe/semi-ui";
|
||||
import { STATUS } from "../../../data/constants";
|
||||
import { useAreas, useNotes, useTables } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
useAreas,
|
||||
useNotes,
|
||||
useTables,
|
||||
useTransform,
|
||||
useUndoRedo,
|
||||
} from "../../../hooks";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function ImportDiagram({ setImportData, error, setError }) {
|
||||
const { areas } = useAreas();
|
||||
const { notes } = useNotes();
|
||||
const { tables, relationships } = useTables();
|
||||
export default function ImportDiagram({ hideModal, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
const { tables, relationships, setTables, setRelationships } = useTables();
|
||||
const { areas, setAreas } = useAreas();
|
||||
const { notes, setNotes } = useNotes();
|
||||
const { setTransform } = useTransform();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
const [importData, setImportData] = useState(null);
|
||||
const [error, setError] = useState({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
});
|
||||
|
||||
const diagramIsEmpty = () => {
|
||||
return (
|
||||
@ -22,69 +38,96 @@ export default function ImportDiagram({ setImportData, error, setError }) {
|
||||
);
|
||||
};
|
||||
|
||||
const beforeUpload = ({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
let jsonObject = null;
|
||||
try {
|
||||
jsonObject = JSON.parse(e.target.result);
|
||||
} catch (error) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message: "The file contains an error.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (f.type === "application/json") {
|
||||
if (!jsonDiagramIsValid(jsonObject)) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message: "The file is missing necessary properties for a diagram.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (f.name.split(".").pop() === "ddb") {
|
||||
if (!ddbDiagramIsValid(jsonObject)) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message: "The file is missing necessary properties for a diagram.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setImportData(jsonObject);
|
||||
if (diagramIsEmpty()) {
|
||||
setError({
|
||||
type: STATUS.OK,
|
||||
message: "Everything looks good. You can now import.",
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
type: STATUS.WARNING,
|
||||
message:
|
||||
"The current diagram is not empty. Importing a new diagram will overwrite the current changes.",
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
};
|
||||
|
||||
const overwriteDiagram = () => {
|
||||
setTables(importData.tables);
|
||||
setRelationships(importData.relationships);
|
||||
setAreas(importData.subjectAreas);
|
||||
setNotes(importData.notes);
|
||||
if (importData.title) {
|
||||
setTitle(importData.title);
|
||||
}
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
if (error.type !== STATUS.ERROR) {
|
||||
setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
|
||||
overwriteDiagram();
|
||||
setImportData(null);
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
hideModal();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseModal
|
||||
modalTitle={t("import_diagram")}
|
||||
okText={t("import")}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
okBtnDisabled={(error && error?.type === STATUS.ERROR) || !importData}
|
||||
>
|
||||
<Upload
|
||||
action="#"
|
||||
beforeUpload={({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
let jsonObject = null;
|
||||
try {
|
||||
jsonObject = JSON.parse(e.target.result);
|
||||
} catch (error) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message: "The file contains an error.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (f.type === "application/json") {
|
||||
if (!jsonDiagramIsValid(jsonObject)) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message:
|
||||
"The file is missing necessary properties for a diagram.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (f.name.split(".").pop() === "ddb") {
|
||||
if (!ddbDiagramIsValid(jsonObject)) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message:
|
||||
"The file is missing necessary properties for a diagram.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setImportData(jsonObject);
|
||||
if (diagramIsEmpty()) {
|
||||
setError({
|
||||
type: STATUS.OK,
|
||||
message: "Everything looks good. You can now import.",
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
type: STATUS.WARNING,
|
||||
message:
|
||||
"The current diagram is not empty. Importing a new diagram will overwrite the current changes.",
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
}}
|
||||
beforeUpload={beforeUpload}
|
||||
draggable={true}
|
||||
dragMainText={t("drag_and_drop_files")}
|
||||
dragSubText={t("support_json_and_ddb")}
|
||||
@ -124,6 +167,6 @@ export default function ImportDiagram({ setImportData, error, setError }) {
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
@ -1,37 +1,107 @@
|
||||
import { Upload, Checkbox, Banner } from "@douyinfe/semi-ui";
|
||||
import { STATUS } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { Parser } from "node-sql-parser";
|
||||
import { astToDiagram } from "../../../utils/astToDiagram";
|
||||
import { STATUS } from "../../../data/constants";
|
||||
import {
|
||||
useAreas,
|
||||
useNotes,
|
||||
useTables,
|
||||
useTransform,
|
||||
useTypes,
|
||||
useUndoRedo,
|
||||
} from "../../../hooks";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function ImportSource({
|
||||
importData,
|
||||
setImportData,
|
||||
error,
|
||||
setError,
|
||||
}) {
|
||||
export default function ImportSource({ hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const { setTables, setRelationships } = useTables();
|
||||
const { setAreas } = useAreas();
|
||||
const { setNotes } = useNotes();
|
||||
const { setTypes } = useTypes();
|
||||
const { setTransform } = useTransform();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
const [importData, setImportData] = useState({
|
||||
src: "",
|
||||
overwrite: true,
|
||||
dbms: "MySQL",
|
||||
});
|
||||
const [error, setError] = useState({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
});
|
||||
|
||||
const beforeUpload = ({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
setImportData((prev) => ({ ...prev, src: e.target.result }));
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
const parser = new Parser();
|
||||
let ast = null;
|
||||
try {
|
||||
ast = parser.astify(importData.src, { database: "MySQL" });
|
||||
} catch (err) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message:
|
||||
err.name +
|
||||
" [Ln " +
|
||||
err.location.start.line +
|
||||
", Col " +
|
||||
err.location.start.column +
|
||||
"]: " +
|
||||
err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const d = astToDiagram(ast);
|
||||
if (importData.overwrite) {
|
||||
setTables(d.tables);
|
||||
setRelationships(d.relationships);
|
||||
setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
|
||||
setNotes([]);
|
||||
setAreas([]);
|
||||
setTypes([]);
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
} else {
|
||||
setTables((prev) => [...prev, ...d.tables]);
|
||||
setRelationships((prev) => [...prev, ...d.relationships]);
|
||||
}
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseModal
|
||||
modalTitle={t("import_diagram")}
|
||||
okText={t("import")}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
okBtnDisabled={
|
||||
(error && error?.type === STATUS.ERROR) || importData.src === ""
|
||||
}
|
||||
>
|
||||
<Upload
|
||||
action="#"
|
||||
beforeUpload={({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
setImportData((prev) => ({ ...prev, src: e.target.result }));
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
}}
|
||||
beforeUpload={beforeUpload}
|
||||
draggable={true}
|
||||
dragMainText={t("drag_and_drop_files")}
|
||||
dragSubText={t("upload_sql_to_generate_diagrams")}
|
||||
@ -92,6 +162,6 @@ export default function ImportSource({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
@ -1,30 +1,33 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import { languages } from "../../../i18n/i18n";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function Language() {
|
||||
export default function Language({ hideModal }) {
|
||||
const { settings } = useSettings();
|
||||
const { i18n } = useTranslation();
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => i18n.changeLanguage(l.code)}
|
||||
className={`space-y-1 py-3 px-4 rounded-md border-2 ${
|
||||
settings.mode === "dark"
|
||||
? "bg-zinc-700 hover:bg-zinc-600"
|
||||
: "bg-zinc-100 hover:bg-zinc-200"
|
||||
} ${i18n.resolvedLanguage === l.code ? "border-zinc-400" : "border-transparent"}`}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="font-semibold">{l.native_name}</div>
|
||||
<div className="opacity-60">{l.code}</div>
|
||||
</div>
|
||||
<div className="text-start">{l.name}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<BaseModal modalTitle={t("language")} onOk={hideModal} onCancel={hideModal}>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => i18n.changeLanguage(l.code)}
|
||||
className={`space-y-1 py-3 px-4 rounded-md border-2 ${
|
||||
settings.mode === "dark"
|
||||
? "bg-zinc-700 hover:bg-zinc-600"
|
||||
: "bg-zinc-100 hover:bg-zinc-200"
|
||||
} ${i18n.resolvedLanguage === l.code ? "border-zinc-400" : "border-transparent"}`}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="font-semibold">{l.native_name}</div>
|
||||
<div className="opacity-60">{l.code}</div>
|
||||
</div>
|
||||
<div className="text-start">{l.name}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
@ -1,341 +0,0 @@
|
||||
import {
|
||||
Spin,
|
||||
Input,
|
||||
Image,
|
||||
Toast,
|
||||
Modal as SemiUIModal,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { MODAL, STATUS } from "../../../data/constants";
|
||||
import { useState } from "react";
|
||||
import { db } from "../../../data/db";
|
||||
import {
|
||||
useAreas,
|
||||
useNotes,
|
||||
useSettings,
|
||||
useTables,
|
||||
useTransform,
|
||||
useTypes,
|
||||
useUndoRedo,
|
||||
} from "../../../hooks";
|
||||
import { saveAs } from "file-saver";
|
||||
import { Parser } from "node-sql-parser";
|
||||
import { astToDiagram } from "../../../utils/astToDiagram";
|
||||
import { getModalTitle, getOkText } from "../../../utils/modalTitles";
|
||||
import Rename from "./Rename";
|
||||
import Open from "./Open";
|
||||
import New from "./New";
|
||||
import ImportDiagram from "./ImportDiagram";
|
||||
import ImportSource from "./ImportSource";
|
||||
import SetTableWidth from "./SetTableWidth";
|
||||
import Language from "./Language";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { sql } from "@codemirror/lang-sql";
|
||||
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const languageExtension = {
|
||||
sql: [sql()],
|
||||
json: [json()],
|
||||
};
|
||||
|
||||
export default function Modal({
|
||||
modal,
|
||||
setModal,
|
||||
title,
|
||||
setTitle,
|
||||
prevTitle,
|
||||
setPrevTitle,
|
||||
setDiagramId,
|
||||
exportData,
|
||||
setExportData,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { setTables, setRelationships } = useTables();
|
||||
const { setNotes } = useNotes();
|
||||
const { setAreas } = useAreas();
|
||||
const { setTypes } = useTypes();
|
||||
const { settings } = useSettings();
|
||||
const { setTransform } = useTransform();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [importSource, setImportSource] = useState({
|
||||
src: "",
|
||||
overwrite: true,
|
||||
dbms: "MySQL",
|
||||
});
|
||||
const [importData, setImportData] = useState(null);
|
||||
const [error, setError] = useState({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
});
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState(-1);
|
||||
const [selectedDiagramId, setSelectedDiagramId] = useState(0);
|
||||
const [saveAsTitle, setSaveAsTitle] = useState(title);
|
||||
|
||||
const overwriteDiagram = () => {
|
||||
setTables(importData.tables);
|
||||
setRelationships(importData.relationships);
|
||||
setAreas(importData.subjectAreas);
|
||||
setNotes(importData.notes);
|
||||
if (importData.title) {
|
||||
setTitle(importData.title);
|
||||
}
|
||||
};
|
||||
|
||||
const loadDiagram = async (id) => {
|
||||
await db.diagrams
|
||||
.get(id)
|
||||
.then((diagram) => {
|
||||
if (diagram) {
|
||||
setDiagramId(diagram.id);
|
||||
setTitle(diagram.name);
|
||||
setTables(diagram.tables);
|
||||
setTypes(diagram.types);
|
||||
setRelationships(diagram.references);
|
||||
setAreas(diagram.areas);
|
||||
setNotes(diagram.notes);
|
||||
setTransform({
|
||||
pan: diagram.pan,
|
||||
zoom: diagram.zoom,
|
||||
});
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
window.name = `d ${diagram.id}`;
|
||||
} else {
|
||||
Toast.error("Oops! Something went wrong.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Toast.error("Oops! Couldn't load diagram.");
|
||||
});
|
||||
};
|
||||
|
||||
const parseSQLAndLoadDiagram = () => {
|
||||
const parser = new Parser();
|
||||
let ast = null;
|
||||
try {
|
||||
ast = parser.astify(importSource.src, { database: "MySQL" });
|
||||
} catch (err) {
|
||||
setError({
|
||||
type: STATUS.ERROR,
|
||||
message:
|
||||
err.name +
|
||||
" [Ln " +
|
||||
err.location.start.line +
|
||||
", Col " +
|
||||
err.location.start.column +
|
||||
"]: " +
|
||||
err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const d = astToDiagram(ast);
|
||||
if (importSource.overwrite) {
|
||||
setTables(d.tables);
|
||||
setRelationships(d.relationships);
|
||||
setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
|
||||
setNotes([]);
|
||||
setAreas([]);
|
||||
setTypes([]);
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
} else {
|
||||
setTables((prev) => [...prev, ...d.tables]);
|
||||
setRelationships((prev) => [...prev, ...d.relationships]);
|
||||
}
|
||||
setModal(MODAL.NONE);
|
||||
};
|
||||
|
||||
const createNewDiagram = (id) => {
|
||||
const newWindow = window.open("/editor");
|
||||
newWindow.name = "lt " + id;
|
||||
};
|
||||
|
||||
const getModalOnOk = async () => {
|
||||
switch (modal) {
|
||||
case MODAL.IMG:
|
||||
saveAs(
|
||||
exportData.data,
|
||||
`${exportData.filename}.${exportData.extension}`,
|
||||
);
|
||||
return;
|
||||
case MODAL.CODE: {
|
||||
const blob = new Blob([exportData.data], {
|
||||
type: "application/json",
|
||||
});
|
||||
saveAs(blob, `${exportData.filename}.${exportData.extension}`);
|
||||
return;
|
||||
}
|
||||
case MODAL.IMPORT:
|
||||
if (error.type !== STATUS.ERROR) {
|
||||
setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
|
||||
overwriteDiagram();
|
||||
setImportData(null);
|
||||
setModal(MODAL.NONE);
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
}
|
||||
return;
|
||||
case MODAL.IMPORT_SRC:
|
||||
parseSQLAndLoadDiagram();
|
||||
return;
|
||||
case MODAL.OPEN:
|
||||
if (selectedDiagramId === 0) return;
|
||||
loadDiagram(selectedDiagramId);
|
||||
setModal(MODAL.NONE);
|
||||
return;
|
||||
case MODAL.RENAME:
|
||||
setPrevTitle(title);
|
||||
setModal(MODAL.NONE);
|
||||
return;
|
||||
case MODAL.SAVEAS:
|
||||
setTitle(saveAsTitle);
|
||||
setModal(MODAL.NONE);
|
||||
return;
|
||||
case MODAL.NEW:
|
||||
setModal(MODAL.NONE);
|
||||
createNewDiagram(selectedTemplateId);
|
||||
return;
|
||||
default:
|
||||
setModal(MODAL.NONE);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const getModalBody = () => {
|
||||
switch (modal) {
|
||||
case MODAL.IMPORT:
|
||||
return (
|
||||
<ImportDiagram
|
||||
setImportData={setImportData}
|
||||
error={error}
|
||||
setError={setError}
|
||||
/>
|
||||
);
|
||||
case MODAL.IMPORT_SRC:
|
||||
return (
|
||||
<ImportSource
|
||||
importData={importSource}
|
||||
setImportData={setImportSource}
|
||||
error={error}
|
||||
setError={setError}
|
||||
/>
|
||||
);
|
||||
case MODAL.NEW:
|
||||
return (
|
||||
<New
|
||||
selectedTemplateId={selectedTemplateId}
|
||||
setSelectedTemplateId={setSelectedTemplateId}
|
||||
/>
|
||||
);
|
||||
case MODAL.RENAME:
|
||||
return <Rename title={title} setTitle={setTitle} />;
|
||||
case MODAL.OPEN:
|
||||
return (
|
||||
<Open
|
||||
selectedDiagramId={selectedDiagramId}
|
||||
setSelectedDiagramId={setSelectedDiagramId}
|
||||
/>
|
||||
);
|
||||
case MODAL.SAVEAS:
|
||||
return (
|
||||
<Input
|
||||
placeholder={t("name")}
|
||||
value={saveAsTitle}
|
||||
onChange={(v) => setSaveAsTitle(v)}
|
||||
/>
|
||||
);
|
||||
case MODAL.CODE:
|
||||
case MODAL.IMG:
|
||||
if (exportData.data !== "" || exportData.data) {
|
||||
return (
|
||||
<>
|
||||
{modal === MODAL.IMG ? (
|
||||
<Image src={exportData.data} alt="Diagram" height={280} />
|
||||
) : (
|
||||
<CodeMirror
|
||||
value={exportData.data}
|
||||
height="360px"
|
||||
extensions={languageExtension[exportData.extension]}
|
||||
onChange={() => {}}
|
||||
editable={false}
|
||||
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
||||
/>
|
||||
)}
|
||||
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
|
||||
<Input
|
||||
value={exportData.filename}
|
||||
placeholder={t("filename")}
|
||||
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
|
||||
onChange={(value) =>
|
||||
setExportData((prev) => ({ ...prev, filename: value }))
|
||||
}
|
||||
field="filename"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="text-center my-3">
|
||||
<Spin tip={t("loading")} size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case MODAL.TABLE_WIDTH:
|
||||
return <SetTableWidth />;
|
||||
case MODAL.LANGUAGE:
|
||||
return <Language />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SemiUIModal
|
||||
title={getModalTitle(modal)}
|
||||
visible={modal !== MODAL.NONE}
|
||||
onOk={getModalOnOk}
|
||||
afterClose={() => {
|
||||
setExportData(() => ({
|
||||
data: "",
|
||||
extension: "",
|
||||
filename: `${title}_${new Date().toISOString()}`,
|
||||
}));
|
||||
setError({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
});
|
||||
setImportData(null);
|
||||
setImportSource({
|
||||
src: "",
|
||||
overwrite: true,
|
||||
dbms: "MySQL",
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
if (modal === MODAL.RENAME) setTitle(prevTitle);
|
||||
setModal(MODAL.NONE);
|
||||
}}
|
||||
centered
|
||||
closeOnEsc={true}
|
||||
okText={getOkText(modal)}
|
||||
okButtonProps={{
|
||||
disabled:
|
||||
(error && error?.type === STATUS.ERROR) ||
|
||||
(modal === MODAL.IMPORT &&
|
||||
(error.type === STATUS.ERROR || !importData)) ||
|
||||
(modal === MODAL.RENAME && title === "") ||
|
||||
((modal === MODAL.IMG || modal === MODAL.CODE) && !exportData.data) ||
|
||||
(modal === MODAL.SAVEAS && saveAsTitle === "") ||
|
||||
(modal === MODAL.IMPORT_SRC && importSource.src === ""),
|
||||
}}
|
||||
cancelText={t("cancel")}
|
||||
width={modal === MODAL.NEW ? 740 : 600}
|
||||
bodyStyle={{ maxHeight: window.innerHeight - 280, overflow: "auto" }}
|
||||
>
|
||||
{getModalBody()}
|
||||
</SemiUIModal>
|
||||
);
|
||||
}
|
@ -1,45 +1,67 @@
|
||||
import { db } from "../../../data/db";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import Thumbnail from "../../Thumbnail";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { db } from "../../../data/db";
|
||||
import Thumbnail from "../../Thumbnail";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
||||
const { settings } = useSettings();
|
||||
export default function New({ hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const { settings } = useSettings();
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState(-1);
|
||||
|
||||
const templates = useLiveQuery(() => db.templates.toArray());
|
||||
|
||||
const createNewDiagram = (id) => {
|
||||
const newWindow = window.open("/editor");
|
||||
newWindow.name = "lt " + id;
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
hideModal();
|
||||
createNewDiagram(selectedTemplateId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-2 overflow-auto px-1">
|
||||
<div onClick={() => setSelectedTemplateId(0)}>
|
||||
<div
|
||||
className={`rounded-md h-[180px] border-2 hover:border-dashed ${
|
||||
selectedTemplateId === 0 ? "border-blue-400" : "border-zinc-400"
|
||||
}`}
|
||||
>
|
||||
<Thumbnail i={0} diagram={{}} zoom={0.24} theme={settings.mode} />
|
||||
</div>
|
||||
<div className="text-center mt-1">{t("blank")}</div>
|
||||
</div>
|
||||
{templates?.map((temp, i) => (
|
||||
<div key={i} onClick={() => setSelectedTemplateId(temp.id)}>
|
||||
<BaseModal
|
||||
modalTitle={t("create_new_diagram")}
|
||||
okText={t("create")}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
width={740}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-2 overflow-auto px-1">
|
||||
<div onClick={() => setSelectedTemplateId(0)}>
|
||||
<div
|
||||
className={`rounded-md h-[180px] border-2 hover:border-dashed ${
|
||||
selectedTemplateId === temp.id
|
||||
? "border-blue-400"
|
||||
: "border-zinc-400"
|
||||
selectedTemplateId === 0 ? "border-blue-400" : "border-zinc-400"
|
||||
}`}
|
||||
>
|
||||
<Thumbnail
|
||||
i={temp.id}
|
||||
diagram={temp}
|
||||
zoom={0.24}
|
||||
theme={settings.mode}
|
||||
/>
|
||||
<Thumbnail i={0} diagram={{}} zoom={0.24} theme={settings.mode} />
|
||||
</div>
|
||||
<div className="text-center mt-1">{temp.title}</div>
|
||||
<div className="text-center mt-1">{t("blank")}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{templates?.map((temp, i) => (
|
||||
<div key={i} onClick={() => setSelectedTemplateId(temp.id)}>
|
||||
<div
|
||||
className={`rounded-md h-[180px] border-2 hover:border-dashed ${
|
||||
selectedTemplateId === temp.id
|
||||
? "border-blue-400"
|
||||
: "border-zinc-400"
|
||||
}`}
|
||||
>
|
||||
<Thumbnail
|
||||
i={temp.id}
|
||||
diagram={temp}
|
||||
zoom={0.24}
|
||||
theme={settings.mode}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center mt-1">{temp.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,29 @@
|
||||
import { db } from "../../../data/db";
|
||||
import { Banner } from "@douyinfe/semi-ui";
|
||||
import { Banner, Toast } from "@douyinfe/semi-ui";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { db } from "../../../data/db";
|
||||
import {
|
||||
useAreas,
|
||||
useNotes,
|
||||
useTables,
|
||||
useTransform,
|
||||
useTypes,
|
||||
useUndoRedo,
|
||||
} from "../../../hooks";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||
const diagrams = useLiveQuery(() => db.diagrams.toArray());
|
||||
export default function Open({ hideModal, setDiagramId, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
const { setTables, setRelationships } = useTables();
|
||||
const { setNotes } = useNotes();
|
||||
const { setAreas } = useAreas();
|
||||
const { setTypes } = useTypes();
|
||||
const { setTransform } = useTransform();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
const diagrams = useLiveQuery(() => db.diagrams.toArray());
|
||||
const [selectedDiagramId, setSelectedDiagramId] = useState(0);
|
||||
|
||||
const getDiagramSize = (d) => {
|
||||
const size = JSON.stringify(d).length;
|
||||
@ -18,8 +36,48 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||
|
||||
return sizeStr;
|
||||
};
|
||||
|
||||
const loadDiagram = async (id) => {
|
||||
await db.diagrams
|
||||
.get(id)
|
||||
.then((diagram) => {
|
||||
if (diagram) {
|
||||
setDiagramId(diagram.id);
|
||||
setTitle(diagram.name);
|
||||
setTables(diagram.tables);
|
||||
setTypes(diagram.types);
|
||||
setRelationships(diagram.references);
|
||||
setAreas(diagram.areas);
|
||||
setNotes(diagram.notes);
|
||||
setTransform({
|
||||
pan: diagram.pan,
|
||||
zoom: diagram.zoom,
|
||||
});
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
window.name = `d ${diagram.id}`;
|
||||
} else {
|
||||
Toast.error("Oops! Something went wrong.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Toast.error("Oops! Couldn't load diagram.");
|
||||
});
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
if (selectedDiagramId === 0) return;
|
||||
loadDiagram(selectedDiagramId);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseModal
|
||||
modalTitle={t("open_diagram")}
|
||||
okText={t("open")}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
>
|
||||
{diagrams?.length === 0 ? (
|
||||
<Banner
|
||||
fullMode={false}
|
||||
@ -70,6 +128,6 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,34 @@
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function Rename({ title, setTitle }) {
|
||||
export default function Rename({ hideModal, title, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
const originalTitle = useRef(title);
|
||||
|
||||
const onCancel = () => {
|
||||
setTitle(originalTitle.current);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
placeholder={t("name")}
|
||||
value={title}
|
||||
onChange={(v) => setTitle(v)}
|
||||
/>
|
||||
<BaseModal
|
||||
modalTitle={t("rename_diagram")}
|
||||
okText={t("rename")}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
okBtnDisabled={title === ""}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("name")}
|
||||
value={title}
|
||||
onChange={(v) => setTitle(v)}
|
||||
/>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
30
src/components/EditorHeader/Modal/SaveAs.jsx
Normal file
30
src/components/EditorHeader/Modal/SaveAs.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function SaveAs({ hideModal, title, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
const [saveAsTitle, setSaveAsTitle] = useState(title);
|
||||
|
||||
const onOk = () => {
|
||||
setTitle(saveAsTitle);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
modalTitle={t("save_as")}
|
||||
okText={t("save_as")}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
okBtnDisabled={saveAsTitle === ""}
|
||||
>
|
||||
<Input
|
||||
placeholder={t("name")}
|
||||
value={saveAsTitle}
|
||||
onChange={(v) => setSaveAsTitle(v)}
|
||||
/>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
@ -1,17 +1,26 @@
|
||||
import { InputNumber } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import BaseModal from "./BaseModal";
|
||||
|
||||
export default function SetTableWidth() {
|
||||
export default function SetTableWidth({ hideModal }) {
|
||||
const { t } = useTranslation();
|
||||
const { settings, setSettings } = useSettings();
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
value={settings.tableWidth}
|
||||
onChange={(c) => {
|
||||
if (c < 180) return;
|
||||
setSettings((prev) => ({ ...prev, tableWidth: c }));
|
||||
}}
|
||||
/>
|
||||
<BaseModal
|
||||
modalTitle={t("table_width")}
|
||||
onOk={hideModal}
|
||||
onCancel={hideModal}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
value={settings.tableWidth}
|
||||
onChange={(c) => {
|
||||
if (c < 180) return;
|
||||
setSettings((prev) => ({ ...prev, tableWidth: c }));
|
||||
}}
|
||||
/>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
53
src/components/EditorHeader/ModalManager.jsx
Normal file
53
src/components/EditorHeader/ModalManager.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { MODAL } from "../../data/constants";
|
||||
import Rename from "./Modal/Rename";
|
||||
import Open from "./Modal/Open";
|
||||
import New from "./Modal/New";
|
||||
import ImportDiagram from "./Modal/ImportDiagram";
|
||||
import ImportSource from "./Modal/ImportSource";
|
||||
import SetTableWidth from "./Modal/SetTableWidth";
|
||||
import Language from "./Modal/Language";
|
||||
import ExportImage from "./Modal/ExportImage";
|
||||
import ExportJson from "./Modal/ExportJson";
|
||||
import ExportSql from "./Modal/ExportSql";
|
||||
import SaveAs from "./Modal/SaveAs";
|
||||
|
||||
export default function ModalManager({
|
||||
modal,
|
||||
hideModal,
|
||||
title,
|
||||
setTitle,
|
||||
setDiagramId,
|
||||
}) {
|
||||
switch (modal) {
|
||||
case MODAL.EXPORT_IMG:
|
||||
return <ExportImage hideModal={hideModal} title={title} />;
|
||||
case MODAL.EXPORT_SQL:
|
||||
return <ExportSql hideModal={hideModal} title={title} />;
|
||||
case MODAL.EXPORT_JSON:
|
||||
return <ExportJson hideModal={hideModal} title={title} />;
|
||||
case MODAL.IMPORT_DIAGRAM:
|
||||
return <ImportDiagram hideModal={hideModal} setTitle={setTitle} />;
|
||||
case MODAL.IMPORT_SRC:
|
||||
return <ImportSource hideModal={hideModal} />;
|
||||
case MODAL.NEW:
|
||||
return <New hideModal={hideModal} />;
|
||||
case MODAL.OPEN:
|
||||
return (
|
||||
<Open
|
||||
hideModal={hideModal}
|
||||
setDiagramId={setDiagramId}
|
||||
setTitle={setTitle}
|
||||
/>
|
||||
);
|
||||
case MODAL.SAVEAS:
|
||||
return <SaveAs hideModal={hideModal} title={title} setTitle={setTitle} />;
|
||||
case MODAL.RENAME:
|
||||
return <Rename hideModal={hideModal} title={title} setTitle={setTitle} />;
|
||||
case MODAL.TABLE_WIDTH:
|
||||
return <SetTableWidth hideModal={hideModal} />;
|
||||
case MODAL.LANGUAGE:
|
||||
return <Language hideModal={hideModal} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
@ -105,16 +105,17 @@ export const State = {
|
||||
|
||||
export const MODAL = {
|
||||
NONE: 0,
|
||||
IMG: 1,
|
||||
CODE: 2,
|
||||
IMPORT: 3,
|
||||
NEW: 1,
|
||||
OPEN: 2,
|
||||
SAVEAS: 3,
|
||||
RENAME: 4,
|
||||
OPEN: 5,
|
||||
SAVEAS: 6,
|
||||
NEW: 7,
|
||||
IMPORT_SRC: 8,
|
||||
TABLE_WIDTH: 9,
|
||||
LANGUAGE: 10,
|
||||
IMPORT_SRC: 5,
|
||||
IMPORT_DIAGRAM: 6,
|
||||
TABLE_WIDTH: 7,
|
||||
LANGUAGE: 8,
|
||||
EXPORT_IMG: 9,
|
||||
EXPORT_SQL: 10,
|
||||
EXPORT_JSON: 11,
|
||||
};
|
||||
|
||||
export const STATUS = {
|
||||
@ -129,3 +130,13 @@ export const SIDESHEET = {
|
||||
TODO: 1,
|
||||
TIMELINE: 2,
|
||||
};
|
||||
|
||||
export const DATABASE_TYPES = [
|
||||
"MySQL",
|
||||
"MariaDB",
|
||||
"PostgreSQL",
|
||||
"SQLite",
|
||||
"MSSQL",
|
||||
];
|
||||
|
||||
export const IMAGE_TYPES = ["png", "jpeg", "svg"];
|
||||
|
@ -165,7 +165,7 @@ const da = {
|
||||
no_values_for_field:
|
||||
"'{{fieldName}}' felt fra tabellen '{{tableName}}' er af type `{{type}}` men ingen værdi er blevet specificeret",
|
||||
default_doesnt_match_type:
|
||||
"Standardværdien for feltet '{{fieldName}}' i tabellen '{{table.name}}' stemmer ikke overens med dens type",
|
||||
"Standardværdien for feltet '{{fieldName}}' i tabellen '{{tableName}}' stemmer ikke overens med dens type",
|
||||
not_null_is_null:
|
||||
"'{{fieldName}}' felt fra tabellen '{{tableName}}' er IKKE NUL, men har standardværdien NUL",
|
||||
duplicate_fields:
|
||||
|
@ -168,7 +168,7 @@ const de = {
|
||||
no_values_for_field:
|
||||
"Das Feld '{{fieldName}}' der Tabelle '{{tableName}}' ist vom Typ `{{type}}`, aber es wurden keine Werte angegeben",
|
||||
default_doesnt_match_type:
|
||||
"Der Standardwert für das Feld '{{fieldName}}' in der Tabelle '{{table.name}}' entspricht nicht seinem Typ",
|
||||
"Der Standardwert für das Feld '{{fieldName}}' in der Tabelle '{{tableName}}' entspricht nicht seinem Typ",
|
||||
not_null_is_null:
|
||||
"Das Feld '{{fieldName}}' der Tabelle '{{tableName}}' ist NOT NULL, hat aber standardmäßig NULL",
|
||||
duplicate_fields:
|
||||
|
@ -165,7 +165,7 @@ const en = {
|
||||
no_values_for_field:
|
||||
"'{{fieldName}}' field of table '{{tableName}}' is of type `{{type}}` but no values have been specified",
|
||||
default_doesnt_match_type:
|
||||
"Default value for field '{{fieldName}}' in table '{{table.name}}' does not match its type",
|
||||
"Default value for field '{{fieldName}}' in table '{{tableName}}' does not match its type",
|
||||
not_null_is_null:
|
||||
"'{{fieldName}}' field of table '{{tableName}}' is NOT NULL but has default NULL",
|
||||
duplicate_fields:
|
||||
@ -212,6 +212,8 @@ const en = {
|
||||
edit_relationship: "{{extra}} Edit relationship {{refName}}",
|
||||
delete_relationship: "Delete relationship {{refName}}",
|
||||
not_found: "Not found",
|
||||
export_json: "Export json",
|
||||
format: "Format",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -165,7 +165,7 @@ const es = {
|
||||
no_values_for_field:
|
||||
"El campo '{{fieldName}}' de la tabla '{{tableName}}' es de tipo `{{type}}` pero no se han especificado valores",
|
||||
default_doesnt_match_type:
|
||||
"El valor predeterminado para el campo '{{fieldName}}' en la tabla '{{table.name}}' no coincide con su tipo",
|
||||
"El valor predeterminado para el campo '{{fieldName}}' en la tabla '{{tableName}}' no coincide con su tipo",
|
||||
not_null_is_null:
|
||||
"El campo '{{fieldName}}' de la tabla '{{tableName}}' es NOT NULL pero tiene NULL por defecto",
|
||||
duplicate_fields:
|
||||
|
@ -156,7 +156,7 @@ const fa = {
|
||||
empty_field_name: "فیلد خالی name در جدول '{{tableName}}'",
|
||||
empty_field_type: "فیلد خالی type در جدول '{{tableName}}'",
|
||||
no_values_for_field: "فیلد '{{fieldName}}' جدول '{{tableName}}' از نوع {{type}} است اما هیچ مقداری مشخص نشده است",
|
||||
default_doesnt_match_type: "مقدار پیشفرض برای فیلد '{{fieldName}}' در جدول '{{table.name}}' با نوع آن مطابقت ندارد",
|
||||
default_doesnt_match_type: "مقدار پیشفرض برای فیلد '{{fieldName}}' در جدول '{{tableName}}' با نوع آن مطابقت ندارد",
|
||||
not_null_is_null: "فیلد '{{fieldName}}' جدول '{{tableName}}' غیر خالی است اما پیشفرض آن خالی است",
|
||||
duplicate_fields: "فیلدهای تکراری جدول به نام '{{fieldName}}' در جدول '{{tableName}}'",
|
||||
duplicate_index: "شاخص تکراری به نام '{{indexName}}' در جدول '{{tableName}}'",
|
||||
@ -198,9 +198,8 @@ const fa = {
|
||||
edit_relationship: "{{extra}} ویرایش ارتباط {{refName}}",
|
||||
delete_relationship: "حذف ارتباط {{refName}}",
|
||||
not_found: "یافت نشد"
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export { fa,persian };
|
||||
|
@ -165,7 +165,7 @@ const pt = {
|
||||
no_values_for_field:
|
||||
"O campo '{{fieldName}}' da tabela '{{tableName}}' é do tipo `{{type}}`, mas nenhum valor foi especificado",
|
||||
default_doesnt_match_type:
|
||||
"O valor padrão para o campo '{{fieldName}}' na tabela '{{table.name}}' não corresponde ao seu tipo",
|
||||
"O valor padrão para o campo '{{fieldName}}' na tabela '{{tableName}}' não corresponde ao seu tipo",
|
||||
not_null_is_null:
|
||||
"O campo '{{fieldName}}' da tabela '{{tableName}}' é NOT NULL mas tem o valor padrão NULL",
|
||||
duplicate_fields:
|
||||
|
@ -112,7 +112,7 @@ const vi = {
|
||||
size: "Kích cỡ",
|
||||
precision: "Độ chính xác",
|
||||
set_precision: "Đặt độ chính xác: (kích thước, chữ số)",
|
||||
use_for_batch_input: "Sử dụng , để nhập hàng loạt",
|
||||
use_for_batch_input: "Sử dụng để nhập hàng loạt",
|
||||
indices: "Chỉ số",
|
||||
add_index: "Thêm chỉ mục",
|
||||
select_fields: "Chọn các trường",
|
||||
@ -123,9 +123,9 @@ const vi = {
|
||||
on_update: "Khi cập nhật",
|
||||
on_delete: "Khi xóa",
|
||||
swap: "Tráo đổi",
|
||||
one_to_one: "Một đối một",
|
||||
one_to_many: "Một đến nhiều",
|
||||
many_to_one: "Nhiều thành một",
|
||||
one_to_one: "Một với một",
|
||||
one_to_many: "Một với nhiều",
|
||||
many_to_one: "Nhiều với một",
|
||||
content: "Nội dung",
|
||||
types_info: "Tính năng này dành cho các DBMS quan hệ đối tượng như PostgreSQL.\nNếu được sử dụng cho MySQL hoặc MariaDB, loại JSON sẽ được tạo bằng kiểm tra xác thực json tương ứng.\nNếu được sử dụng cho SQLite, nó sẽ được dịch sang BLOB.\nNếu được sử dụng cho MSSQL một bí danh loại cho trường đầu tiên sẽ được tạo.",
|
||||
table_deleted: "Đã xóa bảng",
|
||||
@ -156,7 +156,7 @@ const vi = {
|
||||
empty_field_name: "Trường trống `name` trong bảng '{{tableName}}'",
|
||||
empty_field_type: "Trường trống `loại` trong bảng '{{tableName}}'",
|
||||
no_values_for_field: "Trường '{{fieldName}}' của bảng '{{tableName}}' thuộc loại `{{type}}` nhưng không có giá trị nào được chỉ định",
|
||||
default_doesnt_match_type: "Giá trị mặc định cho trường '{{fieldName}}' trong bảng '{{table.name}}' không khớp với loại của nó",
|
||||
default_doesnt_match_type: "Giá trị mặc định cho trường '{{fieldName}}' trong bảng '{{tableName}}' không khớp với loại của nó",
|
||||
not_null_is_null: "Trường '{{fieldName}}' của bảng '{{tableName}}' là NOT NULL nhưng đang là NULL",
|
||||
duplicate_fields: "Các trường bảng trùng lặp theo tên '{{fieldName}}' trong bảng '{{tableName}}'",
|
||||
duplicate_index: "Chỉ mục trùng lặp theo tên '{{indexName}}' trong bảng '{{tableName}}'",
|
||||
@ -198,6 +198,8 @@ const vi = {
|
||||
edit_relationship: "{{extra}} Chỉnh sửa quan hệ {{refName}}",
|
||||
delete_relationship: "Xóa quan hệ {{refName}}",
|
||||
not_found: "Không tìm thấy",
|
||||
export_json: "Xuất json",
|
||||
format: "Định dạng",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { MODAL } from "../data/constants";
|
||||
import i18n from "../i18n/i18n";
|
||||
|
||||
export const getModalTitle = (modal) => {
|
||||
switch (modal) {
|
||||
case MODAL.IMPORT:
|
||||
case MODAL.IMPORT_SRC:
|
||||
return i18n.t("import_diagram");
|
||||
case MODAL.CODE:
|
||||
return i18n.t("export_source");
|
||||
case MODAL.IMG:
|
||||
return i18n.t("export_image");
|
||||
case MODAL.RENAME:
|
||||
return i18n.t("rename_diagram");
|
||||
case MODAL.OPEN:
|
||||
return i18n.t("open_diagram");
|
||||
case MODAL.SAVEAS:
|
||||
return i18n.t("save_as");
|
||||
case MODAL.NEW:
|
||||
return i18n.t("create_new_diagram");
|
||||
case MODAL.TABLE_WIDTH:
|
||||
return i18n.t("table_width");
|
||||
case MODAL.LANGUAGE:
|
||||
return i18n.t("language");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const getOkText = (modal) => {
|
||||
switch (modal) {
|
||||
case MODAL.IMPORT:
|
||||
case MODAL.IMPORT_SRC:
|
||||
return i18n.t("import");
|
||||
case MODAL.CODE:
|
||||
case MODAL.IMG:
|
||||
return i18n.t("export");
|
||||
case MODAL.RENAME:
|
||||
return i18n.t("rename");
|
||||
case MODAL.OPEN:
|
||||
return i18n.t("open");
|
||||
case MODAL.SAVEAS:
|
||||
return i18n.t("save_as");
|
||||
case MODAL.NEW:
|
||||
return i18n.t("create");
|
||||
default:
|
||||
return i18n.t("confirm");
|
||||
}
|
||||
};
|
@ -39,94 +39,143 @@ export function generateSchema(type) {
|
||||
)}\n\t\t\t},\n\t\t\t"additionalProperties": false\n\t\t}`;
|
||||
}
|
||||
|
||||
export function getTypeString(field, dbms = "mysql", baseType = false) {
|
||||
if (dbms === "mysql") {
|
||||
if (field.type === "UUID") {
|
||||
return `VARCHAR(36)`;
|
||||
}
|
||||
if (hasPrecision(field.type) || isSized(field.type)) {
|
||||
return `${field.type}${field.size ? `(${field.size})` : ""}`;
|
||||
}
|
||||
if (field.type === "SET" || field.type === "ENUM") {
|
||||
return `${field.type}(${field.values.map((v) => `"${v}"`).join(", ")})`;
|
||||
}
|
||||
if (!sqlDataTypes.includes(field.type)) {
|
||||
return "JSON";
|
||||
}
|
||||
return field.type;
|
||||
} else if (dbms === "postgres") {
|
||||
if (field.type === "SMALLINT" && field.increment) {
|
||||
return "smallserial";
|
||||
}
|
||||
if (field.type === "INT" && field.increment) {
|
||||
return "serial";
|
||||
}
|
||||
if (field.type === "BIGINT" && field.increment) {
|
||||
return "bigserial";
|
||||
}
|
||||
if (field.type === "ENUM") {
|
||||
return `${field.name}_t`;
|
||||
}
|
||||
if (field.type === "SET") {
|
||||
return `${field.name}_t[]`;
|
||||
}
|
||||
if (field.type === "TIMESTAMP") {
|
||||
return "TIMESTAMPTZ";
|
||||
}
|
||||
if (field.type === "DATETIME") {
|
||||
return `timestamp`;
|
||||
}
|
||||
if (isSized(field.type)) {
|
||||
const type =
|
||||
field.type === "BINARY"
|
||||
? "bit"
|
||||
: field.type === "VARBINARY"
|
||||
? "bit varying"
|
||||
: field.type.toLowerCase();
|
||||
return `${type}(${field.size})`;
|
||||
}
|
||||
if (hasPrecision(field.type) && field.size !== "") {
|
||||
return `${field.type}${field.size}`;
|
||||
}
|
||||
return field.type.toLowerCase();
|
||||
} else if (dbms === "mssql") {
|
||||
let type = field.type;
|
||||
switch (field.type) {
|
||||
case "ENUM":
|
||||
return baseType
|
||||
? "NVARCHAR(255)"
|
||||
: `NVARCHAR(255) CHECK([${field.name}] in (${field.values
|
||||
.map((v) => `'${v}'`)
|
||||
.join(", ")}))`;
|
||||
case "VARCHAR":
|
||||
type = `NVARCHAR`;
|
||||
break;
|
||||
case "UUID":
|
||||
type = "UNIQUEIDENTIFIER";
|
||||
break;
|
||||
case "DOUBLE":
|
||||
type = "FLOAT";
|
||||
break;
|
||||
case "BOOLEAN":
|
||||
return "BIT";
|
||||
case "SET":
|
||||
return "NVARCHAR(255)";
|
||||
case "BLOB":
|
||||
return "VARBINARY(MAX)";
|
||||
case "JSON":
|
||||
return "NVARCHAR(MAX)";
|
||||
case "TEXT":
|
||||
return "TEXT";
|
||||
default:
|
||||
type = field.type;
|
||||
break;
|
||||
}
|
||||
if (isSized(field.type)) {
|
||||
return `${type}(${field.size})`;
|
||||
}
|
||||
|
||||
return type;
|
||||
export function getMysqlType(field) {
|
||||
if (field.type === "UUID") {
|
||||
return `VARCHAR(36)`;
|
||||
}
|
||||
if (hasPrecision(field.type) || isSized(field.type)) {
|
||||
return `${field.type}${field.size ? `(${field.size})` : ""}`;
|
||||
}
|
||||
if (field.type === "SET" || field.type === "ENUM") {
|
||||
return `${field.type}(${field.values.map((v) => `"${v}"`).join(", ")})`;
|
||||
}
|
||||
if (!sqlDataTypes.includes(field.type)) {
|
||||
return "JSON";
|
||||
}
|
||||
return field.type;
|
||||
}
|
||||
|
||||
export function getPostgreSQLType(field) {
|
||||
if (field.type === "SMALLINT" && field.increment) {
|
||||
return "smallserial";
|
||||
}
|
||||
if (field.type === "INT" && field.increment) {
|
||||
return "serial";
|
||||
}
|
||||
if (field.type === "BIGINT" && field.increment) {
|
||||
return "bigserial";
|
||||
}
|
||||
if (field.type === "ENUM") {
|
||||
return `${field.name}_t`;
|
||||
}
|
||||
if (field.type === "SET") {
|
||||
return `${field.name}_t[]`;
|
||||
}
|
||||
if (field.type === "TIMESTAMP") {
|
||||
return "TIMESTAMPTZ";
|
||||
}
|
||||
if (field.type === "DATETIME") {
|
||||
return `timestamp`;
|
||||
}
|
||||
if (isSized(field.type)) {
|
||||
const type =
|
||||
field.type === "BINARY"
|
||||
? "bit"
|
||||
: field.type === "VARBINARY"
|
||||
? "bit varying"
|
||||
: field.type.toLowerCase();
|
||||
return `${type}(${field.size})`;
|
||||
}
|
||||
if (hasPrecision(field.type) && field.size !== "") {
|
||||
return `${field.type}${field.size}`;
|
||||
}
|
||||
return field.type.toLowerCase();
|
||||
}
|
||||
|
||||
export function getMSSQLType(field, baseType = false) {
|
||||
let type = field.type;
|
||||
switch (field.type) {
|
||||
case "ENUM":
|
||||
return baseType
|
||||
? "NVARCHAR(255)"
|
||||
: `NVARCHAR(255) CHECK([${field.name}] in (${field.values
|
||||
.map((v) => `'${v}'`)
|
||||
.join(", ")}))`;
|
||||
case "VARCHAR":
|
||||
type = `NVARCHAR`;
|
||||
break;
|
||||
case "UUID":
|
||||
type = "UNIQUEIDENTIFIER";
|
||||
break;
|
||||
case "DOUBLE":
|
||||
type = "FLOAT";
|
||||
break;
|
||||
case "BOOLEAN":
|
||||
return "BIT";
|
||||
case "SET":
|
||||
return "NVARCHAR(255)";
|
||||
case "BLOB":
|
||||
return "VARBINARY(MAX)";
|
||||
case "JSON":
|
||||
return "NVARCHAR(MAX)";
|
||||
case "TEXT":
|
||||
return "TEXT";
|
||||
default:
|
||||
type = field.type;
|
||||
break;
|
||||
}
|
||||
if (isSized(field.type)) {
|
||||
return `${type}(${field.size})`;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getSQLiteType(field) {
|
||||
switch (field.type) {
|
||||
case "INT":
|
||||
case "SMALLINT":
|
||||
case "BIGINT":
|
||||
case "BOOLEAN":
|
||||
return "INTEGER";
|
||||
case "DECIMAL":
|
||||
case "NUMERIC":
|
||||
case "FLOAT":
|
||||
case "DOUBLE":
|
||||
case "REAL":
|
||||
return "REAL";
|
||||
case "CHAR":
|
||||
case "VARCHAR":
|
||||
case "UUID":
|
||||
case "TEXT":
|
||||
case "DATE":
|
||||
case "TIME":
|
||||
case "TIMESTAMP":
|
||||
case "DATETIME":
|
||||
case "BINARY":
|
||||
case "VARBINARY":
|
||||
return "TEXT";
|
||||
case "ENUM":
|
||||
return `TEXT CHECK("${field.name}" in (${field.values
|
||||
.map((v) => `'${v}'`)
|
||||
.join(", ")}))`;
|
||||
default:
|
||||
return "BLOB";
|
||||
}
|
||||
}
|
||||
|
||||
export function getTypeString(field, dbms = "mysql", baseType = false) {
|
||||
if (dbms === "mysql" || dbms === "mariadb") {
|
||||
return getMysqlType(field);
|
||||
} else if (dbms === "postgressql") {
|
||||
return getPostgreSQLType(field);
|
||||
} else if (dbms === "mssql") {
|
||||
return getMSSQLType(field, baseType);
|
||||
} else if (dbms === "sqlite") {
|
||||
return getSQLiteType(field);
|
||||
}
|
||||
|
||||
return field.type;
|
||||
}
|
||||
|
||||
export function hasQuotes(type) {
|
||||
@ -181,7 +230,7 @@ export function jsonToMySQL(obj) {
|
||||
)}", \`${field.name}\`))`
|
||||
: ""
|
||||
: ` CHECK(${field.check})`
|
||||
}${field.comment ? ` COMMENT '${field.comment}'` : ''}`,
|
||||
}${field.comment ? ` COMMENT '${field.comment}'` : ""}`,
|
||||
)
|
||||
.join(",\n")}${
|
||||
table.fields.filter((f) => f.primary).length > 0
|
||||
@ -190,7 +239,7 @@ export function jsonToMySQL(obj) {
|
||||
.map((f) => `\`${f.name}\``)
|
||||
.join(", ")})`
|
||||
: ""
|
||||
}\n)${table.comment ? ` COMMENT='${table.comment}'` : ''};\n${
|
||||
}\n)${table.comment ? ` COMMENT='${table.comment}'` : ""};\n${
|
||||
table.indices.length > 0
|
||||
? `\n${table.indices.map(
|
||||
(i) =>
|
||||
@ -233,14 +282,14 @@ export function jsonToPostgreSQL(obj) {
|
||||
`${
|
||||
type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
}CREATE TYPE ${type.name} AS (\n${type.fields
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`)
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgressql")}`)
|
||||
.join("\n")}\n);`
|
||||
);
|
||||
} else {
|
||||
return `${
|
||||
type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
}CREATE TYPE ${type.name} AS (\n${type.fields
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`)
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgressql")}`)
|
||||
.join("\n")}\n);`;
|
||||
}
|
||||
})}\n${obj.tables
|
||||
@ -263,7 +312,7 @@ export function jsonToPostgreSQL(obj) {
|
||||
(field) =>
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${
|
||||
field.name
|
||||
}" ${getTypeString(field, "postgres")}${
|
||||
}" ${getTypeString(field, "postgressql")}${
|
||||
field.notNull ? " NOT NULL" : ""
|
||||
}${
|
||||
field.default !== "" ? ` DEFAULT ${parseDefault(field)}` : ""
|
||||
@ -305,39 +354,6 @@ export function jsonToPostgreSQL(obj) {
|
||||
.join("\n")}`;
|
||||
}
|
||||
|
||||
export function getSQLiteType(field) {
|
||||
switch (field.type) {
|
||||
case "INT":
|
||||
case "SMALLINT":
|
||||
case "BIGINT":
|
||||
case "BOOLEAN":
|
||||
return "INTEGER";
|
||||
case "DECIMAL":
|
||||
case "NUMERIC":
|
||||
case "FLOAT":
|
||||
case "DOUBLE":
|
||||
case "REAL":
|
||||
return "REAL";
|
||||
case "CHAR":
|
||||
case "VARCHAR":
|
||||
case "UUID":
|
||||
case "TEXT":
|
||||
case "DATE":
|
||||
case "TIME":
|
||||
case "TIMESTAMP":
|
||||
case "DATETIME":
|
||||
case "BINARY":
|
||||
case "VARBINARY":
|
||||
return "TEXT";
|
||||
case "ENUM":
|
||||
return `TEXT CHECK("${field.name}" in (${field.values
|
||||
.map((v) => `'${v}'`)
|
||||
.join(", ")}))`;
|
||||
default:
|
||||
return "BLOB";
|
||||
}
|
||||
}
|
||||
|
||||
export function getInlineFK(table, obj) {
|
||||
let fk = "";
|
||||
obj.references.forEach((r) => {
|
||||
@ -364,7 +380,7 @@ export function jsonToSQLite(obj) {
|
||||
(field) =>
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${
|
||||
field.name
|
||||
}" ${getSQLiteType(field)}${field.notNull ? " NOT NULL" : ""}${
|
||||
}" ${getTypeString(field, "sqlite")}${field.notNull ? " NOT NULL" : ""}${
|
||||
field.unique ? " UNIQUE" : ""
|
||||
}${field.default !== "" ? ` DEFAULT ${parseDefault(field)}` : ""}${
|
||||
field.check === "" || !hasCheck(field.type)
|
||||
@ -458,7 +474,7 @@ export function jsonToMariaDB(obj) {
|
||||
.join("\n")}`;
|
||||
}
|
||||
|
||||
export function jsonToSQLServer(obj) {
|
||||
export function jsonToMSSQL(obj) {
|
||||
return `${obj.types
|
||||
.map((type) => {
|
||||
return `${
|
||||
@ -560,3 +576,11 @@ export function getSize(type) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
jsonToMySQL,
|
||||
jsonToMariaDB,
|
||||
jsonToPostgreSQL,
|
||||
jsonToSQLite,
|
||||
jsonToMSSQL,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user