Add export and import functions

This commit is contained in:
1ilit 2025-02-27 02:37:44 +04:00
parent e3877ef982
commit 81b5a73972
5 changed files with 253 additions and 12 deletions

53
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.6.3",
"@dbml/core": "^3.9.7-alpha.0",
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",
@ -536,6 +537,34 @@
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@dbml/core": {
"version": "3.9.7-alpha.0",
"resolved": "https://registry.npmjs.org/@dbml/core/-/core-3.9.7-alpha.0.tgz",
"integrity": "sha512-KGXr7p80XuoqQJumOs2+RHRBBH703gNxM0uiEvT1FF945+H4LriNK4ZgbXqe2ObmRNbwF2/TYFou+lqkh+tbUw==",
"license": "Apache-2.0",
"dependencies": {
"@dbml/parse": "^3.9.7-alpha.0",
"antlr4": "^4.13.1",
"lodash": "^4.17.15",
"parsimmon": "^1.13.0",
"pluralize": "^8.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@dbml/parse": {
"version": "3.9.7-alpha.0",
"resolved": "https://registry.npmjs.org/@dbml/parse/-/parse-3.9.7-alpha.0.tgz",
"integrity": "sha512-QT0rmbbnjn6hKbGXMhvdw62Gn8YgXjvG5a+0+9EoZFpFdl/Y8VSPlHqpHbdMas2kOpusMgpa1YRFaTMApZM7Mw==",
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.21"
},
"peerDependencies": {
"lodash": "^4.17.21"
}
},
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
@ -2351,6 +2380,15 @@
"node": ">=4"
}
},
"node_modules/antlr4": {
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz",
"integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=16"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -5065,6 +5103,12 @@
"node": ">=6"
}
},
"node_modules/parsimmon": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
"integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw==",
"license": "MIT"
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -5139,6 +5183,15 @@
"node": ">= 6"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postcss": {
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",

View File

@ -12,6 +12,7 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.6.3",
"@dbml/core": "^3.9.7-alpha.0",
"@douyinfe/semi-ui": "^2.51.3",
"@lexical/react": "^0.12.5",
"@uiw/codemirror-theme-github": "^4.21.25",

View File

@ -74,6 +74,7 @@ import { isRtl } from "../../i18n/utils/rtl";
import { jsonToDocumentation } from "../../utils/exportAs/documentation";
import { IdContext } from "../Workspace";
import { socials } from "../../data/socials";
import { toDBML } from "../../utils/exportAs/dbml";
export default function ControlPanel({
diagramId,
@ -963,6 +964,21 @@ export default function ControlPanel({
setModal(MODAL.IMG);
},
},
{
SVG: () => {
const filter = (node) => node.tagName !== "i";
toSvg(document.getElementById("canvas"), { filter: filter }).then(
function (dataUrl) {
setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "svg",
}));
},
);
setModal(MODAL.IMG);
},
},
{
JSON: () => {
setModal(MODAL.CODE);
@ -988,19 +1004,19 @@ export default function ControlPanel({
},
},
{
SVG: () => {
const filter = (node) => node.tagName !== "i";
toSvg(document.getElementById("canvas"), { filter: filter }).then(
function (dataUrl) {
DBML: () => {
setModal(MODAL.CODE);
const result = toDBML({
tables,
relationships,
enums,
});
setExportData((prev) => ({
...prev,
data: dataUrl,
extension: "svg",
data: result,
extension: "dbml",
}));
},
);
setModal(MODAL.IMG);
},
},
{
PDF: () => {

102
src/utils/exportAs/dbml.js Normal file
View File

@ -0,0 +1,102 @@
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")}`;
}

View File

@ -0,0 +1,69 @@
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);
}
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);
}
}
const diagram = { tables, enums };
arrangeTables(diagram);
return diagram;
}