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 { isRtl } from "../../i18n/utils/rtl";
|
||||
import i18n from "../../i18n/i18n";
|
||||
import DBMLEditor from "./DBMLEditor/DBMLEditor";
|
||||
import DBMLEditor from "./DBMLEditor";
|
||||
|
||||
export default function SidePanel({ width, resize, setResize }) {
|
||||
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";
|
||||
|
||||
function columnDefault(field, database) {
|
||||
if (!field.default || field.default.trim() === "") {
|
||||
if (
|
||||
!field.default ||
|
||||
(typeof field.default === "string" && field.default.trim() === "")
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,10 @@ import { DB } from "../../data/constants";
|
||||
import { dbToTypes } from "../../data/datatypes";
|
||||
|
||||
export function parseDefault(field, database = DB.GENERIC) {
|
||||
if (!field.default || field.default.trim() == "") {
|
||||
if (
|
||||
!field.default ||
|
||||
(typeof field.default === "string" && field.default.trim() == "")
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ export function fromDBML(src) {
|
||||
field.id = parsedTable.fields.length;
|
||||
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;
|
||||
|
@ -7,7 +7,12 @@ function checkDefault(field, database) {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -30,7 +30,8 @@ export function strHasQuotes(str) {
|
||||
const keywords = ["CURRENT_TIMESTAMP", "NULL"];
|
||||
|
||||
export function isKeyword(str) {
|
||||
return keywords.includes(str.toUpperCase());
|
||||
if (typeof str === "string") return keywords.includes(str.toUpperCase());
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isFunction(str) {
|
||||
|
Loading…
Reference in New Issue
Block a user