mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-07-18 10:11:24 +00:00
Add dbml editor (#465)
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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 (
|
||||||
<>
|
<>
|
||||||
|
43
src/components/EditorSidePanel/DBMLEditor.jsx
Normal file
43
src/components/EditorSidePanel/DBMLEditor.jsx
Normal 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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@@ -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">
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user