Refactor structure of modal component

This commit is contained in:
Pham Hieu 2024-05-23 11:24:48 +07:00
parent bf744630c0
commit fc018dcca6
26 changed files with 989 additions and 873 deletions

View File

@ -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}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@ -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,11 +38,7 @@ export default function ImportDiagram({ setImportData, error, setError }) {
);
};
return (
<div>
<Upload
action="#"
beforeUpload={({ file, fileList }) => {
const beforeUpload = ({ file, fileList }) => {
const f = fileList[0].fileInstance;
if (!f) {
return;
@ -47,8 +59,7 @@ export default function ImportDiagram({ setImportData, error, setError }) {
if (!jsonDiagramIsValid(jsonObject)) {
setError({
type: STATUS.ERROR,
message:
"The file is missing necessary properties for a diagram.",
message: "The file is missing necessary properties for a diagram.",
});
return;
}
@ -56,8 +67,7 @@ export default function ImportDiagram({ setImportData, error, setError }) {
if (!ddbDiagramIsValid(jsonObject)) {
setError({
type: STATUS.ERROR,
message:
"The file is missing necessary properties for a diagram.",
message: "The file is missing necessary properties for a diagram.",
});
return;
}
@ -84,7 +94,40 @@ export default function ImportDiagram({ setImportData, error, setError }) {
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 (
<BaseModal
modalTitle={t("import_diagram")}
okText={t("import")}
onOk={onOk}
onCancel={hideModal}
okBtnDisabled={(error && error?.type === STATUS.ERROR) || !importData}
>
<Upload
action="#"
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>
);
}

View File

@ -1,20 +1,39 @@
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();
return (
<div>
<Upload
action="#"
beforeUpload={({ file, fileList }) => {
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;
@ -31,7 +50,58 @@ export default function ImportSource({
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 (
<BaseModal
modalTitle={t("import_diagram")}
okText={t("import")}
onOk={onOk}
onCancel={hideModal}
okBtnDisabled={
(error && error?.type === STATUS.ERROR) || importData.src === ""
}
>
<Upload
action="#"
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>
);
}

View File

@ -1,12 +1,14 @@
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 (
<BaseModal modalTitle={t("language")} onOk={hideModal} onCancel={hideModal}>
<div className="grid grid-cols-3 gap-4">
{languages.map((l) => (
<button
@ -26,5 +28,6 @@ export default function Language() {
</button>
))}
</div>
</BaseModal>
);
}

View File

@ -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>
);
}

View File

@ -1,15 +1,36 @@
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 (
<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
@ -41,5 +62,6 @@ export default function New({ selectedTemplateId, setSelectedTemplateId }) {
</div>
))}
</div>
</BaseModal>
);
}

View File

@ -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>
);
}

View File

@ -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 (
<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>
);
}

View 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>
);
}

View File

@ -1,10 +1,18 @@
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 (
<BaseModal
modalTitle={t("table_width")}
onOk={hideModal}
onCancel={hideModal}
>
<InputNumber
className="w-full"
value={settings.tableWidth}
@ -13,5 +21,6 @@ export default function SetTableWidth() {
setSettings((prev) => ({ ...prev, tableWidth: c }));
}}
/>
</BaseModal>
);
}

View 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 <></>;
}
}

View File

@ -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"];

View File

@ -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:

View File

@ -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:

View File

@ -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",
},
};

View File

@ -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:

View File

@ -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}}'",
@ -203,4 +203,3 @@ const fa = {
};
export { fa,persian };

View File

@ -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:

View File

@ -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",
},
};

View File

@ -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");
}
};

View File

@ -39,8 +39,7 @@ 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") {
export function getMysqlType(field) {
if (field.type === "UUID") {
return `VARCHAR(36)`;
}
@ -54,7 +53,9 @@ export function getTypeString(field, dbms = "mysql", baseType = false) {
return "JSON";
}
return field.type;
} else if (dbms === "postgres") {
}
export function getPostgreSQLType(field) {
if (field.type === "SMALLINT" && field.increment) {
return "smallserial";
}
@ -89,7 +90,9 @@ export function getTypeString(field, dbms = "mysql", baseType = false) {
return `${field.type}${field.size}`;
}
return field.type.toLowerCase();
} else if (dbms === "mssql") {
}
export function getMSSQLType(field, baseType = false) {
let type = field.type;
switch (field.type) {
case "ENUM":
@ -126,9 +129,55 @@ export function getTypeString(field, dbms = "mysql", baseType = false) {
}
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) {
return [
"CHAR",
@ -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,
};