mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-05-24 02:09:17 +00:00
add basic dbml editor
This commit is contained in:
parent
894ab774b3
commit
1e06914fe0
@ -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 (
|
|
||||||
<CodeEditor
|
|
||||||
value={value}
|
|
||||||
language="dbml"
|
|
||||||
onChange={(v) => setValue(v)}
|
|
||||||
height="100%"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
72
src/components/EditorSidePanel/DBMLEditor/index.jsx
Normal file
72
src/components/EditorSidePanel/DBMLEditor/index.jsx
Normal file
@ -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 (
|
||||||
|
<CodeEditor
|
||||||
|
value={value}
|
||||||
|
language="dbml"
|
||||||
|
onChange={setValue}
|
||||||
|
height="100%"
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ 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/DBMLEditor";
|
import DBMLEditor from "./DBMLEditor";
|
||||||
|
|
||||||
export default function SidePanel({ width, resize, setResize }) {
|
export default function SidePanel({ width, resize, setResize }) {
|
||||||
const { layout } = useLayout();
|
const { layout } = useLayout();
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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")}`;
|
|
||||||
}
|
|
@ -3,7 +3,10 @@ import i18n from "../../i18n/i18n";
|
|||||||
import { parseDefault } from "../exportSQL/shared";
|
import { parseDefault } from "../exportSQL/shared";
|
||||||
|
|
||||||
function columnDefault(field, database) {
|
function columnDefault(field, database) {
|
||||||
if (!field.default || field.default.trim() === "") {
|
if (
|
||||||
|
!field.default ||
|
||||||
|
(typeof field.default === "string" && field.default.trim() === "")
|
||||||
|
) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ import { DB } from "../../data/constants";
|
|||||||
import { dbToTypes } from "../../data/datatypes";
|
import { dbToTypes } from "../../data/datatypes";
|
||||||
|
|
||||||
export function parseDefault(field, database = DB.GENERIC) {
|
export function parseDefault(field, database = DB.GENERIC) {
|
||||||
if (!field.default || field.default.trim() == "") {
|
if (
|
||||||
|
!field.default ||
|
||||||
|
(typeof field.default === "string" && field.default.trim() == "")
|
||||||
|
) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export function fromDBML(src) {
|
|||||||
field.id = parsedTable.fields.length;
|
field.id = parsedTable.fields.length;
|
||||||
field.name = column.name;
|
field.name = column.name;
|
||||||
field.type = column.type.type_name.toUpperCase();
|
field.type = column.type.type_name.toUpperCase();
|
||||||
field.default = column.dbdefault ?? "";
|
field.default = column.dbdefault?.value ?? "";
|
||||||
field.check = "";
|
field.check = "";
|
||||||
field.primary = !!column.pk;
|
field.primary = !!column.pk;
|
||||||
field.unique = !!column.pk;
|
field.unique = !!column.pk;
|
||||||
|
@ -7,7 +7,12 @@ function checkDefault(field, database) {
|
|||||||
|
|
||||||
if (isFunction(field.default)) return true;
|
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;
|
if (!dbToTypes[database][field.type].checkDefault) return true;
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ export function strHasQuotes(str) {
|
|||||||
const keywords = ["CURRENT_TIMESTAMP", "NULL"];
|
const keywords = ["CURRENT_TIMESTAMP", "NULL"];
|
||||||
|
|
||||||
export function isKeyword(str) {
|
export function isKeyword(str) {
|
||||||
return keywords.includes(str.toUpperCase());
|
if (typeof str === "string") return keywords.includes(str.toUpperCase());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunction(str) {
|
export function isFunction(str) {
|
||||||
|
Loading…
Reference in New Issue
Block a user