Skip to content

Add dbml editor in side panel #462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
21 changes: 14 additions & 7 deletions src/components/CodeEditor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -29,18 +33,21 @@ export default function CodeEditor({ showCopyButton, ...props }) {
};

return (
<div className="relative">
<div className="relative h-full">
<Editor
{...props}
theme={settings.mode === "light" ? "vs" : "vs-dark"}
onMount={handleEditorMount}
/>
{showCopyButton && (
<Button
className="absolute right-6 bottom-2 z-10"
icon={<IconCopy />}
onClick={copyCode}
/>
<div className="absolute right-6 bottom-2 z-10 space-y-2">
<div>{extraControls}</div>
<Button
icon={<IconCopy />}
onClick={copyCode}
className="inline-block"
/>
</div>
)}
</div>
);
Expand Down
15 changes: 14 additions & 1 deletion src/components/EditorHeader/ControlPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,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,
Expand Down Expand Up @@ -1218,6 +1221,15 @@ export default function ControlPanel({
function: () =>
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: {
state: settings.strictMode ? (
<i className="bi bi-toggle-off" />
Expand Down Expand Up @@ -1446,6 +1458,7 @@ export default function ControlPanel({
preventDefault: true,
});
useHotkeys("mod+alt+w", fitWindow, { preventDefault: true });
useHotkeys("alt+e", toggleDBMLEditor, { preventDefault: true });

return (
<>
Expand Down Expand Up @@ -1492,7 +1505,7 @@ export default function ControlPanel({
function toolbar() {
return (
<div
className="py-1.5 px-5 flex justify-between items-center rounded-xl my-1 sm:mx-1 xl:mx-6 select-none overflow-hidden toolbar-theme"
className="py-1.5 px-5 flex justify-between items-center rounded-lg my-1 sm:mx-1 xl:mx-6 select-none overflow-hidden toolbar-theme"
style={isRtl(i18n.language) ? { direction: "rtl" } : {}}
>
<div className="flex justify-start items-center">
Expand Down
43 changes: 43 additions & 0 deletions src/components/EditorSidePanel/DBMLEditor/DBMLEditor.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<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>
}
/>
);
}
48 changes: 35 additions & 13 deletions src/components/EditorSidePanel/Issues.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
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 {
useEnums,
useSettings,
useDiagram,
useTypes,
useLayout,
} 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([]);
const { layout } = useLayout();

useEffect(() => {
const findIssues = async () => {
Expand All @@ -23,21 +29,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 (
<Collapse lazyRender keepDOM={false} style={{ width: "100%" }}>
<Collapse.Panel
header={
<Badge
type={issues.length > 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"
>
Expand All @@ -52,11 +66,19 @@ export default function Issues() {
<div className="max-h-[160px] overflow-y-auto">
{settings.strictMode ? (
<div className="mb-1">{t("strict_mode_is_on_no_issues")}</div>
) : issues.length > 0 ? (
) : issues.dbml.length > 0 || issues.diagram.length > 0 ? (
<>
{issues.map((e, i) => (
<div key={i} className="py-2">
{e}
{!layout.dbmlEditor &&
issues.dbml.map((e, i) => (
<div key={i} className="py-1 flex gap-2">
<i className="opacity-60 bi bi-braces" />
<div>{e}</div>
</div>
))}
{issues.diagram.map((e, i) => (
<div key={i} className="py-1 flex gap-2">
<i className="opacity-60 bi bi-diagram-2" />
<div>{e}</div>
</div>
))}
</>
Expand Down
71 changes: 49 additions & 22 deletions src/components/EditorSidePanel/SidePanel.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -16,21 +17,23 @@ 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";
import i18n from "../../i18n/i18n";
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();
const { notesCount } = useNotes();
const { typesCount } = useTypes();
const { enumsCount } = useEnums();
const { t } = useTranslation();
const [issues, setIssues] = useState({ diagram: [], dbml: [] });

const tabList = useMemo(() => {
const tabs = [
Expand Down Expand Up @@ -84,35 +87,59 @@ export default function SidePanel({ width, resize, setResize }) {
notesCount,
]);

const toggleDBMLEditor = () => {
setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor }));
};

return (
<div className="flex h-full">
<div
className="flex flex-col h-full relative border-r border-color"
style={{ width: `${width}px` }}
>
<div className="h-full flex-1 overflow-y-auto">
<Tabs
type="card"
activeKey={selectedElement.currentTab}
lazyRender
keepDOM={false}
onChange={(key) =>
setSelectedElement((prev) => ({ ...prev, currentTab: key }))
}
collapsible
tabBarStyle={{ direction: "ltr" }}
>
{tabList.length &&
tabList.map((tab) => (
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}>
<div className="p-2">{tab.component}</div>
</TabPane>
))}
</Tabs>
{layout.dbmlEditor ? (
<DBMLEditor setIssues={setIssues} />
) : (
<Tabs
type="card"
activeKey={selectedElement.currentTab}
lazyRender
keepDOM={false}
onChange={(key) =>
setSelectedElement((prev) => ({ ...prev, currentTab: key }))
}
collapsible
tabBarStyle={{ direction: "ltr" }}
tabBarExtraContent={
<>
<Divider layout="vertical" />
<Tooltip content={t("dbml_view")} position="bottom">
<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>
{layout.issues && (
<div className="mt-auto border-t-2 border-color shadow-inner">
<Issues />
<Issues issues={issues} setIssues={setIssues} />
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/context/LayoutContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default function LayoutContextProvider({ children }) {
sidebar: true,
issues: true,
toolbar: true,
dbmlEditor: false,
});

return (
Expand Down
5 changes: 5 additions & 0 deletions src/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export const DB = {
GENERIC: "generic",
};

export const ErrorType = {
DIAGRAM: "diagram",
DBML: "dbml",
};

export const IMPORT_FROM = {
JSON: 0,
DBML: 1,
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
};

Expand Down
4 changes: 2 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
background-color: rgba(var(--semi-blue-6), 1);
}

.semi-spin-wrapper{
.semi-spin-wrapper {
color: inherit;
}

Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion src/utils/exportAs/dbml.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import i18n from "../../i18n/i18n";
import { escapeQuotes, 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 "";
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/importFrom/dbml.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function fromDBML(src) {
field.id = nanoid();
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;
Expand Down
Loading