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) => ( +
+ +
{e}
+
+ ))} + {issues.diagram.map((e, i) => ( +
+ +
{e}
))} 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 && ( -
)}
); 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 ( + +