From 51b68e86b833c43dcacec07500a67d9595f2cad3 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Sun, 24 Nov 2024 16:40:23 +0400
Subject: [PATCH 1/8] Add dbml editor to sidepanel
---
package-lock.json | 30 +++++++++++++++----
package.json | 2 +-
src/components/EditorHeader/ControlPanel.jsx | 13 ++++++++
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 18 +++++++++++
.../EditorSidePanel/DBMLEditor/styles.css | 11 +++++++
src/components/EditorSidePanel/SidePanel.jsx | 29 ++++++++++++++++++
src/context/LayoutContext.jsx | 1 +
src/data/editorExtensions.js | 7 +++++
src/i18n/locales/en.js | 4 +++
src/index.css | 2 +-
10 files changed, 110 insertions(+), 7 deletions(-)
create mode 100644 src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
create mode 100644 src/components/EditorSidePanel/DBMLEditor/styles.css
create mode 100644 src/data/editorExtensions.js
diff --git a/package-lock.json b/package-lock.json
index 09f44734b..39c8b4915 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",
- "@uiw/codemirror-theme-vscode": "^4.21.25",
+ "@uiw/codemirror-theme-vscode": "^4.23.6",
"@uiw/react-codemirror": "^4.21.25",
"@vercel/analytics": "^1.2.2",
"axios": "^1.7.4",
@@ -2195,14 +2195,34 @@
}
},
"node_modules/@uiw/codemirror-theme-vscode": {
- "version": "4.21.25",
- "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.21.25.tgz",
- "integrity": "sha512-1gubCz7kHE5XH3H1IUTSrnyK/G3dQRmOIgPFsefE9e+TizhBJnkbKSDSfRfpm5l7jl1G7v/as0HQvN3cYg/Rkg==",
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.23.6.tgz",
+ "integrity": "sha512-xUo1ic+Kk5hnv5gy+cXU12GZVSnDjic8s8weKq8loPHF1dSR1e6gkKVIKZRnvoOZ302taKRk7phWpBUaWIuKQg==",
+ "license": "MIT",
"dependencies": {
- "@uiw/codemirror-themes": "4.21.25"
+ "@uiw/codemirror-themes": "4.23.6"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ }
+ },
+ "node_modules/@uiw/codemirror-theme-vscode/node_modules/@uiw/codemirror-themes": {
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.6.tgz",
+ "integrity": "sha512-0dpuLQW+V6zrKvfvor/eo71V3tpr2L2Hsu8QZAdtSzksjWABxTOzH3ShaBRxCEsrz6sU9sa9o7ShwBMMDz59bQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
}
},
"node_modules/@uiw/codemirror-themes": {
diff --git a/package.json b/package.json
index f1b38c506..d69cc6c30 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",
- "@uiw/codemirror-theme-vscode": "^4.21.25",
+ "@uiw/codemirror-theme-vscode": "^4.23.6",
"@uiw/react-codemirror": "^4.21.25",
"@vercel/analytics": "^1.2.2",
"axios": "^1.7.4",
diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx
index 8ea0bedfd..be12647a5 100644
--- a/src/components/EditorHeader/ControlPanel.jsx
+++ b/src/components/EditorHeader/ControlPanel.jsx
@@ -516,6 +516,9 @@ export default function ControlPanel({
const viewStrictMode = () => {
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
};
+ const toggleDBMLEditor = () => {
+ setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
+ };
const viewFieldSummary = () => {
setSettings((prev) => ({
...prev,
@@ -1175,6 +1178,15 @@ export default function ControlPanel({
function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
},
+ dbml_editor: {
+ state: layout.dbmlEditor ? (
+
+ ) : (
+
+ ),
+ function: toggleDBMLEditor,
+ shortcut: "Alt+E",
+ },
strict_mode: {
state: settings.strictMode ? (
@@ -1386,6 +1398,7 @@ export default function ControlPanel({
preventDefault: true,
});
useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true });
+ useHotkeys("alt+e", toggleDBMLEditor, { preventDefault: true });
return (
<>
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
new file mode 100644
index 000000000..d67e39fac
--- /dev/null
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -0,0 +1,18 @@
+import CodeMirror from "@uiw/react-codemirror";
+import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
+import { languageExtension } from "../../../data/editorExtensions";
+import { useSettings } from "../../../hooks";
+import "./styles.css";
+
+export default function DBMLEditor() {
+ const { settings } = useSettings();
+ return (
+
+ {}}
+ theme={settings.mode === "dark" ? vscodeDark : vscodeLight}
+ />
+
+ );
+}
diff --git a/src/components/EditorSidePanel/DBMLEditor/styles.css b/src/components/EditorSidePanel/DBMLEditor/styles.css
new file mode 100644
index 000000000..521ade329
--- /dev/null
+++ b/src/components/EditorSidePanel/DBMLEditor/styles.css
@@ -0,0 +1,11 @@
+.cm-editor {
+ font-size: 13px;
+}
+
+.ͼ1o {
+ background-color: var(--semi-color-bg-0);
+}
+
+.ͼ1o .cm-gutters {
+ background-color: var(--semi-color-bg-0);
+}
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 168c1f6b1..77cb04fd9 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -21,6 +21,7 @@ import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
+import DBMLEditor from "./DBMLEditor/DBMLEditor";
export default function SidePanel({ width, resize, setResize }) {
const { layout } = useLayout();
@@ -91,6 +92,7 @@ export default function SidePanel({ width, resize, setResize }) {
style={{ width: `${width}px` }}
>
+<<<<<<< HEAD
))}
+=======
+ {layout.dbmlEditor ? (
+
+ setSelectedElement((prev) => ({ ...prev, currentTab: key }))
+ }
+ collapsible
+ tabBarStyle={{ direction: "ltr" }}
+ >
+ {tabList.length &&
+ tabList.map((tab) => (
+
+ {tab.component}
+
+ ))}
+
+ ) : (
+
+ )}
+>>>>>>> feb41e8 (Add dbml editor to sidepanel)
{layout.issues && (
diff --git a/src/context/LayoutContext.jsx b/src/context/LayoutContext.jsx
index cfb48360c..5349b0440 100644
--- a/src/context/LayoutContext.jsx
+++ b/src/context/LayoutContext.jsx
@@ -8,6 +8,7 @@ export default function LayoutContextProvider({ children }) {
sidebar: true,
issues: true,
toolbar: true,
+ dbmlEditor: false,
});
return (
diff --git a/src/data/editorExtensions.js b/src/data/editorExtensions.js
new file mode 100644
index 000000000..ce083e6e3
--- /dev/null
+++ b/src/data/editorExtensions.js
@@ -0,0 +1,7 @@
+import { sql } from "@codemirror/lang-sql";
+import { json } from "@codemirror/lang-json";
+
+export const languageExtension = {
+ sql: [sql()],
+ json: [json()],
+};
diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js
index 81c903a48..215fc1363 100644
--- a/src/i18n/locales/en.js
+++ b/src/i18n/locales/en.js
@@ -243,7 +243,11 @@ const en = {
failed_to_load: "Failed to load. Make sure the link is correct.",
share_info:
"* Sharing this link will not create a live real-time collaboration session.",
+<<<<<<< HEAD
show_relationship_labels: "Show relationship labels",
+=======
+ dbml_editor: "DBML editor",
+>>>>>>> feb41e8 (Add dbml editor to sidepanel)
},
};
diff --git a/src/index.css b/src/index.css
index 62a845da0..5bbd2902d 100644
--- a/src/index.css
+++ b/src/index.css
@@ -58,7 +58,7 @@
background-color: rgba(var(--semi-blue-6), 1);
}
-.semi-spin-wrapper{
+.semi-spin-wrapper {
color: inherit;
}
From 5bd6f936a192cd98b0924c37d2e8b6db4e1229c1 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Sun, 24 Nov 2024 19:10:02 +0400
Subject: [PATCH 2/8] Parse input
---
package-lock.json | 53 +++++++++++++++++++
package.json | 1 +
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 21 +++++++-
3 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 39c8b4915..5e4170c2e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.6.3",
+ "@dbml/core": "^3.9.3",
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",
@@ -536,6 +537,34 @@
"w3c-keyname": "^2.2.4"
}
},
+ "node_modules/@dbml/core": {
+ "version": "3.9.3",
+ "resolved": "https://registry.npmjs.org/@dbml/core/-/core-3.9.3.tgz",
+ "integrity": "sha512-qCaeVycz0La3GUjUDVXGO6/CLC3BO8NQ6HYaEMKxXzHS1HFi5kRAJDzBZqza0h0WVTms3FcGSG9TH/XitD7zaA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@dbml/parse": "^3.9.3",
+ "antlr4": "^4.13.1",
+ "lodash": "^4.17.15",
+ "parsimmon": "^1.13.0",
+ "pluralize": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@dbml/parse": {
+ "version": "3.9.3",
+ "resolved": "https://registry.npmjs.org/@dbml/parse/-/parse-3.9.3.tgz",
+ "integrity": "sha512-5IBjM5zaCwr75r+6ETEBJ9sB6XnYmWLN2s4bg7Yng2a40wA1/UVGKlq/Aktje0Nn/mK8Lo/sTfZ/LpWEWTRgNw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "peerDependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
@@ -2371,6 +2400,15 @@
"node": ">=4"
}
},
+ "node_modules/antlr4": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz",
+ "integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -5085,6 +5123,12 @@
"node": ">=6"
}
},
+ "node_modules/parsimmon": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
+ "integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw==",
+ "license": "MIT"
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5159,6 +5203,15 @@
"node": ">= 6"
}
},
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/postcss": {
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
diff --git a/package.json b/package.json
index d69cc6c30..7d788fa8c 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.6.3",
+ "@dbml/core": "^3.9.3",
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
index d67e39fac..42ddf0d39 100644
--- a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -1,16 +1,35 @@
+import { useEffect, useState } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
import { languageExtension } from "../../../data/editorExtensions";
import { useSettings } from "../../../hooks";
+import { useDebounceValue } from "usehooks-ts";
+import { Parser } from "@dbml/core";
import "./styles.css";
+const parser = new Parser();
+
export default function DBMLEditor() {
const { settings } = useSettings();
+ const [value, setValue] = useState("");
+ const [debouncedValue] = useDebounceValue(value, 1000);
+
+ useEffect(() => {
+ if (debouncedValue) {
+ try {
+ const database = parser.parse(debouncedValue, "dbml");
+ console.log(database);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ }, [debouncedValue]);
+
return (
{}}
+ onChange={(v) => setValue(v)}
theme={settings.mode === "dark" ? vscodeDark : vscodeLight}
/>
From d76a9e9ff8935bcfb64cbfebe9a1ced70447e1c4 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Tue, 28 Jan 2025 18:45:51 +0400
Subject: [PATCH 3/8] Import tables from dbml
---
src/components/EditorHeader/ControlPanel.jsx | 2 +-
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 16 ++--
.../EditorSidePanel/DBMLEditor/styles.css | 12 +++
src/components/EditorSidePanel/SidePanel.jsx | 22 +-----
src/i18n/locales/en.js | 3 -
src/index.css | 2 +-
src/utils/arrangeTables.js | 27 +++++++
src/utils/dbml/fromDBML.js | 78 +++++++++++++++++++
src/utils/importSQL/index.js | 28 +------
9 files changed, 131 insertions(+), 59 deletions(-)
create mode 100644 src/utils/arrangeTables.js
create mode 100644 src/utils/dbml/fromDBML.js
diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx
index be12647a5..7c6b7c4b8 100644
--- a/src/components/EditorHeader/ControlPanel.jsx
+++ b/src/components/EditorHeader/ControlPanel.jsx
@@ -1444,7 +1444,7 @@ export default function ControlPanel({
function toolbar() {
return (
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
index 42ddf0d39..0f7f986d9 100644
--- a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -2,28 +2,28 @@ import { useEffect, useState } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
import { languageExtension } from "../../../data/editorExtensions";
-import { useSettings } from "../../../hooks";
+import { useDiagram, useSettings } from "../../../hooks";
import { useDebounceValue } from "usehooks-ts";
-import { Parser } from "@dbml/core";
import "./styles.css";
-
-const parser = new Parser();
+import { fromDBML } from "../../../utils/dbml/fromDBML";
export default function DBMLEditor() {
const { settings } = useSettings();
+ const { setTables } = useDiagram();
const [value, setValue] = useState("");
const [debouncedValue] = useDebounceValue(value, 1000);
useEffect(() => {
if (debouncedValue) {
try {
- const database = parser.parse(debouncedValue, "dbml");
- console.log(database);
+ const { tables } = fromDBML(debouncedValue);
+ console.log(tables);
+ setTables(tables);
} catch (e) {
- console.log(e);
+ console.log("error: ", e);
}
}
- }, [debouncedValue]);
+ }, [debouncedValue, setTables]);
return (
diff --git a/src/components/EditorSidePanel/DBMLEditor/styles.css b/src/components/EditorSidePanel/DBMLEditor/styles.css
index 521ade329..50dc37bd3 100644
--- a/src/components/EditorSidePanel/DBMLEditor/styles.css
+++ b/src/components/EditorSidePanel/DBMLEditor/styles.css
@@ -9,3 +9,15 @@
.ͼ1o .cm-gutters {
background-color: var(--semi-color-bg-0);
}
+
+.ͼ1.cm-focused {
+ outline: none;
+}
+
+.ͼ16 {
+ background-color: #1e1e1e00;
+}
+
+.ͼ16 .cm-gutters {
+ background-color: rgba(var(--semi-grey-1), 0.3);
+}
\ No newline at end of file
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 77cb04fd9..65d5c52f6 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -92,31 +92,12 @@ export default function SidePanel({ width, resize, setResize }) {
style={{ width: `${width}px` }}
>
-<<<<<<< HEAD
-
- setSelectedElement((prev) => ({ ...prev, currentTab: key }))
- }
- collapsible
- tabBarStyle={{ direction: "ltr" }}
- >
- {tabList.length &&
- tabList.map((tab) => (
-
- {tab.component}
-
- ))}
-
-=======
{layout.dbmlEditor ? (
setSelectedElement((prev) => ({ ...prev, currentTab: key }))
}
@@ -137,7 +118,6 @@ export default function SidePanel({ width, resize, setResize }) {
) : (
)}
->>>>>>> feb41e8 (Add dbml editor to sidepanel)
{layout.issues && (
diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js
index 215fc1363..397fbbd34 100644
--- a/src/i18n/locales/en.js
+++ b/src/i18n/locales/en.js
@@ -243,11 +243,8 @@ const en = {
failed_to_load: "Failed to load. Make sure the link is correct.",
share_info:
"* Sharing this link will not create a live real-time collaboration session.",
-<<<<<<< HEAD
show_relationship_labels: "Show relationship labels",
-=======
dbml_editor: "DBML editor",
->>>>>>> feb41e8 (Add dbml editor to sidepanel)
},
};
diff --git a/src/index.css b/src/index.css
index 5bbd2902d..af935c8ed 100644
--- a/src/index.css
+++ b/src/index.css
@@ -105,7 +105,7 @@
}
.toolbar-theme {
- background-color: rgba(var(--semi-grey-1), 1);
+ background-color: rgba(var(--semi-grey-1), 0.7);
}
.hover-1:hover {
diff --git a/src/utils/arrangeTables.js b/src/utils/arrangeTables.js
new file mode 100644
index 000000000..a509907d9
--- /dev/null
+++ b/src/utils/arrangeTables.js
@@ -0,0 +1,27 @@
+import {
+ tableColorStripHeight,
+ tableFieldHeight,
+ tableHeaderHeight,
+} from "../data/constants";
+
+export function arrangeTables(diagram) {
+ let maxHeight = -1;
+ const tableWidth = 200;
+ const gapX = 54;
+ const gapY = 40;
+ diagram.tables.forEach((table, i) => {
+ if (i < diagram.tables.length / 2) {
+ table.x = i * tableWidth + (i + 1) * gapX;
+ table.y = gapY;
+ const height =
+ table.fields.length * tableFieldHeight +
+ tableHeaderHeight +
+ tableColorStripHeight;
+ maxHeight = Math.max(height, maxHeight);
+ } else {
+ const index = diagram.tables.length - i - 1;
+ table.x = index * tableWidth + (index + 1) * gapX;
+ table.y = maxHeight + 2 * gapY;
+ }
+ });
+}
diff --git a/src/utils/dbml/fromDBML.js b/src/utils/dbml/fromDBML.js
new file mode 100644
index 000000000..49c11d3d0
--- /dev/null
+++ b/src/utils/dbml/fromDBML.js
@@ -0,0 +1,78 @@
+import { Parser } from "@dbml/core";
+import { arrangeTables } from "../arrangeTables";
+
+const parser = new Parser();
+
+/**
+
+{
+ "id": 0,
+ "name": "some_table",
+ "x": 812.9083754222163,
+ "y": 400.3451698134321,
+ "fields": [
+ {
+ "name": "id",
+ "type": "INT",
+ "default": "",
+ "check": "",
+ "primary": true,
+ "unique": true,
+ "notNull": true,
+ "increment": true,
+ "comment": "",
+ "id": 0
+ }
+ ],
+ "comment": "",
+ "indices": [],
+ "color": "#175e7a",
+ "key": 1737222753837
+ }
+ */
+
+export function fromDBML(src) {
+ const ast = parser.parse(src, "dbml");
+
+ const tables = [];
+
+ for (const schema of ast.schemas) {
+ for (const table of schema.tables) {
+ let parsedTable = {};
+ parsedTable.id = tables.length;
+ parsedTable.name = table.name;
+ parsedTable.comment = "";
+ parsedTable.color = "#175e7a";
+ parsedTable.fields = [];
+ parsedTable.indices = [];
+
+ for (const column of table.fields) {
+ const field = {};
+ field.id = parsedTable.fields.length;
+ field.name = column.name;
+ field.type = column.type.type_name.toUpperCase();
+ field.default = column.dbdefault ?? "";
+ field.check = "";
+ field.primary = !!column.pk;
+ field.unique = true;
+ field.notNull = !!column.not_null;
+ field.increment = !!column.increment;
+ field.comment = column.note ?? "";
+
+ parsedTable.fields.push(field);
+ }
+
+ console.log(table);
+
+ tables.push(parsedTable);
+ }
+ }
+
+ console.log(ast);
+
+ const diagram = { tables };
+
+ arrangeTables(diagram);
+
+ return diagram;
+}
diff --git a/src/utils/importSQL/index.js b/src/utils/importSQL/index.js
index 30974ec6e..fb849e430 100644
--- a/src/utils/importSQL/index.js
+++ b/src/utils/importSQL/index.js
@@ -1,9 +1,5 @@
-import {
- DB,
- tableColorStripHeight,
- tableFieldHeight,
- tableHeaderHeight,
-} from "../../data/constants";
+import { DB } from "../../data/constants";
+import { arrangeTables } from "../arrangeTables";
import { fromMariaDB } from "./mariadb";
import { fromMSSQL } from "./mssql";
import { fromMySQL } from "./mysql";
@@ -33,25 +29,7 @@ export function importSQL(ast, toDb = DB.MYSQL, diagramDb = DB.GENERIC) {
break;
}
- let maxHeight = -1;
- const tableWidth = 200;
- const gapX = 54;
- const gapY = 40;
- diagram.tables.forEach((table, i) => {
- if (i < diagram.tables.length / 2) {
- table.x = i * tableWidth + (i + 1) * gapX;
- table.y = gapY;
- const height =
- table.fields.length * tableFieldHeight +
- tableHeaderHeight +
- tableColorStripHeight;
- maxHeight = Math.max(height, maxHeight);
- } else {
- const index = diagram.tables.length - i - 1;
- table.x = index * tableWidth + (index + 1) * gapX;
- table.y = maxHeight + 2 * gapY;
- }
- });
+ arrangeTables(diagram);
return diagram;
}
From 7fa23fc13b20db745d4ae7d1ee60aa925a0fc48b Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Thu, 30 Jan 2025 16:26:05 +0400
Subject: [PATCH 4/8] Parse indexes and enums from dbml
---
src/utils/dbml/fromDBML.js | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/utils/dbml/fromDBML.js b/src/utils/dbml/fromDBML.js
index 49c11d3d0..714696b76 100644
--- a/src/utils/dbml/fromDBML.js
+++ b/src/utils/dbml/fromDBML.js
@@ -35,19 +35,21 @@ export function fromDBML(src) {
const ast = parser.parse(src, "dbml");
const tables = [];
+ const enums = [];
for (const schema of ast.schemas) {
for (const table of schema.tables) {
let parsedTable = {};
parsedTable.id = tables.length;
parsedTable.name = table.name;
- parsedTable.comment = "";
+ parsedTable.comment = table.note ?? "";
parsedTable.color = "#175e7a";
parsedTable.fields = [];
parsedTable.indices = [];
for (const column of table.fields) {
const field = {};
+
field.id = parsedTable.fields.length;
field.name = column.name;
field.type = column.type.type_name.toUpperCase();
@@ -62,15 +64,36 @@ export function fromDBML(src) {
parsedTable.fields.push(field);
}
+ for (const idx of table.indexes) {
+ const parsedIndex = {};
+
+ parsedIndex.id = idx.id - 1;
+ parsedIndex.fields = idx.columns.map((x) => x.value);
+ parsedIndex.name =
+ idx.name ?? `${parsedTable.name}_index_${parsedIndex.id}`;
+ parsedIndex.unique = !!idx.unique;
+
+ parsedTable.indices.push(parsedIndex);
+ }
+
console.log(table);
tables.push(parsedTable);
}
+
+ for (const schemaEnum of schema.enums) {
+ const parsedEnum = {};
+
+ parsedEnum.name = schemaEnum.name;
+ parsedEnum.values = schemaEnum.values.map((x) => x.name);
+
+ enums.push(parsedEnum);
+ }
}
console.log(ast);
- const diagram = { tables };
+ const diagram = { tables, enums };
arrangeTables(diagram);
From 6ae205a64dcef080dc8ba8bc0dbf538502823bd8 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Fri, 31 Jan 2025 16:17:37 +0400
Subject: [PATCH 5/8] Add compiler errors in issues
---
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 6 +--
src/components/EditorSidePanel/Issues.jsx | 39 +++++++++++++------
src/components/EditorSidePanel/SidePanel.jsx | 7 ++--
src/data/constants.js | 5 +++
src/utils/dbml/fromDBML.js | 28 -------------
5 files changed, 39 insertions(+), 46 deletions(-)
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
index 0f7f986d9..400d77127 100644
--- a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -7,7 +7,7 @@ import { useDebounceValue } from "usehooks-ts";
import "./styles.css";
import { fromDBML } from "../../../utils/dbml/fromDBML";
-export default function DBMLEditor() {
+export default function DBMLEditor({ setIssues }) {
const { settings } = useSettings();
const { setTables } = useDiagram();
const [value, setValue] = useState("");
@@ -20,10 +20,10 @@ export default function DBMLEditor() {
console.log(tables);
setTables(tables);
} catch (e) {
- console.log("error: ", e);
+ setIssues((prev) => ({ ...prev, dbml: e.diags.map((x) => x.message) }));
}
}
- }, [debouncedValue, setTables]);
+ }, [debouncedValue, setTables, setIssues]);
return (
diff --git a/src/components/EditorSidePanel/Issues.jsx b/src/components/EditorSidePanel/Issues.jsx
index f3de9a30a..a0b28cf05 100644
--- a/src/components/EditorSidePanel/Issues.jsx
+++ b/src/components/EditorSidePanel/Issues.jsx
@@ -1,17 +1,16 @@
-import { useState, useEffect } from "react";
+import { useEffect } from "react";
import { Collapse, Badge } from "@douyinfe/semi-ui";
import { arrayIsEqual } from "../../utils/utils";
import { getIssues } from "../../utils/issues";
import { useEnums, useSettings, useDiagram, useTypes } from "../../hooks";
import { useTranslation } from "react-i18next";
-export default function Issues() {
+export default function Issues({ issues, setIssues }) {
const { types } = useTypes();
const { t } = useTranslation();
const { settings } = useSettings();
const { enums } = useEnums();
const { tables, relationships, database } = useDiagram();
- const [issues, setIssues] = useState([]);
useEffect(() => {
const findIssues = async () => {
@@ -23,21 +22,29 @@ export default function Issues() {
enums: enums,
});
- if (!arrayIsEqual(newIssues, issues)) {
- setIssues(newIssues);
+ if (!arrayIsEqual(newIssues, issues.diagram)) {
+ setIssues((prev) => ({ ...prev, diagram: newIssues }));
}
};
findIssues();
- }, [tables, relationships, issues, types, database, enums]);
+ }, [tables, relationships, issues, types, database, enums, setIssues]);
return (
0 ? "danger" : "primary"}
- count={settings.strictMode ? null : issues.length}
+ type={
+ issues.dbml.length > 0 || issues.diagram.length > 0
+ ? "danger"
+ : "primary"
+ }
+ count={
+ settings.strictMode
+ ? null
+ : issues.dbml.length + issues.diagram.length
+ }
overflowCount={99}
className="mt-1"
>
@@ -52,11 +59,19 @@ export default function Issues() {
{settings.strictMode ? (
{t("strict_mode_is_on_no_issues")}
- ) : issues.length > 0 ? (
+ ) : issues.dbml.length > 0 || issues.diagram.length > 0 ? (
<>
- {issues.map((e, i) => (
-
- {e}
+ {!settings.dbmlEditor &&
+ issues.dbml.map((e, i) => (
+
+ ))}
+ {issues.diagram.map((e, i) => (
+
))}
>
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 65d5c52f6..b2fc295f6 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -16,7 +16,7 @@ import AreasTab from "./AreasTab/AreasTab";
import NotesTab from "./NotesTab/NotesTab";
import TablesTab from "./TablesTab/TablesTab";
import { useTranslation } from "react-i18next";
-import { useMemo } from "react";
+import { useMemo, useState } from "react";
import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl";
@@ -32,6 +32,7 @@ export default function SidePanel({ width, resize, setResize }) {
const { typesCount } = useTypes();
const { enumsCount } = useEnums();
const { t } = useTranslation();
+ const [issues, setIssues] = useState({ diagram: [], dbml: [] });
const tabList = useMemo(() => {
const tabs = [
@@ -116,12 +117,12 @@ export default function SidePanel({ width, resize, setResize }) {
))}
) : (
-
+
)}
{layout.issues && (
-
+
)}
diff --git a/src/data/constants.js b/src/data/constants.js
index 3dd8eee09..0f7498bc3 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -115,3 +115,8 @@ export const DB = {
MARIADB: "mariadb",
GENERIC: "generic",
};
+
+export const ErrorType = {
+ DIAGRAM: "diagram",
+ DBML: "dbml",
+};
diff --git a/src/utils/dbml/fromDBML.js b/src/utils/dbml/fromDBML.js
index 714696b76..f656d349f 100644
--- a/src/utils/dbml/fromDBML.js
+++ b/src/utils/dbml/fromDBML.js
@@ -3,34 +3,6 @@ import { arrangeTables } from "../arrangeTables";
const parser = new Parser();
-/**
-
-{
- "id": 0,
- "name": "some_table",
- "x": 812.9083754222163,
- "y": 400.3451698134321,
- "fields": [
- {
- "name": "id",
- "type": "INT",
- "default": "",
- "check": "",
- "primary": true,
- "unique": true,
- "notNull": true,
- "increment": true,
- "comment": "",
- "id": 0
- }
- ],
- "comment": "",
- "indices": [],
- "color": "#175e7a",
- "key": 1737222753837
- }
- */
-
export function fromDBML(src) {
const ast = parser.parse(src, "dbml");
From 7faf7c3ef593ec863025e7e8a351470b093ab9f4 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Sat, 1 Feb 2025 23:48:21 +0400
Subject: [PATCH 6/8] Add toDBML
---
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 8 +-
src/components/EditorSidePanel/Issues.jsx | 11 +-
src/components/EditorSidePanel/SidePanel.jsx | 4 +-
src/utils/dbml/fromDBML.js | 2 +-
src/utils/dbml/toDBML.js | 102 ++++++++++++++++++
src/utils/exportSQL/shared.js | 4 +
6 files changed, 125 insertions(+), 6 deletions(-)
create mode 100644 src/utils/dbml/toDBML.js
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
index 400d77127..a87aeac2c 100644
--- a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -2,16 +2,21 @@ import { useEffect, useState } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
import { languageExtension } from "../../../data/editorExtensions";
-import { useDiagram, useSettings } from "../../../hooks";
+import { useDiagram, useEnums, useSettings } from "../../../hooks";
import { useDebounceValue } from "usehooks-ts";
import "./styles.css";
import { fromDBML } from "../../../utils/dbml/fromDBML";
+import { toDBML } from "../../../utils/dbml/toDBML";
export default function DBMLEditor({ setIssues }) {
const { settings } = useSettings();
const { setTables } = useDiagram();
const [value, setValue] = useState("");
const [debouncedValue] = useDebounceValue(value, 1000);
+ const diagram = useDiagram();
+ const { enums } = useEnums();
+
+ useEffect(() => setValue(toDBML({ ...diagram, enums })), [diagram, enums]);
useEffect(() => {
if (debouncedValue) {
@@ -28,6 +33,7 @@ export default function DBMLEditor({ setIssues }) {
return (
setValue(v)}
theme={settings.mode === "dark" ? vscodeDark : vscodeLight}
diff --git a/src/components/EditorSidePanel/Issues.jsx b/src/components/EditorSidePanel/Issues.jsx
index a0b28cf05..d25bb7e9a 100644
--- a/src/components/EditorSidePanel/Issues.jsx
+++ b/src/components/EditorSidePanel/Issues.jsx
@@ -2,7 +2,13 @@ import { useEffect } from "react";
import { Collapse, Badge } from "@douyinfe/semi-ui";
import { arrayIsEqual } from "../../utils/utils";
import { getIssues } from "../../utils/issues";
-import { useEnums, useSettings, useDiagram, useTypes } from "../../hooks";
+import {
+ useEnums,
+ useSettings,
+ useDiagram,
+ useTypes,
+ useLayout,
+} from "../../hooks";
import { useTranslation } from "react-i18next";
export default function Issues({ issues, setIssues }) {
@@ -11,6 +17,7 @@ export default function Issues({ issues, setIssues }) {
const { settings } = useSettings();
const { enums } = useEnums();
const { tables, relationships, database } = useDiagram();
+ const { layout } = useLayout();
useEffect(() => {
const findIssues = async () => {
@@ -61,7 +68,7 @@ export default function Issues({ issues, setIssues }) {
{t("strict_mode_is_on_no_issues")}
) : issues.dbml.length > 0 || issues.diagram.length > 0 ? (
<>
- {!settings.dbmlEditor &&
+ {!layout.dbmlEditor &&
issues.dbml.map((e, i) => (
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index b2fc295f6..0707e2315 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -94,6 +94,8 @@ export default function SidePanel({ width, resize, setResize }) {
>
{layout.dbmlEditor ? (
+
+ ) : (
))}
- ) : (
-
)}
{layout.issues && (
diff --git a/src/utils/dbml/fromDBML.js b/src/utils/dbml/fromDBML.js
index f656d349f..34eb05e60 100644
--- a/src/utils/dbml/fromDBML.js
+++ b/src/utils/dbml/fromDBML.js
@@ -28,7 +28,7 @@ export function fromDBML(src) {
field.default = column.dbdefault ?? "";
field.check = "";
field.primary = !!column.pk;
- field.unique = true;
+ field.unique = !!column.pk;
field.notNull = !!column.not_null;
field.increment = !!column.increment;
field.comment = column.note ?? "";
diff --git a/src/utils/dbml/toDBML.js b/src/utils/dbml/toDBML.js
new file mode 100644
index 000000000..bd38c49e8
--- /dev/null
+++ b/src/utils/dbml/toDBML.js
@@ -0,0 +1,102 @@
+import { Cardinality } from "../../data/constants";
+import { parseDefault } from "../exportSQL/shared";
+
+function hasColumnSettings(field) {
+ return (
+ field.primary ||
+ field.notNull ||
+ field.increment ||
+ field.unique ||
+ (field.comment && field.comment.trim() != "") ||
+ (field.default && field.default.trim() != "")
+ );
+}
+
+function columnDefault(field, database) {
+ if (!field.default || field.default.trim() === "") {
+ return "";
+ }
+
+ return `default: ${parseDefault(field, database)}`;
+}
+
+function columnComment(field) {
+ if (!field.comment || field.comment.trim() === "") {
+ return "";
+ }
+
+ return `note: '${field.comment}'`;
+}
+
+function columnSettings(field, database) {
+ if (!hasColumnSettings(field)) {
+ return "";
+ }
+
+ return ` [ ${field.primary ? "pk " : ""}${
+ field.increment ? "increment " : ""
+ }${field.notNull ? "not null " : ""}${
+ field.unique ? "unique " : ""
+ }${columnDefault(field, database)}${columnComment(field, database)}]`;
+}
+
+function cardinality(rel) {
+ switch (rel.cardinality) {
+ case Cardinality.ONE_TO_ONE:
+ return "-";
+ case Cardinality.ONE_TO_MANY:
+ return "<";
+ case Cardinality.MANY_TO_ONE:
+ return ">";
+ }
+}
+
+export function toDBML(diagram) {
+ return `${diagram.enums
+ .map(
+ (en) =>
+ `enum ${en.name} {\n${en.values.map((v) => `\t${v}`).join("\n")}\n}\n\n`,
+ )
+ .join("\n\n")}${diagram.tables
+ .map(
+ (table) =>
+ `Table ${table.name} {\n${table.fields
+ .map(
+ (field) =>
+ `\t${field.name} ${field.type.toLowerCase()}${columnSettings(
+ field,
+ diagram.database,
+ )}`,
+ )
+ .join("\n")}${
+ table.indices.length > 0
+ ? "\n\n\tindexes {\n" +
+ table.indices
+ .map(
+ (index) =>
+ `\t\t(${index.fields.join(", ")}) [ name: '${
+ index.name
+ }'${index.unique ? " unique" : ""} ]`,
+ )
+ .join("\n") +
+ "\n\t}"
+ : ""
+ }${
+ table.comment && table.comment.trim() !== ""
+ ? `\n\n\tNote: '${table.comment}'`
+ : ""
+ }\n}`,
+ )
+ .join("\n\n")}\n\n${diagram.relationships
+ .map(
+ (rel) =>
+ `Ref ${rel.name} {\n\t${
+ diagram.tables[rel.startTableId].name
+ }.${diagram.tables[rel.startTableId].fields[rel.startFieldId].name} ${cardinality(
+ rel,
+ )} ${diagram.tables[rel.endTableId].name}.${
+ diagram.tables[rel.endTableId].fields[rel.endFieldId].name
+ } [ delete: ${rel.deleteConstraint.toLowerCase()}, on update: ${rel.updateConstraint.toLowerCase()} ]\n}`,
+ )
+ .join("\n\n")}`;
+}
diff --git a/src/utils/exportSQL/shared.js b/src/utils/exportSQL/shared.js
index 9e0f5cbcb..3cbf21a16 100644
--- a/src/utils/exportSQL/shared.js
+++ b/src/utils/exportSQL/shared.js
@@ -4,6 +4,10 @@ import { DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
export function parseDefault(field, database = DB.GENERIC) {
+ if (!field.default || field.default.trim() == "") {
+ return "";
+ }
+
if (
strHasQuotes(field.default) ||
isFunction(field.default) ||
From 1e06914fe067a8040c44e97716ab779157be246e Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Sun, 13 Apr 2025 02:02:14 +0400
Subject: [PATCH 7/8] add basic dbml editor
---
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 37 -------
.../EditorSidePanel/DBMLEditor/index.jsx | 72 +++++++++++++
.../EditorSidePanel/DBMLEditor/styles.css | 23 ----
src/components/EditorSidePanel/SidePanel.jsx | 2 +-
src/utils/dbml/fromDBML.js | 73 -------------
src/utils/dbml/toDBML.js | 102 ------------------
src/utils/exportAs/dbml.js | 5 +-
src/utils/exportSQL/shared.js | 5 +-
src/utils/importFrom/dbml.js | 2 +-
src/utils/issues.js | 7 +-
src/utils/utils.js | 3 +-
11 files changed, 90 insertions(+), 241 deletions(-)
delete mode 100644 src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
create mode 100644 src/components/EditorSidePanel/DBMLEditor/index.jsx
delete mode 100644 src/components/EditorSidePanel/DBMLEditor/styles.css
delete mode 100644 src/utils/dbml/fromDBML.js
delete mode 100644 src/utils/dbml/toDBML.js
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
deleted file mode 100644
index 5e3e3ffa0..000000000
--- a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useEffect, useState } from "react";
-import { useDiagram, useEnums } from "../../../hooks";
-import { useDebounceValue } from "usehooks-ts";
-import { fromDBML } from "../../../utils/dbml/fromDBML";
-import { toDBML } from "../../../utils/dbml/toDBML";
-import CodeEditor from "../../CodeEditor";
-
-export default function DBMLEditor({ setIssues }) {
- const { setTables } = useDiagram();
- const [value, setValue] = useState("");
- const [debouncedValue] = useDebounceValue(value, 1000);
- const diagram = useDiagram();
- const { enums } = useEnums();
-
- useEffect(() => setValue(toDBML({ ...diagram, enums })), [diagram, enums]);
-
- useEffect(() => {
- if (debouncedValue) {
- try {
- const { tables } = fromDBML(debouncedValue);
- console.log(tables);
- setTables(tables);
- } catch (e) {
- setIssues((prev) => ({ ...prev, dbml: e.diags.map((x) => x.message) }));
- }
- }
- }, [debouncedValue, setTables, setIssues]);
-
- return (
-
setValue(v)}
- height="100%"
- />
- );
-}
diff --git a/src/components/EditorSidePanel/DBMLEditor/index.jsx b/src/components/EditorSidePanel/DBMLEditor/index.jsx
new file mode 100644
index 000000000..14126ae24
--- /dev/null
+++ b/src/components/EditorSidePanel/DBMLEditor/index.jsx
@@ -0,0 +1,72 @@
+import { useEffect, useState } from "react";
+import { useDiagram, useEnums, useTransform } from "../../../hooks";
+import { useDebounceValue } from "usehooks-ts";
+import { fromDBML } from "../../../utils/importFrom/dbml";
+import { toDBML } from "../../../utils/exportAs/dbml";
+import CodeEditor from "../../CodeEditor";
+
+export default function DBMLEditor({ setIssues }) {
+ const { tables: currentTables, setTables } = useDiagram();
+ const diagram = useDiagram();
+ const { enums } = useEnums();
+ const { transform } = useTransform();
+ const [value, setValue] = useState(() => toDBML({ ...diagram, enums }));
+ const [debouncedValue] = useDebounceValue(value, 2000);
+
+ useEffect(() => {
+ const updateDiagram = () => {
+ try {
+ const currentDBML = toDBML({ ...diagram, enums });
+
+ if (debouncedValue && debouncedValue !== currentDBML) {
+ const { tables: newTables } = fromDBML(debouncedValue);
+
+ const mergedTables = newTables
+ .map((newTable) => {
+ const existingTable = currentTables.find(
+ (t) => t.id === newTable.id || t.name === newTable.name,
+ );
+
+ return {
+ ...newTable,
+ ...(existingTable
+ ? {
+ x: existingTable.x,
+ y: existingTable.y,
+ color: existingTable.color,
+ id: existingTable.id,
+ }
+ : {
+ x: transform.pan.x,
+ y: transform.pan.y,
+ }),
+ };
+ })
+ .map((x, i) => ({ ...x, id: i }));
+
+ setTables(mergedTables);
+ }
+ } catch (e) {
+ setIssues((prev) => ({
+ ...prev,
+ dbml: e.diags?.map((x) => x.message) || [e.message],
+ }));
+ }
+ };
+
+ updateDiagram();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedValue]);
+
+ return (
+
+ );
+}
diff --git a/src/components/EditorSidePanel/DBMLEditor/styles.css b/src/components/EditorSidePanel/DBMLEditor/styles.css
deleted file mode 100644
index 50dc37bd3..000000000
--- a/src/components/EditorSidePanel/DBMLEditor/styles.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.cm-editor {
- font-size: 13px;
-}
-
-.ͼ1o {
- background-color: var(--semi-color-bg-0);
-}
-
-.ͼ1o .cm-gutters {
- background-color: var(--semi-color-bg-0);
-}
-
-.ͼ1.cm-focused {
- outline: none;
-}
-
-.ͼ16 {
- background-color: #1e1e1e00;
-}
-
-.ͼ16 .cm-gutters {
- background-color: rgba(var(--semi-grey-1), 0.3);
-}
\ No newline at end of file
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 0707e2315..839dd11b1 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -21,7 +21,7 @@ import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
-import DBMLEditor from "./DBMLEditor/DBMLEditor";
+import DBMLEditor from "./DBMLEditor";
export default function SidePanel({ width, resize, setResize }) {
const { layout } = useLayout();
diff --git a/src/utils/dbml/fromDBML.js b/src/utils/dbml/fromDBML.js
deleted file mode 100644
index 34eb05e60..000000000
--- a/src/utils/dbml/fromDBML.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Parser } from "@dbml/core";
-import { arrangeTables } from "../arrangeTables";
-
-const parser = new Parser();
-
-export function fromDBML(src) {
- const ast = parser.parse(src, "dbml");
-
- const tables = [];
- const enums = [];
-
- for (const schema of ast.schemas) {
- for (const table of schema.tables) {
- let parsedTable = {};
- parsedTable.id = tables.length;
- parsedTable.name = table.name;
- parsedTable.comment = table.note ?? "";
- parsedTable.color = "#175e7a";
- parsedTable.fields = [];
- parsedTable.indices = [];
-
- for (const column of table.fields) {
- const field = {};
-
- field.id = parsedTable.fields.length;
- field.name = column.name;
- field.type = column.type.type_name.toUpperCase();
- field.default = column.dbdefault ?? "";
- field.check = "";
- field.primary = !!column.pk;
- field.unique = !!column.pk;
- field.notNull = !!column.not_null;
- field.increment = !!column.increment;
- field.comment = column.note ?? "";
-
- parsedTable.fields.push(field);
- }
-
- for (const idx of table.indexes) {
- const parsedIndex = {};
-
- parsedIndex.id = idx.id - 1;
- parsedIndex.fields = idx.columns.map((x) => x.value);
- parsedIndex.name =
- idx.name ?? `${parsedTable.name}_index_${parsedIndex.id}`;
- parsedIndex.unique = !!idx.unique;
-
- parsedTable.indices.push(parsedIndex);
- }
-
- console.log(table);
-
- tables.push(parsedTable);
- }
-
- for (const schemaEnum of schema.enums) {
- const parsedEnum = {};
-
- parsedEnum.name = schemaEnum.name;
- parsedEnum.values = schemaEnum.values.map((x) => x.name);
-
- enums.push(parsedEnum);
- }
- }
-
- console.log(ast);
-
- const diagram = { tables, enums };
-
- arrangeTables(diagram);
-
- return diagram;
-}
diff --git a/src/utils/dbml/toDBML.js b/src/utils/dbml/toDBML.js
deleted file mode 100644
index bd38c49e8..000000000
--- a/src/utils/dbml/toDBML.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import { Cardinality } from "../../data/constants";
-import { parseDefault } from "../exportSQL/shared";
-
-function hasColumnSettings(field) {
- return (
- field.primary ||
- field.notNull ||
- field.increment ||
- field.unique ||
- (field.comment && field.comment.trim() != "") ||
- (field.default && field.default.trim() != "")
- );
-}
-
-function columnDefault(field, database) {
- if (!field.default || field.default.trim() === "") {
- return "";
- }
-
- return `default: ${parseDefault(field, database)}`;
-}
-
-function columnComment(field) {
- if (!field.comment || field.comment.trim() === "") {
- return "";
- }
-
- return `note: '${field.comment}'`;
-}
-
-function columnSettings(field, database) {
- if (!hasColumnSettings(field)) {
- return "";
- }
-
- return ` [ ${field.primary ? "pk " : ""}${
- field.increment ? "increment " : ""
- }${field.notNull ? "not null " : ""}${
- field.unique ? "unique " : ""
- }${columnDefault(field, database)}${columnComment(field, database)}]`;
-}
-
-function cardinality(rel) {
- switch (rel.cardinality) {
- case Cardinality.ONE_TO_ONE:
- return "-";
- case Cardinality.ONE_TO_MANY:
- return "<";
- case Cardinality.MANY_TO_ONE:
- return ">";
- }
-}
-
-export function toDBML(diagram) {
- return `${diagram.enums
- .map(
- (en) =>
- `enum ${en.name} {\n${en.values.map((v) => `\t${v}`).join("\n")}\n}\n\n`,
- )
- .join("\n\n")}${diagram.tables
- .map(
- (table) =>
- `Table ${table.name} {\n${table.fields
- .map(
- (field) =>
- `\t${field.name} ${field.type.toLowerCase()}${columnSettings(
- field,
- diagram.database,
- )}`,
- )
- .join("\n")}${
- table.indices.length > 0
- ? "\n\n\tindexes {\n" +
- table.indices
- .map(
- (index) =>
- `\t\t(${index.fields.join(", ")}) [ name: '${
- index.name
- }'${index.unique ? " unique" : ""} ]`,
- )
- .join("\n") +
- "\n\t}"
- : ""
- }${
- table.comment && table.comment.trim() !== ""
- ? `\n\n\tNote: '${table.comment}'`
- : ""
- }\n}`,
- )
- .join("\n\n")}\n\n${diagram.relationships
- .map(
- (rel) =>
- `Ref ${rel.name} {\n\t${
- diagram.tables[rel.startTableId].name
- }.${diagram.tables[rel.startTableId].fields[rel.startFieldId].name} ${cardinality(
- rel,
- )} ${diagram.tables[rel.endTableId].name}.${
- diagram.tables[rel.endTableId].fields[rel.endFieldId].name
- } [ delete: ${rel.deleteConstraint.toLowerCase()}, on update: ${rel.updateConstraint.toLowerCase()} ]\n}`,
- )
- .join("\n\n")}`;
-}
diff --git a/src/utils/exportAs/dbml.js b/src/utils/exportAs/dbml.js
index f1329cf82..ec39dd4db 100644
--- a/src/utils/exportAs/dbml.js
+++ b/src/utils/exportAs/dbml.js
@@ -3,7 +3,10 @@ import i18n from "../../i18n/i18n";
import { parseDefault } from "../exportSQL/shared";
function columnDefault(field, database) {
- if (!field.default || field.default.trim() === "") {
+ if (
+ !field.default ||
+ (typeof field.default === "string" && field.default.trim() === "")
+ ) {
return "";
}
diff --git a/src/utils/exportSQL/shared.js b/src/utils/exportSQL/shared.js
index 3cbf21a16..c2614c513 100644
--- a/src/utils/exportSQL/shared.js
+++ b/src/utils/exportSQL/shared.js
@@ -4,7 +4,10 @@ import { DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
export function parseDefault(field, database = DB.GENERIC) {
- if (!field.default || field.default.trim() == "") {
+ if (
+ !field.default ||
+ (typeof field.default === "string" && field.default.trim() == "")
+ ) {
return "";
}
diff --git a/src/utils/importFrom/dbml.js b/src/utils/importFrom/dbml.js
index 899dfeb18..31d7e3619 100644
--- a/src/utils/importFrom/dbml.js
+++ b/src/utils/importFrom/dbml.js
@@ -27,7 +27,7 @@ export function fromDBML(src) {
field.id = parsedTable.fields.length;
field.name = column.name;
field.type = column.type.type_name.toUpperCase();
- field.default = column.dbdefault ?? "";
+ field.default = column.dbdefault?.value ?? "";
field.check = "";
field.primary = !!column.pk;
field.unique = !!column.pk;
diff --git a/src/utils/issues.js b/src/utils/issues.js
index 6b3db61de..bba535592 100644
--- a/src/utils/issues.js
+++ b/src/utils/issues.js
@@ -7,7 +7,12 @@ function checkDefault(field, database) {
if (isFunction(field.default)) return true;
- if (!field.notNull && field.default.toLowerCase() === "null") return true;
+ if (
+ !field.notNull &&
+ typeof field.default === "string" &&
+ field.default.toLowerCase() === "null"
+ )
+ return true;
if (!dbToTypes[database][field.type].checkDefault) return true;
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 553c3401a..1d93e7a1d 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -30,7 +30,8 @@ export function strHasQuotes(str) {
const keywords = ["CURRENT_TIMESTAMP", "NULL"];
export function isKeyword(str) {
- return keywords.includes(str.toUpperCase());
+ if (typeof str === "string") return keywords.includes(str.toUpperCase());
+ return false;
}
export function isFunction(str) {
From 479d1cd680950fa476f6d1a7a5ac9c67ff7ea5d0 Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Sun, 25 May 2025 22:24:44 +0400
Subject: [PATCH 8/8] update stuff idek
---
src/components/CodeEditor/index.jsx | 19 +++--
src/components/EditorHeader/ControlPanel.jsx | 2 +-
.../EditorSidePanel/DBMLEditor/DBMLEditor.jsx | 43 +++++++++++
.../EditorSidePanel/DBMLEditor/index.jsx | 72 -------------------
src/components/EditorSidePanel/SidePanel.jsx | 23 +++++-
src/i18n/locales/en.js | 3 +-
6 files changed, 79 insertions(+), 83 deletions(-)
create mode 100644 src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
delete mode 100644 src/components/EditorSidePanel/DBMLEditor/index.jsx
diff --git a/src/components/CodeEditor/index.jsx b/src/components/CodeEditor/index.jsx
index baa07ca0c..df34824ee 100644
--- a/src/components/CodeEditor/index.jsx
+++ b/src/components/CodeEditor/index.jsx
@@ -6,7 +6,11 @@ import { IconCopy } from "@douyinfe/semi-icons";
import { setUpDBML } from "./setUpDBML";
import "./styles.css";
-export default function CodeEditor({ showCopyButton, ...props }) {
+export default function CodeEditor({
+ showCopyButton,
+ extraControls,
+ ...props
+}) {
const { settings } = useSettings();
const { database } = useDiagram();
const { t } = useTranslation();
@@ -36,11 +40,14 @@ export default function CodeEditor({ showCopyButton, ...props }) {
onMount={handleEditorMount}
/>
{showCopyButton && (
- }
- onClick={copyCode}
- />
+
+
{extraControls}
+
}
+ onClick={copyCode}
+ className="inline-block"
+ />
+
)}
);
diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx
index 499537365..de02a8307 100644
--- a/src/components/EditorHeader/ControlPanel.jsx
+++ b/src/components/EditorHeader/ControlPanel.jsx
@@ -1221,7 +1221,7 @@ export default function ControlPanel({
function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
},
- dbml_editor: {
+ dbml_view: {
state: layout.dbmlEditor ? (
) : (
diff --git a/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
new file mode 100644
index 000000000..30745f03e
--- /dev/null
+++ b/src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
@@ -0,0 +1,43 @@
+import { useEffect, useState } from "react";
+import { useDiagram, useEnums, useLayout } from "../../../hooks";
+import { toDBML } from "../../../utils/exportAs/dbml";
+import CodeEditor from "../../CodeEditor";
+import { Button, Tooltip } from "@douyinfe/semi-ui";
+import { IconTemplate } from "@douyinfe/semi-icons";
+import { useTranslation } from "react-i18next";
+
+export default function DBMLEditor() {
+ const { tables: currentTables, relationships } = useDiagram();
+ const diagram = useDiagram();
+ const { enums } = useEnums();
+ const [value, setValue] = useState(() => toDBML({ ...diagram, enums }));
+ const { setLayout } = useLayout();
+ const { t } = useTranslation();
+
+ const toggleDBMLEditor = () => {
+ setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
+ };
+
+ useEffect(() => {
+ setValue(toDBML({ tables: currentTables, enums, relationships }));
+ }, [currentTables, enums, relationships]);
+
+ return (
+
+ } onClick={toggleDBMLEditor} />
+
+ }
+ />
+ );
+}
diff --git a/src/components/EditorSidePanel/DBMLEditor/index.jsx b/src/components/EditorSidePanel/DBMLEditor/index.jsx
deleted file mode 100644
index 14126ae24..000000000
--- a/src/components/EditorSidePanel/DBMLEditor/index.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useEffect, useState } from "react";
-import { useDiagram, useEnums, useTransform } from "../../../hooks";
-import { useDebounceValue } from "usehooks-ts";
-import { fromDBML } from "../../../utils/importFrom/dbml";
-import { toDBML } from "../../../utils/exportAs/dbml";
-import CodeEditor from "../../CodeEditor";
-
-export default function DBMLEditor({ setIssues }) {
- const { tables: currentTables, setTables } = useDiagram();
- const diagram = useDiagram();
- const { enums } = useEnums();
- const { transform } = useTransform();
- const [value, setValue] = useState(() => toDBML({ ...diagram, enums }));
- const [debouncedValue] = useDebounceValue(value, 2000);
-
- useEffect(() => {
- const updateDiagram = () => {
- try {
- const currentDBML = toDBML({ ...diagram, enums });
-
- if (debouncedValue && debouncedValue !== currentDBML) {
- const { tables: newTables } = fromDBML(debouncedValue);
-
- const mergedTables = newTables
- .map((newTable) => {
- const existingTable = currentTables.find(
- (t) => t.id === newTable.id || t.name === newTable.name,
- );
-
- return {
- ...newTable,
- ...(existingTable
- ? {
- x: existingTable.x,
- y: existingTable.y,
- color: existingTable.color,
- id: existingTable.id,
- }
- : {
- x: transform.pan.x,
- y: transform.pan.y,
- }),
- };
- })
- .map((x, i) => ({ ...x, id: i }));
-
- setTables(mergedTables);
- }
- } catch (e) {
- setIssues((prev) => ({
- ...prev,
- dbml: e.diags?.map((x) => x.message) || [e.message],
- }));
- }
- };
-
- updateDiagram();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [debouncedValue]);
-
- return (
-
- );
-}
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 839dd11b1..99a623af8 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -1,5 +1,6 @@
-import { Tabs, TabPane } from "@douyinfe/semi-ui";
+import { Tabs, TabPane, Button, Divider, Tooltip } from "@douyinfe/semi-ui";
import { Tab } from "../../data/constants";
+import { IconCode } from "@douyinfe/semi-icons";
import {
useLayout,
useSelect,
@@ -21,10 +22,10 @@ import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
-import DBMLEditor from "./DBMLEditor";
+import DBMLEditor from "./DBMLEditor/DBMLEditor";
export default function SidePanel({ width, resize, setResize }) {
- const { layout } = useLayout();
+ const { layout, setLayout } = useLayout();
const { selectedElement, setSelectedElement } = useSelect();
const { database, tablesCount, relationshipsCount } = useDiagram();
const { areasCount } = useAreas();
@@ -86,6 +87,10 @@ export default function SidePanel({ width, resize, setResize }) {
notesCount,
]);
+ const toggleDBMLEditor = () => {
+ setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
+ };
+
return (
+
+
+ }
+ theme="borderless"
+ />
+
+ >
+ }
>
{tabList.length &&
tabList.map((tab) => (
diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js
index 59f2cfd83..96ecf99a3 100644
--- a/src/i18n/locales/en.js
+++ b/src/i18n/locales/en.js
@@ -251,7 +251,8 @@ const en = {
bulk_update: "Bulk update",
multiselect: "Multiselect",
export_saved_data: "Export saved data",
- dbml_editor: "DBML editor",
+ dbml_view: "DBML view",
+ tab_view: "Tab view",
},
};