Add dbml editor (#465)

This commit is contained in:
1ilit
2025-05-27 18:09:55 +04:00
committed by GitHub
parent 2b8310910a
commit 0e45b0ede6
7 changed files with 122 additions and 30 deletions

View File

@@ -6,7 +6,11 @@ import { IconCopy } from "@douyinfe/semi-icons";
import { setUpDBML } from "./setUpDBML"; import { setUpDBML } from "./setUpDBML";
import "./styles.css"; import "./styles.css";
export default function CodeEditor({ showCopyButton, ...props }) { export default function CodeEditor({
showCopyButton,
extraControls,
...props
}) {
const { settings } = useSettings(); const { settings } = useSettings();
const { database } = useDiagram(); const { database } = useDiagram();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,18 +33,21 @@ export default function CodeEditor({ showCopyButton, ...props }) {
}; };
return ( return (
<div className="relative"> <div className="relative h-full">
<Editor <Editor
{...props} {...props}
theme={settings.mode === "light" ? "vs" : "vs-dark"} theme={settings.mode === "light" ? "vs" : "vs-dark"}
onMount={handleEditorMount} onMount={handleEditorMount}
/> />
{showCopyButton && ( {showCopyButton && (
<Button <div className="absolute right-6 bottom-2 z-10 space-y-2">
className="absolute right-6 bottom-2 z-10" <div>{extraControls}</div>
icon={<IconCopy />} <Button
onClick={copyCode} icon={<IconCopy />}
/> onClick={copyCode}
className="inline-block"
/>
</div>
)} )}
</div> </div>
); );

View File

@@ -693,6 +693,9 @@ export default function ControlPanel({
copy(); copy();
del(); del();
}; };
const toggleDBMLEditor = () => {
setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
};
const save = () => setSaveState(State.SAVING); const save = () => setSaveState(State.SAVING);
const open = () => setModal(MODAL.OPEN); const open = () => setModal(MODAL.OPEN);
const saveDiagramAs = () => setModal(MODAL.SAVEAS); const saveDiagramAs = () => setModal(MODAL.SAVEAS);
@@ -1218,6 +1221,15 @@ export default function ControlPanel({
function: () => function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })), setLayout((prev) => ({ ...prev, issues: !prev.issues })),
}, },
dbml_view: {
state: layout.dbmlEditor ? (
<i className="bi bi-toggle-off" />
) : (
<i className="bi bi-toggle-on" />
),
function: toggleDBMLEditor,
shortcut: "Alt+E",
},
strict_mode: { strict_mode: {
state: settings.strictMode ? ( state: settings.strictMode ? (
<i className="bi bi-toggle-off" /> <i className="bi bi-toggle-off" />
@@ -1446,6 +1458,7 @@ export default function ControlPanel({
preventDefault: true, preventDefault: true,
}); });
useHotkeys("mod+alt+w", fitWindow, { preventDefault: true }); useHotkeys("mod+alt+w", fitWindow, { preventDefault: true });
useHotkeys("alt+e", toggleDBMLEditor, { preventDefault: true });
return ( return (
<> <>

View File

@@ -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 (
<CodeEditor
showCopyButton
value={value}
language="dbml"
onChange={setValue}
height="100%"
options={{
readOnly: true,
minimap: { enabled: false },
}}
extraControls={
<Tooltip content={t("tab_view")}>
<Button icon={<IconTemplate />} onClick={toggleDBMLEditor} />
</Tooltip>
}
/>
);
}

View File

@@ -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 { Tab } from "../../data/constants";
import { import {
useLayout, useLayout,
@@ -9,21 +11,21 @@ import {
useEnums, useEnums,
useTypes, useTypes,
} from "../../hooks"; } from "../../hooks";
import { useTranslation } from "react-i18next";
import RelationshipsTab from "./RelationshipsTab/RelationshipsTab"; import RelationshipsTab from "./RelationshipsTab/RelationshipsTab";
import TypesTab from "./TypesTab/TypesTab"; import TypesTab from "./TypesTab/TypesTab";
import Issues from "./Issues"; import Issues from "./Issues";
import AreasTab from "./AreasTab/AreasTab"; import AreasTab from "./AreasTab/AreasTab";
import NotesTab from "./NotesTab/NotesTab"; import NotesTab from "./NotesTab/NotesTab";
import TablesTab from "./TablesTab/TablesTab"; import TablesTab from "./TablesTab/TablesTab";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
import { databases } from "../../data/databases"; import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab"; import EnumsTab from "./EnumsTab/EnumsTab";
import { isRtl } from "../../i18n/utils/rtl"; import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n"; import i18n from "../../i18n/i18n";
import DBMLEditor from "./DBMLEditor";
export default function SidePanel({ width, resize, setResize }) { export default function SidePanel({ width, resize, setResize }) {
const { layout } = useLayout(); const { layout, setLayout } = useLayout();
const { selectedElement, setSelectedElement } = useSelect(); const { selectedElement, setSelectedElement } = useSelect();
const { database, tablesCount, relationshipsCount } = useDiagram(); const { database, tablesCount, relationshipsCount } = useDiagram();
const { areasCount } = useAreas(); const { areasCount } = useAreas();
@@ -32,6 +34,10 @@ export default function SidePanel({ width, resize, setResize }) {
const { enumsCount } = useEnums(); const { enumsCount } = useEnums();
const { t } = useTranslation(); const { t } = useTranslation();
const toggleDBMLEditor = () => {
setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
};
const tabList = useMemo(() => { const tabList = useMemo(() => {
const tabs = [ const tabs = [
{ {
@@ -91,24 +97,44 @@ export default function SidePanel({ width, resize, setResize }) {
style={{ width: `${width}px` }} style={{ width: `${width}px` }}
> >
<div className="h-full flex-1 overflow-y-auto"> <div className="h-full flex-1 overflow-y-auto">
<Tabs {layout.dbmlEditor ? (
type="card" <DBMLEditor />
activeKey={selectedElement.currentTab} ) : (
lazyRender <Tabs
keepDOM={false} type="card"
onChange={(key) => activeKey={selectedElement.currentTab}
setSelectedElement((prev) => ({ ...prev, currentTab: key })) lazyRender
} keepDOM={false}
collapsible onChange={(key) =>
tabBarStyle={{ direction: "ltr" }} setSelectedElement((prev) => ({ ...prev, currentTab: key }))
> }
{tabList.length && collapsible
tabList.map((tab) => ( tabBarStyle={{ direction: "ltr" }}
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}> tabBarExtraContent={
<div className="p-2">{tab.component}</div> <>
</TabPane> <Divider layout="vertical" />
))} <Tooltip content={t("dbml_view")} position="bottom">
</Tabs> <Button
onClick={toggleDBMLEditor}
icon={<IconCode />}
theme="borderless"
/>
</Tooltip>
</>
}
>
{tabList.length &&
tabList.map((tab) => (
<TabPane
tab={tab.tab}
itemKey={tab.itemKey}
key={tab.itemKey}
>
<div className="p-2">{tab.component}</div>
</TabPane>
))}
</Tabs>
)}
</div> </div>
{layout.issues && ( {layout.issues && (
<div className="mt-auto border-t-2 border-color shadow-inner"> <div className="mt-auto border-t-2 border-color shadow-inner">

View File

@@ -8,6 +8,7 @@ export default function LayoutContextProvider({ children }) {
sidebar: true, sidebar: true,
issues: true, issues: true,
toolbar: true, toolbar: true,
dbmlEditor: false,
}); });
return ( return (

View File

@@ -251,6 +251,8 @@ const en = {
bulk_update: "Bulk update", bulk_update: "Bulk update",
multiselect: "Multiselect", multiselect: "Multiselect",
export_saved_data: "Export saved data", export_saved_data: "Export saved data",
dbml_view: "DBML view",
tab_view: "Tab view",
}, },
}; };

View File

@@ -119,7 +119,7 @@
} }
.toolbar-theme { .toolbar-theme {
background-color: rgba(var(--semi-grey-1), 1); background-color: rgba(var(--semi-grey-1), 0.7);
} }
.hover-1:hover { .hover-1:hover {