From 0e45b0ede644a16e34735e2cea29ab7d61ca9fde Mon Sep 17 00:00:00 2001
From: 1ilit <1ilit@proton.me>
Date: Tue, 27 May 2025 18:09:55 +0400
Subject: [PATCH] Add dbml editor (#465)
---
src/components/CodeEditor/index.jsx | 21 ++++--
src/components/EditorHeader/ControlPanel.jsx | 13 ++++
src/components/EditorSidePanel/DBMLEditor.jsx | 43 ++++++++++++
src/components/EditorSidePanel/SidePanel.jsx | 70 +++++++++++++------
src/context/LayoutContext.jsx | 1 +
src/i18n/locales/en.js | 2 +
src/index.css | 2 +-
7 files changed, 122 insertions(+), 30 deletions(-)
create mode 100644 src/components/EditorSidePanel/DBMLEditor.jsx
diff --git a/src/components/CodeEditor/index.jsx b/src/components/CodeEditor/index.jsx
index 2bed794..df34824 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();
@@ -29,18 +33,21 @@ export default function CodeEditor({ showCopyButton, ...props }) {
};
return (
-
+
{showCopyButton && (
-
}
- onClick={copyCode}
- />
+
+
{extraControls}
+
}
+ onClick={copyCode}
+ className="inline-block"
+ />
+
)}
);
diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx
index e485806..bc519b2 100644
--- a/src/components/EditorHeader/ControlPanel.jsx
+++ b/src/components/EditorHeader/ControlPanel.jsx
@@ -693,6 +693,9 @@ export default function ControlPanel({
copy();
del();
};
+ const toggleDBMLEditor = () => {
+ setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
+ };
const save = () => setSaveState(State.SAVING);
const open = () => setModal(MODAL.OPEN);
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
@@ -1218,6 +1221,15 @@ export default function ControlPanel({
function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
},
+ dbml_view: {
+ state: layout.dbmlEditor ? (
+
+ ) : (
+
+ ),
+ function: toggleDBMLEditor,
+ shortcut: "Alt+E",
+ },
strict_mode: {
state: settings.strictMode ? (
@@ -1446,6 +1458,7 @@ export default function ControlPanel({
preventDefault: true,
});
useHotkeys("mod+alt+w", fitWindow, { preventDefault: true });
+ useHotkeys("alt+e", toggleDBMLEditor, { preventDefault: true });
return (
<>
diff --git a/src/components/EditorSidePanel/DBMLEditor.jsx b/src/components/EditorSidePanel/DBMLEditor.jsx
new file mode 100644
index 0000000..2a024e3
--- /dev/null
+++ b/src/components/EditorSidePanel/DBMLEditor.jsx
@@ -0,0 +1,43 @@
+import { useEffect, useState } from "react";
+import { useDiagram, useEnums, useLayout } from "../../hooks";
+import { toDBML } from "../../utils/exportAs/dbml";
+import { Button, Tooltip } from "@douyinfe/semi-ui";
+import { IconTemplate } from "@douyinfe/semi-icons";
+import { useTranslation } from "react-i18next";
+import CodeEditor from "../CodeEditor";
+
+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/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index 168c1f6..2cae1a8 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -1,4 +1,6 @@
-import { Tabs, TabPane } from "@douyinfe/semi-ui";
+import { useMemo } from "react";
+import { Tabs, TabPane, Divider, Tooltip, Button } from "@douyinfe/semi-ui";
+import { IconCode } from "@douyinfe/semi-icons";
import { Tab } from "../../data/constants";
import {
useLayout,
@@ -9,21 +11,21 @@ import {
useEnums,
useTypes,
} from "../../hooks";
+import { useTranslation } from "react-i18next";
import RelationshipsTab from "./RelationshipsTab/RelationshipsTab";
import TypesTab from "./TypesTab/TypesTab";
import Issues from "./Issues";
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 { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
+import DBMLEditor from "./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();
@@ -32,6 +34,10 @@ export default function SidePanel({ width, resize, setResize }) {
const { enumsCount } = useEnums();
const { t } = useTranslation();
+ const toggleDBMLEditor = () => {
+ setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
+ };
+
const tabList = useMemo(() => {
const tabs = [
{
@@ -91,24 +97,44 @@ export default function SidePanel({ width, resize, setResize }) {
style={{ width: `${width}px` }}
>
-
- setSelectedElement((prev) => ({ ...prev, currentTab: key }))
- }
- collapsible
- tabBarStyle={{ direction: "ltr" }}
- >
- {tabList.length &&
- tabList.map((tab) => (
-
- {tab.component}
-
- ))}
-
+ {layout.dbmlEditor ? (
+
+ ) : (
+
+ setSelectedElement((prev) => ({ ...prev, currentTab: key }))
+ }
+ collapsible
+ tabBarStyle={{ direction: "ltr" }}
+ tabBarExtraContent={
+ <>
+
+
+ }
+ theme="borderless"
+ />
+
+ >
+ }
+ >
+ {tabList.length &&
+ tabList.map((tab) => (
+
+ {tab.component}
+
+ ))}
+
+ )}
{layout.issues && (
diff --git a/src/context/LayoutContext.jsx b/src/context/LayoutContext.jsx
index cfb4836..5349b04 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/i18n/locales/en.js b/src/i18n/locales/en.js
index 5dc8ed4..96ecf99 100644
--- a/src/i18n/locales/en.js
+++ b/src/i18n/locales/en.js
@@ -251,6 +251,8 @@ const en = {
bulk_update: "Bulk update",
multiselect: "Multiselect",
export_saved_data: "Export saved data",
+ dbml_view: "DBML view",
+ tab_view: "Tab view",
},
};
diff --git a/src/index.css b/src/index.css
index 976ecf3..931b672 100644
--- a/src/index.css
+++ b/src/index.css
@@ -119,7 +119,7 @@
}
.toolbar-theme {
- background-color: rgba(var(--semi-grey-1), 1);
+ background-color: rgba(var(--semi-grey-1), 0.7);
}
.hover-1:hover {