Table and field drag and drop ordering (#444)

* Add dnd for tables and fields

* Fix inputs

* Decouple ids and indecies in the editor

* Decouple ids and indecies in utils

* Fix field indexes

* Use nanoid instead of numberic ids for fields and tables

* Fix review comments
This commit is contained in:
1ilit
2025-05-11 21:44:04 +04:00
committed by GitHub
parent e35fbde3e7
commit 94226de561
44 changed files with 990 additions and 955 deletions

76
package-lock.json generated
View File

@@ -9,6 +9,9 @@
"version": "0.0.0",
"dependencies": {
"@dbml/core": "^3.9.7-alpha.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@douyinfe/semi-ui": "^2.77.1",
"@lexical/react": "^0.12.5",
"@monaco-editor/react": "^4.7.0",
@@ -27,6 +30,7 @@
"jspdf": "^3.0.1",
"jszip": "^3.10.1",
"lexical": "^0.12.5",
"nanoid": "^5.1.5",
"node-sql-parser": "^5.3.9",
"oracle-sql-parser": "^0.1.0",
"react": "^18.2.0",
@@ -402,9 +406,10 @@
}
},
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
"integrity": "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
@@ -413,11 +418,12 @@
}
},
"node_modules/@dnd-kit/core": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz",
"integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.0",
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
@@ -427,15 +433,16 @@
}
},
"node_modules/@dnd-kit/sortable": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
"integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
"license": "MIT",
"dependencies": {
"@dnd-kit/utilities": "^3.2.0",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@dnd-kit/core": "^6.0.7",
"@dnd-kit/core": "^6.3.0",
"react": ">=16.8.0"
}
},
@@ -443,6 +450,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
@@ -568,6 +576,20 @@
"react-dom": ">=16.0.0"
}
},
"node_modules/@douyinfe/semi-ui/node_modules/@dnd-kit/sortable": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
"integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
"license": "MIT",
"dependencies": {
"@dnd-kit/utilities": "^3.2.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@dnd-kit/core": "^6.0.7",
"react": ">=16.8.0"
}
},
"node_modules/@emotion/is-prop-valid": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
@@ -6214,10 +6236,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
"funding": [
{
"type": "github",
@@ -6226,10 +6247,10 @@
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
@@ -6571,6 +6592,25 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@@ -11,6 +11,9 @@
},
"dependencies": {
"@dbml/core": "^3.9.7-alpha.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@douyinfe/semi-ui": "^2.77.1",
"@lexical/react": "^0.12.5",
"@monaco-editor/react": "^4.7.0",
@@ -29,6 +32,7 @@
"jspdf": "^3.0.1",
"jszip": "^3.10.1",
"lexical": "^0.12.5",
"nanoid": "^5.1.5",
"node-sql-parser": "^5.3.9",
"oracle-sql-parser": "^0.1.0",
"react": "^18.2.0",

View File

@@ -55,7 +55,7 @@ export default function Canvas() {
} = useSelect();
const [dragging, setDragging] = useState({
element: ObjectType.NONE,
id: -1,
id: null,
prevX: 0,
prevY: 0,
initialPositions: [],
@@ -73,8 +73,8 @@ export default function Canvas() {
});
const [grabOffset, setGrabOffset] = useState({ x: 0, y: 0 });
const [hoveredTable, setHoveredTable] = useState({
tableId: -1,
field: -2,
tableId: null,
fieldId: null,
});
const [panning, setPanning] = useState({
isPanning: false,
@@ -167,7 +167,7 @@ export default function Canvas() {
const getElement = (element) => {
switch (element.type) {
case ObjectType.TABLE:
return tables[element.id];
return tables.find((t) => t.id === element.id);
case ObjectType.AREA:
return areas[element.id];
case ObjectType.NOTE:
@@ -264,7 +264,7 @@ export default function Canvas() {
});
} else if (
dragging.element !== ObjectType.NONE &&
dragging.id >= 0 &&
dragging.id !== null &&
bulkSelectedElements.length
) {
const currentX = pointer.spaces.diagram.x + grabOffset.x;
@@ -274,9 +274,10 @@ export default function Canvas() {
for (const element of bulkSelectedElements) {
if (element.type === ObjectType.TABLE) {
const { x, y } = tables.find((e) => e.id === element.id);
updateTable(element.id, {
x: tables[element.id].x + deltaX,
y: tables[element.id].y + deltaY,
x: x + deltaX,
y: y + deltaY,
});
}
if (element.type === ObjectType.AREA) {
@@ -317,21 +318,21 @@ export default function Canvas() {
(panning.cursorStart.y - pointer.spaces.screen.y) / transform.zoom,
},
}));
} else if (dragging.element === ObjectType.TABLE && dragging.id >= 0) {
} else if (dragging.element === ObjectType.TABLE && dragging.id !== null) {
updateTable(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y,
});
} else if (
dragging.element === ObjectType.AREA &&
dragging.id >= 0 &&
dragging.id !== null &&
areaResize.id === -1
) {
updateArea(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y,
});
} else if (dragging.element === ObjectType.NOTE && dragging.id >= 0) {
} else if (dragging.element === ObjectType.NOTE && dragging.id !== null) {
updateNote(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y,
@@ -441,7 +442,10 @@ export default function Canvas() {
};
const didPan = () =>
!(transform.pan.x === panning.panStart.x && transform.pan.y === panning.panStart.y);
!(
transform.pan.x === panning.panStart.x &&
transform.pan.y === panning.panStart.y
);
/**
* @param {PointerEvent} e
@@ -503,7 +507,7 @@ export default function Canvas() {
}
setDragging({
element: ObjectType.NONE,
id: -1,
id: null,
prevX: 0,
prevY: 0,
initialPositions: [],
@@ -585,7 +589,7 @@ export default function Canvas() {
setPanning((old) => ({ ...old, isPanning: false }));
setDragging({
element: ObjectType.NONE,
id: -1,
id: null,
prevX: 0,
prevY: 0,
initialPositions: [],
@@ -594,34 +598,40 @@ export default function Canvas() {
};
const handleLinking = () => {
if (hoveredTable.tableId < 0) return;
if (hoveredTable.field < 0) return;
if (
!areFieldsCompatible(
database,
tables[linkingLine.startTableId].fields[linkingLine.startFieldId],
tables[hoveredTable.tableId].fields[hoveredTable.field],
)
) {
if (hoveredTable.tableId === null) return;
if (hoveredTable.fieldId === null) return;
const { fields: startTableFields, name: startTableName } = tables.find(
(t) => t.id === linkingLine.startTableId,
);
const { type: startType, name: startFieldName } = startTableFields.find(
(f) => f.id === linkingLine.startFieldId,
);
const { fields: endTableFields, name: endTableName } = tables.find(
(t) => t.id === hoveredTable.tableId,
);
const { type: endType } = endTableFields.find(
(f) => f.id === hoveredTable.fieldId,
);
if (!areFieldsCompatible(database, startType, endType)) {
Toast.info(t("cannot_connect"));
return;
}
if (
linkingLine.startTableId === hoveredTable.tableId &&
linkingLine.startFieldId === hoveredTable.field
linkingLine.startFieldId === hoveredTable.fieldId
)
return;
const newRelationship = {
...linkingLine,
endTableId: hoveredTable.tableId,
endFieldId: hoveredTable.field,
endFieldId: hoveredTable.fieldId,
cardinality: Cardinality.ONE_TO_ONE,
updateConstraint: Constraint.NONE,
deleteConstraint: Constraint.NONE,
name: `fk_${tables[linkingLine.startTableId].name}_${
tables[linkingLine.startTableId].fields[linkingLine.startFieldId].name
}_${tables[hoveredTable.tableId].name}`,
name: `fk_${startTableName}_${startFieldName}_${endTableName}`,
id: relationships.length,
};
delete newRelationship.startX;

View File

@@ -1,4 +1,4 @@
import { useRef } from "react";
import { useMemo, useRef } from "react";
import {
Cardinality,
darkBgTheme,
@@ -20,6 +20,22 @@ export default function Relationship({ data }) {
const { selectedElement, setSelectedElement } = useSelect();
const { t } = useTranslation();
const pathValues = useMemo(() => {
const startTable = tables.find((t) => t.id === data.startTableId);
const endTable = tables.find((t) => t.id === data.endTableId);
if (!startTable || !endTable) return null;
return {
startFieldIndex: startTable.fields.findIndex(
(f) => f.id === data.startFieldId,
),
endFieldIndex: endTable.fields.findIndex((f) => f.id === data.endFieldId),
startTable: { x: startTable.x, y: startTable.y },
endTable: { x: endTable.x, y: endTable.y },
};
}, [tables, data]);
const theme = localStorage.getItem("theme");
const pathRef = useRef();
@@ -106,20 +122,7 @@ export default function Relationship({ data }) {
<g className="select-none group" onDoubleClick={edit}>
<path
ref={pathRef}
d={calcPath(
{
...data,
startTable: {
x: tables[data.startTableId].x,
y: tables[data.startTableId].y,
},
endTable: {
x: tables[data.endTableId].x,
y: tables[data.endTableId].y,
},
},
settings.tableWidth,
)}
d={calcPath(pathValues, settings.tableWidth)}
stroke="gray"
className="group-hover:stroke-sky-700"
fill="none"

View File

@@ -22,7 +22,7 @@ import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
export default function Table(props) {
const [hoveredField, setHoveredField] = useState(-1);
const [hoveredField, setHoveredField] = useState(null);
const { database } = useDiagram();
const {
tableData,
@@ -45,9 +45,10 @@ export default function Table(props) {
const height =
tableData.fields.length * tableFieldHeight + tableHeaderHeight + 7;
const isSelected = useMemo(() => {
return (
(selectedElement.id === tableData.id &&
(selectedElement.id == tableData.id &&
selectedElement.element === ObjectType.TABLE) ||
bulkSelectedElements.some(
(e) => e.type === ObjectType.TABLE && e.id === tableData.id,
@@ -124,7 +125,7 @@ export default function Table(props) {
onClick={openEditor}
/>
<Popover
key={tableData.key}
key={tableData.id}
content={
<div className="popover-theme">
<div className="mb-2">
@@ -304,13 +305,17 @@ export default function Table(props) {
setHoveredField(index);
setHoveredTable({
tableId: tableData.id,
field: index,
fieldId: fieldData.id,
});
}}
onPointerLeave={(e) => {
if (!e.isPrimary) return;
setHoveredField(-1);
setHoveredField(null);
setHoveredTable({
tableId: null,
fieldId: null,
});
}}
onPointerDown={(e) => {
// Required for onPointerLeave to trigger when a touch pointer leaves
@@ -328,10 +333,10 @@ export default function Table(props) {
onPointerDown={(e) => {
if (!e.isPrimary) return;
handleGripField(index);
handleGripField();
setLinkingLine((prev) => ({
...prev,
startFieldId: index,
startFieldId: fieldData.id,
startTableId: tableData.id,
startX: tableData.x + 15,
startY:

View File

@@ -78,6 +78,7 @@ import { IdContext } from "../Workspace";
import { socials } from "../../data/socials";
import { toDBML } from "../../utils/exportAs/dbml";
import { exportSavedData } from "../../utils/exportSavedData";
import { nanoid } from "nanoid";
export default function ControlPanel({
diagramId,
@@ -144,13 +145,12 @@ export default function ControlPanel({
}
}
setRedoStack((prev) => [...prev, a]);
console.log(a);
return;
}
if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) {
deleteTable(tables[tables.length - 1].id, false);
deleteTable(a.id, false);
} else if (a.element === ObjectType.AREA) {
deleteArea(areas[areas.length - 1].id, false);
} else if (a.element === ObjectType.NOTE) {
@@ -165,10 +165,8 @@ export default function ControlPanel({
setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) {
setRedoStack((prev) => [
...prev,
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
const { x, y } = tables.find((t) => t.id === a.id);
setRedoStack((prev) => [...prev, { ...a, x, y }]);
updateTable(a.id, { x: a.x, y: a.y });
} else if (a.element === ObjectType.AREA) {
setRedoStack((prev) => [
@@ -205,6 +203,7 @@ export default function ControlPanel({
} else if (a.element === ObjectType.NOTE) {
updateNote(a.nid, a.undo);
} else if (a.element === ObjectType.TABLE) {
const table = tables.find((t) => t.id === a.tid);
if (a.component === "field") {
updateField(a.tid, a.fid, a.undo);
} else if (a.component === "field_delete") {
@@ -213,65 +212,24 @@ export default function ControlPanel({
a.data.relationship.forEach((r) => {
temp.splice(r.id, 0, r);
});
temp = temp.map((e, i) => {
const recoveredRel = a.data.relationship.find(
(x) =>
(x.startTableId === e.startTableId &&
x.startFieldId === e.startFieldId) ||
(x.endTableId === e.endTableId &&
x.endFieldId === a.endFieldId),
);
if (
e.startTableId === a.tid &&
e.startFieldId >= a.data.field.id &&
!recoveredRel
) {
return {
...e,
id: i,
startFieldId: e.startFieldId + 1,
};
}
if (
e.endTableId === a.tid &&
e.endFieldId >= a.data.field.id &&
!recoveredRel
) {
return {
...e,
id: i,
endFieldId: e.endFieldId + 1,
};
}
return { ...e, id: i };
});
return temp;
});
setTables((prev) =>
prev.map((t) => {
if (t.id === a.tid) {
const temp = t.fields.slice();
temp.splice(a.data.field.id, 0, a.data.field);
return { ...t, fields: temp.map((t, i) => ({ ...t, id: i })) };
}
return t;
}),
);
const updatedFields = table.fields.slice();
updatedFields.splice(a.data.index, 0, a.data.field);
updateTable(a.tid, { fields: updatedFields });
} else if (a.component === "field_add") {
updateTable(a.tid, {
fields: tables[a.tid].fields
.filter((e) => e.id !== tables[a.tid].fields.length - 1)
.map((t, i) => ({ ...t, id: i })),
fields: table.fields.filter((e) => e.id !== a.fid),
});
} else if (a.component === "index_add") {
updateTable(a.tid, {
indices: tables[a.tid].indices
.filter((e) => e.id !== tables[a.tid].indices.length - 1)
indices: table.indices
.filter((e) => e.id !== table.indices.length - 1)
.map((t, i) => ({ ...t, id: i })),
});
} else if (a.component === "index") {
updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) =>
indices: table.indices.map((index) =>
index.id === a.iid
? {
...index,
@@ -281,19 +239,11 @@ export default function ControlPanel({
),
});
} else if (a.component === "index_delete") {
setTables((prev) =>
prev.map((table) => {
if (table.id === a.tid) {
const temp = table.indices.slice();
temp.splice(a.data.id, 0, a.data);
return {
...table,
indices: temp.map((t, i) => ({ ...t, id: i })),
};
}
return table;
}),
);
const updatedIndices = table.indices.slice();
updatedIndices.splice(a.data.id, 0, a.data);
updateTable(a.tid, {
indices: updatedIndices.map((t, i) => ({ ...t, id: i })),
});
} else if (a.component === "self") {
updateTable(a.tid, a.undo);
}
@@ -390,10 +340,8 @@ export default function ControlPanel({
setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) {
setUndoStack((prev) => [
...prev,
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
const { x, y } = tables.find((t) => t.id == a.id);
setUndoStack((prev) => [...prev, { ...a, x, y }]);
updateTable(a.id, { x: a.x, y: a.y });
} else if (a.element === ObjectType.AREA) {
setUndoStack((prev) => [
@@ -429,6 +377,7 @@ export default function ControlPanel({
} else if (a.element === ObjectType.NOTE) {
updateNote(a.nid, a.redo);
} else if (a.element === ObjectType.TABLE) {
const table = tables.find((t) => t.id === a.tid);
if (a.component === "field") {
updateField(a.tid, a.fid, a.redo);
} else if (a.component === "field_delete") {
@@ -436,7 +385,7 @@ export default function ControlPanel({
} else if (a.component === "field_add") {
updateTable(a.tid, {
fields: [
...tables[a.tid].fields,
...table.fields,
{
name: "",
type: "",
@@ -447,32 +396,24 @@ export default function ControlPanel({
notNull: false,
increment: false,
comment: "",
id: tables[a.tid].fields.length,
id: nanoid(),
},
],
});
} else if (a.component === "index_add") {
setTables((prev) =>
prev.map((table) => {
if (table.id === a.tid) {
return {
...table,
indices: [
...table.indices,
{
id: table.indices.length,
name: `index_${table.indices.length}`,
fields: [],
},
],
};
}
return table;
}),
);
updateTable(a.tid, {
indices: [
...table.indices,
{
id: table.indices.length,
name: `index_${table.indices.length}`,
fields: [],
},
],
});
} else if (a.component === "index") {
updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) =>
indices: table.indices.map((index) =>
index.id === a.iid
? {
...index,
@@ -483,7 +424,7 @@ export default function ControlPanel({
});
} else if (a.component === "index_delete") {
updateTable(a.tid, {
indices: tables[a.tid].indices
indices: table.indices
.filter((e) => e.id !== a.data.id)
.map((t, i) => ({ ...t, id: i })),
});
@@ -662,14 +603,16 @@ export default function ControlPanel({
};
const duplicate = () => {
switch (selectedElement.element) {
case ObjectType.TABLE:
case ObjectType.TABLE: {
const copiedTable = tables.find((t) => t.id === selectedElement.id);
addTable({
...tables[selectedElement.id],
x: tables[selectedElement.id].x + 20,
y: tables[selectedElement.id].y + 20,
...copiedTable,
x: copiedTable.x + 20,
y: copiedTable.y + 20,
id: tables.length,
});
break;
}
case ObjectType.NOTE:
addNote({
...notes[selectedElement.id],
@@ -694,7 +637,9 @@ export default function ControlPanel({
switch (selectedElement.element) {
case ObjectType.TABLE:
navigator.clipboard
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
.writeText(
JSON.stringify(tables.find((t) => t.id === selectedElement.id)),
)
.catch(() => Toast.error(t("oops_smth_went_wrong")));
break;
case ObjectType.NOTE:
@@ -1201,17 +1146,18 @@ export default function ControlPanel({
setRedoStack([]);
if (!diagramId) {
console.error("Something went wrong.");
Toast.error(t("oops_smth_went_wrong"));
return;
}
db.table("diagrams")
.delete(diagramId)
.then(() => {
console.info('Deleted diagram successfully.')
})
.catch((error) => {
console.error(`Error deleting records with gistId '${diagramId}':`, error);
Toast.error(t("oops_smth_went_wrong"));
console.error(
`Error deleting records with gistId '${diagramId}':`,
error,
);
});
},
},

View File

@@ -83,10 +83,12 @@ export default function ImportDiagram({
let ok = true;
jsonObject.relationships.forEach((rel) => {
if (
!jsonObject.tables[rel.startTableId] ||
!jsonObject.tables[rel.endTableId]
) {
const startTable = jsonObject.tables.find(
(t) => t.id === rel.startTableId,
);
const endTable = jsonObject.tables.find((t) => t.id === rel.endTableId);
if (!startTable || !endTable) {
setError({
type: STATUS.ERROR,
message: `Relationship ${rel.name} references a table that does not exist.`,
@@ -96,8 +98,8 @@ export default function ImportDiagram({
}
if (
!jsonObject.tables[rel.startTableId].fields[rel.startFieldId] ||
!jsonObject.tables[rel.endTableId].fields[rel.endFieldId]
!startTable.fields.find((f) => f.id === rel.startFieldId) ||
!endTable.fields.find((f) => f.id === rel.endFieldId)
) {
setError({
type: STATUS.ERROR,

View File

@@ -59,8 +59,7 @@ export default function Modal({
importFrom,
}) {
const { t, i18n } = useTranslation();
const { tables, setTables, setRelationships, database, setDatabase } =
useDiagram();
const { setTables, setRelationships, database, setDatabase } = useDiagram();
const { setNotes } = useNotes();
const { setAreas } = useAreas();
const { setTypes } = useTypes();
@@ -182,15 +181,10 @@ export default function Modal({
setUndoStack([]);
setRedoStack([]);
} else {
const initialTablesLength = tables.length;
setTables((prev) =>
[...prev, ...diagramData.tables].map((t, i) => ({ ...t, id: i })),
);
setTables((prev) => [...prev, ...diagramData.tables]);
setRelationships((prev) =>
[...prev, ...diagramData.relationships].map((r, i) => ({
...r,
startTableId: initialTablesLength + r.startTableId,
endTableId: initialTablesLength + r.endTableId,
id: i,
})),
);

View File

@@ -31,6 +31,7 @@ export default function Share({ title, setModal }) {
const diagramToString = useCallback(() => {
return JSON.stringify({
title,
tables: tables,
relationships: relationships,
notes: notes,
@@ -38,7 +39,6 @@ export default function Share({ title, setModal }) {
database: database,
...(databases[database].hasTypes && { types: types }),
...(databases[database].hasEnums && { enums: enums }),
title: title,
transform: transform,
});
}, [

View File

@@ -22,10 +22,12 @@ export default function EnumDetails({ data, i }) {
validateStatus={data.name.trim() === "" ? "error" : "default"}
onChange={(value) => {
updateEnum(i, { name: value });
tables.forEach((table, i) => {
table.fields.forEach((field, j) => {
tables.forEach((table) => {
table.fields.forEach((field) => {
if (field.type.toLowerCase() === data.name.toLowerCase()) {
updateField(i, j, { type: value.toUpperCase() });
updateField(table.id, field.id, {
type: value.toUpperCase(),
});
}
});
});

View File

@@ -21,7 +21,7 @@ import {
import { useDiagram, useUndoRedo } from "../../../hooks";
import i18n from "../../../i18n/i18n";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useMemo, useState } from "react";
const columns = [
{
@@ -36,11 +36,31 @@ const columns = [
export default function RelationshipInfo({ data }) {
const { setUndoStack, setRedoStack } = useUndoRedo();
const { tables, setRelationships, deleteRelationship, updateRelationship } =
useDiagram();
const { tables, deleteRelationship, updateRelationship } = useDiagram();
const { t } = useTranslation();
const [editField, setEditField] = useState({});
const relValues = useMemo(() => {
const { fields: startTableFields, name: startTableName } = tables.find(
(t) => t.id === data.startTableId,
);
const { name: startFieldName } = startTableFields.find(
(f) => f.id === data.startFieldId,
);
const { fields: endTableFields, name: endTableName } = tables.find(
(t) => t.id === data.endTableId,
);
const { name: endFieldName } = endTableFields.find(
(f) => f.id === data.endFieldId,
);
return {
startTableName,
startFieldName,
endTableName,
endFieldName,
};
}, [tables, data]);
const swapKeys = () => {
setUndoStack((prev) => [
...prev,
@@ -67,22 +87,14 @@ export default function RelationshipInfo({ data }) {
},
]);
setRedoStack([]);
setRelationships((prev) =>
prev.map((e, idx) =>
idx === data.id
? {
...e,
name: `fk_${tables[e.endTableId].name}_${
tables[e.endTableId].fields[e.endFieldId].name
}_${tables[e.startTableId].name}`,
startTableId: e.endTableId,
startFieldId: e.endFieldId,
endTableId: e.startTableId,
endFieldId: e.startFieldId,
}
: e,
),
);
updateRelationship(data.id, {
name: `fk_${relValues.endTableName}_${relValues.endFieldName}_${relValues.startTableName}`,
startTableId: data.endTableId,
startFieldId: data.endFieldId,
endTableId: data.startTableId,
endFieldId: data.startFieldId,
});
};
const changeCardinality = (value) => {
@@ -101,11 +113,7 @@ export default function RelationshipInfo({ data }) {
},
]);
setRedoStack([]);
setRelationships((prev) =>
prev.map((e, idx) =>
idx === data.id ? { ...e, cardinality: value } : e,
),
);
updateRelationship(data.id, { cardinality: value });
};
const changeConstraint = (key, value) => {
@@ -125,9 +133,7 @@ export default function RelationshipInfo({ data }) {
},
]);
setRedoStack([]);
setRelationships((prev) =>
prev.map((e, idx) => (idx === data.id ? { ...e, [undoKey]: value } : e)),
);
updateRelationship(data.id, { [undoKey]: value });
};
return (
@@ -165,11 +171,11 @@ export default function RelationshipInfo({ data }) {
<div className="flex justify-between items-center mb-3">
<div className="me-3">
<span className="font-semibold">{t("primary")}: </span>
{tables[data.endTableId].name}
{relValues.endTableName}
</div>
<div className="mx-1">
<span className="font-semibold">{t("foreign")}: </span>
{tables[data.startTableId].name}
{relValues.startTableName}
</div>
<div className="ms-1">
<Popover
@@ -180,13 +186,8 @@ export default function RelationshipInfo({ data }) {
dataSource={[
{
key: "1",
foreign: `${tables[data.startTableId]?.name}(${
tables[data.startTableId].fields[data.startFieldId]
?.name
})`,
primary: `${tables[data.endTableId]?.name}(${
tables[data.endTableId].fields[data.endFieldId]?.name
})`,
foreign: `${relValues.startTableName}(${relValues.startFieldName})`,
primary: `${relValues.endTableName}(${relValues.endFieldName})`,
},
]}
pagination={false}

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import {
Input,
TextArea,
@@ -14,12 +14,13 @@ import { useTranslation } from "react-i18next";
import { dbToTypes } from "../../../data/datatypes";
import { databases } from "../../../data/databases";
export default function FieldDetails({ data, tid, index }) {
export default function FieldDetails({ data, tid }) {
const { t } = useTranslation();
const { tables, database } = useDiagram();
const { setUndoStack, setRedoStack } = useUndoRedo();
const { updateField, deleteField } = useDiagram();
const [editField, setEditField] = useState({});
const table = useMemo(() => tables.find((t) => t.id === tid), [tables, tid]);
return (
<div>
@@ -29,7 +30,7 @@ export default function FieldDetails({ data, tid, index }) {
placeholder={t("default_value")}
value={data.default}
disabled={dbToTypes[database][data.type].noDefault || data.increment}
onChange={(value) => updateField(tid, index, { default: value })}
onChange={(value) => updateField(tid, data.id, { default: value })}
onFocus={(e) => setEditField({ default: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.default) return;
@@ -40,11 +41,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { default: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -66,7 +67,7 @@ export default function FieldDetails({ data, tid, index }) {
addOnBlur
className="my-2"
placeholder={t("use_for_batch_input")}
onChange={(v) => updateField(tid, index, { values: v })}
onChange={(v) => updateField(tid, data.id, { values: v })}
onFocus={() => setEditField({ values: data.values })}
onBlur={() => {
if (
@@ -80,11 +81,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { values: data.values },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -101,7 +102,7 @@ export default function FieldDetails({ data, tid, index }) {
className="my-2 w-full"
placeholder={t("size")}
value={data.size}
onChange={(value) => updateField(tid, index, { size: value })}
onChange={(value) => updateField(tid, data.id, { size: value })}
onFocus={(e) => setEditField({ size: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.size) return;
@@ -112,11 +113,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { size: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -138,7 +139,7 @@ export default function FieldDetails({ data, tid, index }) {
: "error"
}
value={data.size}
onChange={(value) => updateField(tid, index, { size: value })}
onChange={(value) => updateField(tid, data.id, { size: value })}
onFocus={(e) => setEditField({ size: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.size) return;
@@ -149,11 +150,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { size: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -171,7 +172,7 @@ export default function FieldDetails({ data, tid, index }) {
placeholder={t("check")}
value={data.check}
disabled={data.increment}
onChange={(value) => updateField(tid, index, { check: value })}
onChange={(value) => updateField(tid, data.id, { check: value })}
onFocus={(e) => setEditField({ check: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.check) return;
@@ -182,11 +183,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { check: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -210,7 +211,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: {
[checkedValues.target.value]: !checkedValues.target.checked,
},
@@ -220,7 +221,7 @@ export default function FieldDetails({ data, tid, index }) {
},
]);
setRedoStack([]);
updateField(tid, index, {
updateField(tid, data.id, {
[checkedValues.target.value]: checkedValues.target.checked,
});
}}
@@ -242,7 +243,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: {
[checkedValues.target.value]: !checkedValues.target.checked,
},
@@ -250,13 +251,13 @@ export default function FieldDetails({ data, tid, index }) {
[checkedValues.target.value]: checkedValues.target.checked,
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
]);
setRedoStack([]);
updateField(tid, index, {
updateField(tid, data.id, {
increment: !data.increment,
check: data.increment ? data.check : "",
});
@@ -277,7 +278,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: {
[checkedValues.target.value]: !checkedValues.target.checked,
},
@@ -285,13 +286,13 @@ export default function FieldDetails({ data, tid, index }) {
[checkedValues.target.value]: checkedValues.target.checked,
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
]);
setRedoStack([]);
updateField(tid, index, {
updateField(tid, data.id, {
isArray: checkedValues.target.checked,
increment: data.isArray ? data.increment : false,
});
@@ -314,7 +315,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: {
[checkedValues.target.value]:
!checkedValues.target.checked,
@@ -324,13 +325,13 @@ export default function FieldDetails({ data, tid, index }) {
checkedValues.target.checked,
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
]);
setRedoStack([]);
updateField(tid, index, {
updateField(tid, data.id, {
unsigned: checkedValues.target.checked,
});
}}
@@ -344,7 +345,7 @@ export default function FieldDetails({ data, tid, index }) {
value={data.comment}
autosize
rows={2}
onChange={(value) => updateField(tid, index, { comment: value })}
onChange={(value) => updateField(tid, data.id, { comment: value })}
onFocus={(e) => setEditField({ comment: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.comment) return;
@@ -355,11 +356,11 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { comment: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},

View File

@@ -3,13 +3,14 @@ import { Input, Button, Popover, Checkbox, Select } from "@douyinfe/semi-ui";
import { IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
import { useDiagram, useUndoRedo } from "../../../hooks";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useMemo, useState } from "react";
export default function IndexDetails({ data, fields, iid, tid }) {
const { t } = useTranslation();
const { tables, updateTable } = useDiagram();
const { setUndoStack, setRedoStack } = useUndoRedo();
const [editField, setEditField] = useState({});
const table = useMemo(() => tables.find((t) => t.id === tid), [tables, tid]);
return (
<div className="flex justify-between items-center mb-2">
@@ -36,14 +37,14 @@ export default function IndexDetails({ data, fields, iid, tid }) {
fields: [...value],
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[index field]",
}),
},
]);
setRedoStack([]);
updateTable(tid, {
indices: tables[tid].indices.map((index) =>
indices: table.indices.map((index) =>
index.id === iid
? {
...index,
@@ -69,7 +70,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
}
onChange={(value) =>
updateTable(tid, {
indices: tables[tid].indices.map((index) =>
indices: table.indices.map((index) =>
index.id === iid
? {
...index,
@@ -92,7 +93,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
undo: editField,
redo: { name: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[index]",
}),
},
@@ -123,14 +124,14 @@ export default function IndexDetails({ data, fields, iid, tid }) {
checkedValues.target.checked,
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[index field]",
}),
},
]);
setRedoStack([]);
updateTable(tid, {
indices: tables[tid].indices.map((index) =>
indices: table.indices.map((index) =>
index.id === iid
? {
...index,
@@ -157,14 +158,14 @@ export default function IndexDetails({ data, fields, iid, tid }) {
tid: tid,
data: data,
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[delete index]",
}),
},
]);
setRedoStack([]);
updateTable(tid, {
indices: tables[tid].indices
indices: table.indices
.filter((e) => e.id !== iid)
.map((e, j) => ({
...e,

View File

@@ -11,7 +11,7 @@ export default function SearchBar({ tables }) {
const treeData = useMemo(() => {
return tables.map(({ id, name: parentName, fields }, i) => {
const children = fields.map(({ name }, j) => ({
const children = fields?.map(({ name }, j) => ({
tableId: id,
id: `${j}`,
label: name,

View File

@@ -1,11 +1,12 @@
import { useMemo, useState } from "react";
import { Action, ObjectType } from "../../../data/constants";
import { Row, Col, Input, Button, Popover, Select } from "@douyinfe/semi-ui";
import { Input, Button, Popover, Select } from "@douyinfe/semi-ui";
import { IconMore, IconKeyStroked } from "@douyinfe/semi-icons";
import { useEnums, useDiagram, useTypes, useUndoRedo } from "../../../hooks";
import { useState } from "react";
import FieldDetails from "./FieldDetails";
import { useTranslation } from "react-i18next";
import { dbToTypes } from "../../../data/datatypes";
import { DragHandle } from "../../SortableList/DragHandle";
import FieldDetails from "./FieldDetails";
export default function TableField({ data, tid, index }) {
const { updateField } = useDiagram();
@@ -15,16 +16,18 @@ export default function TableField({ data, tid, index }) {
const { t } = useTranslation();
const { setUndoStack, setRedoStack } = useUndoRedo();
const [editField, setEditField] = useState({});
const table = useMemo(() => tables.find((t) => t.id === tid), [tables, tid]);
return (
<Row gutter={6} className="hover-1 my-2">
<Col span={7}>
<div className="hover-1 my-2 flex gap-2 items-center">
<DragHandle id={data.id} />
<div className="min-w-20 flex-1/3">
<Input
id={`scroll_table_${tid}_input_${index}`}
value={data.name}
id={`scroll_table_${tid}_input_${index}`}
validateStatus={data.name.trim() === "" ? "error" : "default"}
placeholder="Name"
onChange={(value) => updateField(tid, index, { name: value })}
onChange={(value) => updateField(tid, data.id, { name: value })}
onFocus={(e) => setEditField({ name: e.target.value })}
onBlur={(e) => {
if (e.target.value === editField.name) return;
@@ -35,11 +38,11 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: editField,
redo: { name: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -47,8 +50,8 @@ export default function TableField({ data, tid, index }) {
setRedoStack([]);
}}
/>
</Col>
<Col span={8}>
</div>
<div className="min-w-24 flex-1/3">
<Select
className="w-full"
optionList={[
@@ -78,11 +81,11 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: { type: data.type },
redo: { type: value },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
@@ -92,7 +95,7 @@ export default function TableField({ data, tid, index }) {
data.increment && !!dbToTypes[database][value].canIncrement;
if (value === "ENUM" || value === "SET") {
updateField(tid, index, {
updateField(tid, data.id, {
type: value,
default: "",
values: data.values ? [...data.values] : [],
@@ -102,13 +105,13 @@ export default function TableField({ data, tid, index }) {
dbToTypes[database][value].isSized ||
dbToTypes[database][value].hasPrecision
) {
updateField(tid, index, {
updateField(tid, data.id, {
type: value,
size: dbToTypes[database][value].defaultSize,
increment: incr,
});
} else if (!dbToTypes[database][value].hasDefault || incr) {
updateField(tid, index, {
updateField(tid, data.id, {
type: value,
increment: incr,
default: "",
@@ -116,13 +119,13 @@ export default function TableField({ data, tid, index }) {
values: [],
});
} else if (dbToTypes[database][value].hasCheck) {
updateField(tid, index, {
updateField(tid, data.id, {
type: value,
check: "",
increment: incr,
});
} else {
updateField(tid, index, {
updateField(tid, data.id, {
type: value,
increment: incr,
size: "",
@@ -131,10 +134,9 @@ export default function TableField({ data, tid, index }) {
}
}}
/>
</Col>
<Col span={3}>
</div>
<div>
<Button
type={data.notNull ? "tertiary" : "primary"}
title={t("nullable")}
theme={data.notNull ? "light" : "solid"}
@@ -146,23 +148,23 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: { notNull: data.notNull },
redo: { notNull: !data.notNull },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
]);
setRedoStack([]);
updateField(tid, index, { notNull: !data.notNull });
updateField(tid, data.id, { notNull: !data.notNull });
}}
>
?
</Button>
</Col>
<Col span={3}>
</div>
<div>
<Button
type={data.primary ? "primary" : "tertiary"}
title={t("primary")}
@@ -175,26 +177,26 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE,
component: "field",
tid: tid,
fid: index,
fid: data.id,
undo: { primary: data.primary },
redo: { primary: !data.primary },
message: t("edit_table", {
tableName: tables[tid].name,
tableName: table.name,
extra: "[field]",
}),
},
]);
setRedoStack([]);
updateField(tid, index, { primary: !data.primary });
updateField(tid, data.id, { primary: !data.primary });
}}
icon={<IconKeyStroked />}
/>
</Col>
<Col span={3}>
</div>
<div>
<Popover
content={
<div className="px-1 w-[240px] popover-theme">
<FieldDetails data={data} index={index} tid={tid} />
<FieldDetails data={data} tid={tid} />
</div>
}
trigger="click"
@@ -203,7 +205,7 @@ export default function TableField({ data, tid, index }) {
>
<Button type="tertiary" icon={<IconMore />} />
</Popover>
</Col>
</Row>
</div>
</div>
);
}

View File

@@ -8,24 +8,21 @@ import {
ColorPicker,
} from "@douyinfe/semi-ui";
import { IconDeleteStroked } from "@douyinfe/semi-icons";
import { useDiagram, useUndoRedo } from "../../../hooks";
import { Action, ObjectType } from "../../../data/constants";
import { useDiagram, useSaveState, useUndoRedo } from "../../../hooks";
import { Action, ObjectType, State } from "../../../data/constants";
import TableField from "./TableField";
import IndexDetails from "./IndexDetails";
import { useTranslation } from "react-i18next";
import { dbToTypes } from "../../../data/datatypes";
import { SortableList } from "../../SortableList/SortableList";
import { nanoid } from "nanoid";
export default function TableInfo({ data }) {
const { t } = useTranslation();
const [indexActiveKey, setIndexActiveKey] = useState("");
const { deleteTable, updateTable, updateField, setRelationships, database } =
useDiagram();
const { deleteTable, updateTable, setTables } = useDiagram();
const { setUndoStack, setRedoStack } = useUndoRedo();
const { setSaveState } = useSaveState();
const [editField, setEditField] = useState({});
const [drag, setDrag] = useState({
draggingElementIndex: null,
draggingOverIndexList: [],
});
return (
<div>
@@ -59,98 +56,21 @@ export default function TableInfo({ data }) {
}}
/>
</div>
{data.fields.map((f, j) => (
<div
key={"field_" + j}
className={`cursor-pointer ${drag.draggingOverIndexList.includes(j) ? "opacity-25" : ""}`}
style={{ direction: "ltr" }}
draggable
onDragStart={() => {
setDrag((prev) => ({ ...prev, draggingElementIndex: j }));
}}
onDragLeave={() => {
setDrag((prev) => ({
...prev,
draggingOverIndexList: prev.draggingOverIndexList.filter(
(index) => index !== j,
),
}));
}}
onDragOver={(e) => {
e.preventDefault();
if (drag.draggingElementIndex != null) {
if (j !== drag.draggingElementIndex) {
setDrag((prev) => {
if (prev.draggingOverIndexList.includes(j)) {
return prev;
}
return {
...prev,
draggingOverIndexList: prev.draggingOverIndexList.concat(j),
};
});
}
return;
}
}}
onDrop={(e) => {
e.preventDefault();
const index = drag.draggingElementIndex;
setDrag({ draggingElementIndex: null, draggingOverIndexList: [] });
if (index == null || index === j) {
return;
}
const a = data.fields[index];
const b = data.fields[j];
updateField(data.id, index, {
...b,
...(!dbToTypes[database][b.type].isSized && { size: "" }),
...(!dbToTypes[database][b.type].hasCheck && { check: "" }),
...(dbToTypes[database][b.type].noDefault && { default: "" }),
id: index,
});
updateField(data.id, j, {
...a,
...(!dbToTypes[database][a.type].isSized && { size: "" }),
...(!dbToTypes[database][a.type].hasCheck && { check: "" }),
...(!dbToTypes[database][a.type].noDefault && { default: "" }),
id: j,
});
setRelationships((prev) =>
prev.map((e) => {
if (e.startTableId === data.id) {
if (e.startFieldId === index) {
return { ...e, startFieldId: j };
}
if (e.startFieldId === j) {
return { ...e, startFieldId: index };
}
}
if (e.endTableId === data.id) {
if (e.endFieldId === index) {
return { ...e, endFieldId: j };
}
if (e.endFieldId === j) {
return { ...e, endFieldId: index };
}
}
return e;
}),
<SortableList
items={data.fields}
keyPrefix={`table-${data.id}`}
onChange={(newFields) => {
setTables((prev) => {
return prev.map((t) =>
t.id === data.id ? { ...t, fields: newFields } : t,
);
}}
onDragEnd={(e) => {
e.preventDefault();
setDrag({ draggingElementIndex: null, draggingOverIndexList: [] });
}}
>
<TableField data={f} tid={data.id} index={j} />
</div>
))}
});
}}
afterChange={() => setSaveState(State.SAVING)}
renderItem={(item, i) => (
<TableField data={item} tid={data.id} index={i} />
)}
/>
{data.indices.length > 0 && (
<Card
bodyStyle={{ padding: "4px" }}
@@ -281,6 +201,7 @@ export default function TableInfo({ data }) {
</Button>
<Button
onClick={() => {
const id = nanoid();
setUndoStack((prev) => [
...prev,
{
@@ -288,6 +209,7 @@ export default function TableInfo({ data }) {
element: ObjectType.TABLE,
component: "field_add",
tid: data.id,
fid: id,
message: t("edit_table", {
tableName: data.name,
extra: "[add field]",
@@ -299,6 +221,7 @@ export default function TableInfo({ data }) {
fields: [
...data.fields,
{
id,
name: "",
type: "",
default: "",
@@ -308,7 +231,6 @@ export default function TableInfo({ data }) {
notNull: false,
increment: false,
comment: "",
id: data.fields.length,
},
],
});

View File

@@ -1,16 +1,19 @@
import { Collapse, Button } from "@douyinfe/semi-ui";
import { IconPlus } from "@douyinfe/semi-icons";
import { useSelect, useDiagram } from "../../../hooks";
import { ObjectType } from "../../../data/constants";
import { useSelect, useDiagram, useSaveState } from "../../../hooks";
import { ObjectType, State } from "../../../data/constants";
import { useTranslation } from "react-i18next";
import { DragHandle } from "../../SortableList/DragHandle";
import { SortableList } from "../../SortableList/SortableList";
import SearchBar from "./SearchBar";
import Empty from "../Empty";
import TableInfo from "./TableInfo";
import { useTranslation } from "react-i18next";
export default function TablesTab() {
const { tables, addTable } = useDiagram();
const { tables, addTable, setTables } = useDiagram();
const { selectedElement, setSelectedElement } = useSelect();
const { t } = useTranslation();
const { setSaveState } = useSaveState();
return (
<>
@@ -37,35 +40,48 @@ export default function TablesTab() {
setSelectedElement((prev) => ({
...prev,
open: true,
id: parseInt(k),
id: k[0],
element: ObjectType.TABLE,
}))
}
accordion
>
{tables.map((t) => (
<div id={`scroll_table_${t.id}`} key={t.id}>
<Collapse.Panel
className="relative"
header={
<>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{t.name}
</div>
<div
className="w-1 h-full absolute top-0 left-0 bottom-0"
style={{ backgroundColor: t.color }}
/>
</>
}
itemKey={`${t.id}`}
>
<TableInfo data={t} />
</Collapse.Panel>
</div>
))}
<SortableList
keyPrefix="tables-tab"
items={tables}
onChange={(newTables) => setTables(newTables)}
afterChange={() => setSaveState(State.SAVING)}
renderItem={(item) => <TableListItem table={item} />}
/>
</Collapse>
)}
</>
);
}
function TableListItem({ table }) {
return (
<div id={`scroll_table_${table.id}`}>
<Collapse.Panel
className="relative"
header={
<>
<div className="flex items-center gap-2">
<DragHandle id={table.id} />
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{table.name}
</div>
</div>
<div
className="w-1 h-full absolute top-0 left-0 bottom-0"
style={{ backgroundColor: table.color }}
/>
</>
}
itemKey={`${table.id}`}
>
<TableInfo data={table} />
</Collapse.Panel>
</div>
);
}

View File

@@ -40,10 +40,12 @@ export default function TypeInfo({ index, data }) {
className="ms-2"
onChange={(value) => {
updateType(index, { name: value });
tables.forEach((table, i) => {
table.fields.forEach((field, j) => {
tables.forEach((table) => {
table.fields.forEach((field) => {
if (field.type.toLowerCase() === data.name.toLowerCase()) {
updateField(i, j, { type: value.toUpperCase() });
updateField(table.id, field.id, {
type: value.toUpperCase(),
});
}
});
});

View File

@@ -121,7 +121,8 @@ function Relationship({ relationship, tables }) {
<path
ref={pathRef}
d={calcPath({
...relationship,
startFieldIndex: relationship.startFieldId,
endFieldIndex: relationship.endFieldId,
startTable: {
x: tables[relationship.startTableId].x,
y: tables[relationship.startTableId].y,

View File

@@ -0,0 +1,14 @@
import { IconHandle } from "@douyinfe/semi-icons";
import { useSortable } from "@dnd-kit/sortable";
export function DragHandle({ id }) {
const { listeners } = useSortable({ id });
return (
<div
className="flex cursor-move items-center justify-center opacity-50 mt-0.5"
{...listeners}
>
<IconHandle />
</div>
);
}

View File

@@ -0,0 +1,18 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
export function SortableItem({ children, id }) {
const { attributes, setNodeRef, transform, transition } = useSortable({
id,
});
const style = {
transform: CSS.Translate.toString(transform),
transition,
};
return (
<div ref={setNodeRef} style={style} {...attributes}>
{children}
</div>
);
}

View File

@@ -0,0 +1,54 @@
import {
closestCenter,
DndContext,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
SortableContext,
arrayMove,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { SortableItem } from "./SortableItem";
export function SortableList({
items,
onChange,
afterChange,
renderItem,
keyPrefix,
}) {
const sensors = useSensors(useSensor(PointerSensor));
const handleDragEnd = (event) => {
const { active, over } = event;
if (active.id !== over.id) {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
const newItems = arrayMove(items, oldIndex, newIndex);
onChange(newItems);
afterChange();
}
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((item, i) => (
<SortableItem
id={item.id}
key={`${keyPrefix}-sortable-item-${item.id}`}
>
{renderItem(item, i)}
</SortableItem>
))}
</SortableContext>
</DndContext>
);
}

View File

@@ -1,5 +1,4 @@
import { tableFieldHeight, tableHeaderHeight } from "../data/constants";
import { calcPath } from "../utils/calcPath";
export default function Thumbnail({ diagram, i, zoom, theme }) {
return (
@@ -58,25 +57,6 @@ export default function Thumbnail({ diagram, i, zoom, theme }) {
</div>
</foreignObject>
))}
{diagram.relationships?.map((r, i) => (
<path
key={i}
d={calcPath({
...r,
startTable: {
x: diagram.tables[r.startTableId].x,
y: diagram.tables[r.startTableId].y - tableFieldHeight / 2,
},
endTable: {
x: diagram.tables[r.endTableId].x,
y: diagram.tables[r.endTableId].y - tableFieldHeight / 2,
},
})}
fill="none"
strokeWidth={2}
stroke="gray"
/>
))}
{diagram.tables?.map((table, i) => {
const height =
table.fields.length * tableFieldHeight + tableHeaderHeight + 7;

View File

@@ -28,13 +28,15 @@ import { get } from "../api/gists";
export const IdContext = createContext({ gistId: "", setGistId: () => {} });
const SIDEPANEL_MIN_WIDTH = 384;
export default function WorkSpace() {
const [id, setId] = useState(0);
const [gistId, setGistId] = useState("");
const [loadedFromGistId, setLoadedFromGistId] = useState("");
const [title, setTitle] = useState("Untitled Diagram");
const [resize, setResize] = useState(false);
const [width, setWidth] = useState(340);
const [width, setWidth] = useState(SIDEPANEL_MIN_WIDTH);
const [lastSaved, setLastSaved] = useState("");
const [showSelectDbModal, setShowSelectDbModal] = useState(false);
const [selectedDb, setSelectedDb] = useState("");
@@ -61,7 +63,7 @@ export default function WorkSpace() {
const handleResize = (e) => {
if (!resize) return;
const w = isRtl(i18n.language) ? window.innerWidth - e.clientX : e.clientX;
if (w > 340) setWidth(w);
if (w > SIDEPANEL_MIN_WIDTH) setWidth(w);
};
const save = useCallback(async () => {

View File

@@ -3,6 +3,7 @@ import { Action, DB, ObjectType, defaultBlue } from "../data/constants";
import { useTransform, useUndoRedo, useSelect } from "../hooks";
import { Toast } from "@douyinfe/semi-ui";
import { useTranslation } from "react-i18next";
import { nanoid } from "nanoid";
export const DiagramContext = createContext(null);
@@ -16,17 +17,18 @@ export default function DiagramContextProvider({ children }) {
const { selectedElement, setSelectedElement } = useSelect();
const addTable = (data, addToHistory = true) => {
const id = nanoid();
if (data) {
setTables((prev) => {
const temp = prev.slice();
temp.splice(data.id, 0, data);
return temp.map((t, i) => ({ ...t, id: i }));
temp.splice(data.index, 0, data);
return temp;
});
} else {
setTables((prev) => [
...prev,
{
id: prev.length,
id,
name: `table_${prev.length}`,
x: transform.pan.x,
y: transform.pan.y,
@@ -41,13 +43,12 @@ export default function DiagramContextProvider({ children }) {
notNull: true,
increment: true,
comment: "",
id: 0,
id: nanoid(),
},
],
comment: "",
indices: [],
color: defaultBlue,
key: Date.now(),
},
]);
}
@@ -55,6 +56,7 @@ export default function DiagramContextProvider({ children }) {
setUndoStack((prev) => [
...prev,
{
id: data ? data.id : id,
action: Action.ADD,
element: ObjectType.TABLE,
message: t("add_table"),
@@ -66,48 +68,39 @@ export default function DiagramContextProvider({ children }) {
const deleteTable = (id, addToHistory = true) => {
if (addToHistory) {
Toast.success(t("table_deleted"));
const rels = relationships.reduce((acc, r) => {
if (r.startTableId === id || r.endTableId === id) {
acc.push(r);
}
return acc;
}, []);
const deletedTable = tables.find((t) => t.id === id);
const deletedTableIndex = tables.findIndex((t) => t.id === id);
setUndoStack((prev) => [
...prev,
{
action: Action.DELETE,
element: ObjectType.TABLE,
data: { table: tables[id], relationship: rels },
message: t("delete_table", { tableName: tables[id].name }),
data: {
table: deletedTable,
relationship: rels,
index: deletedTableIndex,
},
message: t("delete_table", { tableName: deletedTable.name }),
},
]);
setRedoStack([]);
Toast.success(t("table_deleted"));
}
setRelationships((prevR) => {
return prevR
.filter((e) => !(e.startTableId === id || e.endTableId === id))
.map((e, i) => {
const newR = { ...e };
if (e.startTableId > id) {
newR.startTableId = e.startTableId - 1;
}
if (e.endTableId > id) {
newR.endTableId = e.endTableId - 1;
}
return { ...newR, id: i };
});
});
setTables((prev) => {
return prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }));
});
setRelationships((prevR) =>
prevR.filter((e) => !(e.startTableId === id || e.endTableId === id)),
);
setTables((prev) => prev.filter((e) => e.id !== id));
if (id === selectedElement.id) {
setSelectedElement((prev) => ({
...prev,
element: ObjectType.NONE,
id: -1,
id: null,
open: false,
}));
}
@@ -121,12 +114,12 @@ export default function DiagramContextProvider({ children }) {
const updateField = (tid, fid, updatedValues) => {
setTables((prev) =>
prev.map((table, i) => {
if (tid === i) {
prev.map((table) => {
if (tid === table.id) {
return {
...table,
fields: table.fields.map((field, j) =>
fid === j ? { ...field, ...updatedValues } : field,
fields: table.fields.map((field) =>
fid === field.id ? { ...field, ...updatedValues } : field,
),
};
}
@@ -136,6 +129,7 @@ export default function DiagramContextProvider({ children }) {
};
const deleteField = (field, tid, addToHistory = true) => {
const { fields, name } = tables.find((t) => t.id === tid);
if (addToHistory) {
const rels = relationships.reduce((acc, r) => {
if (
@@ -155,50 +149,28 @@ export default function DiagramContextProvider({ children }) {
tid: tid,
data: {
field: field,
index: fields.findIndex((f) => f.id === field.id),
relationship: rels,
},
message: t("edit_table", {
tableName: tables[tid].name,
tableName: name,
extra: "[delete field]",
}),
},
]);
setRedoStack([]);
}
setRelationships((prev) => {
const temp = prev
.filter(
(e) =>
!(
(e.startTableId === tid && e.startFieldId === field.id) ||
(e.endTableId === tid && e.endFieldId === field.id)
),
)
.map((e, i) => {
if (e.startTableId === tid && e.startFieldId > field.id) {
return {
...e,
startFieldId: e.startFieldId - 1,
id: i,
};
}
if (e.endTableId === tid && e.endFieldId > field.id) {
return {
...e,
endFieldId: e.endFieldId - 1,
id: i,
};
}
return { ...e, id: i };
});
return temp;
});
setRelationships((prev) =>
prev.filter(
(e) =>
!(
(e.startTableId === tid && e.startFieldId === field.id) ||
(e.endTableId === tid && e.endFieldId === field.id)
),
),
);
updateTable(tid, {
fields: tables[tid].fields
.filter((e) => e.id !== field.id)
.map((t, i) => {
return { ...t, id: i };
}),
fields: fields.filter((e) => e.id !== field.id),
});
};

View File

@@ -1,17 +1,34 @@
import { tableFieldHeight, tableHeaderHeight } from "../data/constants";
/**
* Generates an SVG path string to visually represent a relationship between two fields.
*
* @param {{
* startTable: { x: number, y: number },
* endTable: { x: number, y: number },
* startFieldIndex: number,
* endFieldIndex: number
* }} r - Relationship data.
* @param {number} tableWidth - Width of each table (used to calculate horizontal offsets).
* @param {number} zoom - Zoom level (used to scale vertical spacing).
* @returns {string} SVG path "d" attribute string.
*/
export function calcPath(r, tableWidth = 200, zoom = 1) {
if (!r) {
return "";
}
const width = tableWidth * zoom;
let x1 = r.startTable.x;
let y1 =
r.startTable.y +
r.startFieldId * tableFieldHeight +
r.startFieldIndex * tableFieldHeight +
tableHeaderHeight +
tableFieldHeight / 2;
let x2 = r.endTable.x;
let y2 =
r.endTable.y +
r.endFieldId * tableFieldHeight +
r.endFieldIndex * tableFieldHeight +
tableHeaderHeight +
tableFieldHeight / 2;

View File

@@ -52,6 +52,22 @@ function cardinality(rel) {
}
export function toDBML(diagram) {
const generateRelString = (rel) => {
const { fields: startTableFields, name: startTableName } =
diagram.tables.find((t) => t.id === rel.startTableId);
const { name: startFieldName } = startTableFields.find(
(f) => f.id === rel.startFieldId,
);
const { fields: endTableFields, name: endTableName } = diagram.tables.find(
(t) => t.id === rel.endTableId,
);
const { name: endFieldName } = endTableFields.find(
(f) => f.id === rel.endFieldId,
);
return `Ref ${rel.name} {\n\t${startTableName}.${startFieldName} ${cardinality(rel)} ${endTableName}.${endFieldName} [ delete: ${rel.deleteConstraint.toLowerCase()}, update: ${rel.updateConstraint.toLowerCase()} ]\n}`;
};
return `${diagram.enums
.map(
(en) =>
@@ -88,15 +104,6 @@ export function toDBML(diagram) {
}\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()}, update: ${rel.updateConstraint.toLowerCase()} ]\n}`,
)
.map((rel) => generateRelString(rel))
.join("\n\n")}`;
}

View File

@@ -62,8 +62,10 @@ export function jsonToDocumentation(obj) {
const documentationRelationships = obj.relationships?.length
? obj.relationships
.map((r) => {
const startTable = obj.tables[r.startTableId].name;
const endTable = obj.tables[r.endTableId].name;
const startTable = obj.tables.find(
(t) => t.id === r.startTableId,
).name;
const endTable = obj.tables.find((t) => t.id === r.endTableId).name;
return `- **${startTable} to ${endTable}**: ${r.cardinality}\n`;
})
.join("")

View File

@@ -41,8 +41,10 @@ export function jsonToMermaid(obj) {
const mermaidRelationships = obj.relationships?.length
? obj.relationships
.map((r) => {
const startTable = obj.tables[r.startTableId].name;
const endTable = obj.tables[r.endTableId].name;
const startTable = obj.tables.find(
(t) => t.id === r.startTableId,
).name;
const endTable = obj.tables.find((t) => t.id === r.endTableId).name;
return `\t${startTable} ${getMermaidRelationship(r.cardinality)} ${endTable} : references`;
})
.join("\n")

View File

@@ -224,16 +224,20 @@ export function jsonToMySQL(obj) {
.join("\n")}`}`,
)
.join("\n")}\n${obj.references
.map(
(r) =>
`ALTER TABLE \`${
obj.tables[r.startTableId].name
}\`\nADD FOREIGN KEY(\`${
obj.tables[r.startTableId].fields[r.startFieldId].name
}\`) REFERENCES \`${obj.tables[r.endTableId].name}\`(\`${
obj.tables[r.endTableId].fields[r.endFieldId].name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = obj.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = obj.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${
startFields.find((f) => f.id === r.startFieldId).name
}\`) REFERENCES \`${endName}\`(\`${
endFields.find((f) => f.id === r.endFieldId).name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}
@@ -327,14 +331,20 @@ export function jsonToPostgreSQL(obj) {
.join("\n")}`,
)
.join("\n")}\n${obj.references
.map(
(r) =>
`ALTER TABLE "${obj.tables[r.startTableId].name}"\nADD FOREIGN KEY("${
obj.tables[r.startTableId].fields[r.startFieldId].name
}") REFERENCES "${obj.tables[r.endTableId].name}"("${
obj.tables[r.endTableId].fields[r.endFieldId].name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = obj.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = obj.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE "${startName}"\nADD FOREIGN KEY("${
startFields.find((f) => f.id === r.startFieldId).name
}") REFERENCES "${endName}"("${
endFields.find((f) => f.id === r.endFieldId).name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}
@@ -459,16 +469,20 @@ export function jsonToMariaDB(obj) {
.join("\n")}`}`,
)
.join("\n")}\n${obj.references
.map(
(r) =>
`ALTER TABLE \`${
obj.tables[r.startTableId].name
}\`\nADD FOREIGN KEY(\`${
obj.tables[r.startTableId].fields[r.startFieldId].name
}\`) REFERENCES \`${obj.tables[r.endTableId].name}\`(\`${
obj.tables[r.endTableId].fields[r.endFieldId].name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = obj.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = obj.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${
startFields.find((f) => f.id === r.startFieldId).name
}\`) REFERENCES \`${endName}\`(\`${
endFields.find((f) => f.id === r.endFieldId).name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}
@@ -527,14 +541,20 @@ export function jsonToSQLServer(obj) {
.join("")}`,
)
.join("\n")}\n${obj.references
.map(
(r) =>
`ALTER TABLE [${obj.tables[r.startTableId].name}]\nADD FOREIGN KEY([${
obj.tables[r.startTableId].fields[r.startFieldId].name
}]) REFERENCES [${obj.tables[r.endTableId].name}]([${
obj.tables[r.endTableId].fields[r.endFieldId].name
}])\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};\nGO`,
)
.map((r) => {
const { name: startName, fields: startFields } = obj.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = obj.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE [${startName}]\nADD FOREIGN KEY([${
startFields.find((f) => f.id === r.startFieldId).name
}]) REFERENCES [${endName}]([${
endFields.find((f) => f.id === r.endFieldId).name
}])\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};\nGO`;
})
.join("\n")}`;
}
@@ -594,13 +614,19 @@ export function jsonToOracleSQL(obj) {
.join("\n")}`,
)
.join("\n\n")}\n${obj.references
.map(
(r) =>
`ALTER TABLE "${obj.tables[r.startTableId].name}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${
obj.tables[r.startTableId].fields[r.startFieldId].name
}") REFERENCES "${obj.tables[r.endTableId].name}"("${
obj.tables[r.endTableId].fields[r.endFieldId].name
}");`,
)
.map((r) => {
const { name: startName, fields: startFields } = obj.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = obj.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE "${startName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${
startFields.find((f) => f.id === r.startFieldId).name
}") REFERENCES "${endName}"("${
endFields.find((f) => f.id === r.endFieldId).name
}");`;
})
.join("\n")}`;
}

View File

@@ -56,15 +56,19 @@ export function toMariaDB(diagram) {
.join("")}`}`,
)
.join("\n")}\n${diagram.references
.map(
(r) =>
`ALTER TABLE \`${
diagram.tables[r.startTableId].name
}\`\nADD FOREIGN KEY(\`${
diagram.tables[r.startTableId].fields[r.startFieldId].name
}\`) REFERENCES \`${diagram.tables[r.endTableId].name}\`(\`${
diagram.tables[r.endTableId].fields[r.endFieldId].name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = diagram.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = diagram.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${
startFields.find((f) => f.id === r.startFieldId).name
}\`) REFERENCES \`${endName}\`(\`${
endFields.find((f) => f.id === r.endFieldId).name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}

View File

@@ -47,13 +47,19 @@ export function toMSSQL(diagram) {
.join("")}`}`,
)
.join("\n")}\n${diagram.references
.map(
(r) =>
`ALTER TABLE [${diagram.tables[r.startTableId].name}]\nADD FOREIGN KEY([${
diagram.tables[r.startTableId].fields[r.startFieldId].name
}]) REFERENCES [${diagram.tables[r.endTableId].name}]([${
diagram.tables[r.endTableId].fields[r.endFieldId].name
}])\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};\nGO`,
)
.map((r) => {
const { name: startName, fields: startFields } = diagram.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = diagram.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE [${startName}]\nADD FOREIGN KEY([${
startFields.find((f) => f.id === r.startFieldId).name
}]) REFERENCES [${endName}]([${
endFields.find((f) => f.id === r.endFieldId).name
}])\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};\nGO`;
})
.join("\n")}`;
}

View File

@@ -58,15 +58,19 @@ export function toMySQL(diagram) {
.join("")}`}`,
)
.join("\n")}\n${diagram.references
.map(
(r) =>
`ALTER TABLE \`${
diagram.tables[r.startTableId].name
}\`\nADD FOREIGN KEY(\`${
diagram.tables[r.startTableId].fields[r.startFieldId].name
}\`) REFERENCES \`${diagram.tables[r.endTableId].name}\`(\`${
diagram.tables[r.endTableId].fields[r.endFieldId].name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = diagram.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = diagram.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${
startFields.find((f) => f.id === r.startFieldId).name
}\`) REFERENCES \`${endName}\`(\`${
endFields.find((f) => f.id === r.endFieldId).name
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}

View File

@@ -2,7 +2,6 @@ import { dbToTypes } from "../../data/datatypes";
import { parseDefault } from "./shared";
export function toOracleSQL(diagram) {
console.log(diagram);
return `${diagram.tables
.map(
(table) =>
@@ -47,13 +46,18 @@ export function toOracleSQL(diagram) {
.join("")}`}`,
)
.join("\n")}\n${diagram.references
.map(
(r) =>
`ALTER TABLE "${diagram.tables[r.startTableId].name}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${
diagram.tables[r.startTableId].fields[r.startFieldId].name
}") REFERENCES "${diagram.tables[r.endTableId].name}" ("${
diagram.tables[r.endTableId].fields[r.endFieldId].name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = diagram.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = diagram.tables.find(
(t) => t.id === r.endTableId,
);
return `ALTER TABLE "${startName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${
startFields.find((f) => f.id === r.startFieldId).name
}") REFERENCES "${endName}" ("${
endFields.find((f) => f.id === r.endFieldId).name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}

View File

@@ -77,13 +77,20 @@ export function toPostgres(diagram) {
.join("\n")}\n`,
)
.join("\n")}${diagram.references
.map(
(r) =>
`\nALTER TABLE "${diagram.tables[r.startTableId].name}"\nADD FOREIGN KEY("${
diagram.tables[r.startTableId].fields[r.startFieldId].name
}") REFERENCES "${diagram.tables[r.endTableId].name}"("${
diagram.tables[r.endTableId].fields[r.endFieldId].name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`,
)
.map((r) => {
const { name: startName, fields: startFields } = diagram.tables.find(
(t) => t.id === r.startTableId,
);
const { name: endName, fields: endFields } = diagram.tables.find(
(t) => t.id === r.endTableId,
);
return `\nALTER TABLE "${startName}"\nADD FOREIGN KEY("${
startFields.find((f) => f.id === r.startFieldId)?.name
}") REFERENCES "${endName}"("${
endFields.find((f) => f.id === r.endFieldId)?.name
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`;
})
.join("\n")}`;
}

View File

@@ -32,10 +32,12 @@ export function getInlineFK(table, obj) {
obj.references.forEach((r) => {
if (r.startTableId === table.id) {
fks.push(
`\tFOREIGN KEY ("${table.fields[r.startFieldId].name}") REFERENCES "${
obj.tables[r.endTableId].name
`\tFOREIGN KEY ("${table.fields.find((f) => f.id === r.startFieldId)?.name}") REFERENCES "${
obj.tables.find((t) => t.id === r.endTableId)?.name
}"("${
obj.tables[r.endTableId].fields[r.endFieldId].name
obj.tables
.find((t) => t.id === r.endTableId)
.fields.find((f) => f.id === r.endFieldId)?.name
}")\n\tON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()}`,
);
}

View File

@@ -54,35 +54,33 @@ export function fromDBML(src) {
}
for (const ref of schema.refs) {
const startTable = ref.endpoints[0].tableName;
const endTable = ref.endpoints[1].tableName;
const startField = ref.endpoints[0].fieldNames[0];
const endField = ref.endpoints[1].fieldNames[0];
const startTableName = ref.endpoints[0].tableName;
const endTableName = ref.endpoints[1].tableName;
const startFieldName = ref.endpoints[0].fieldNames[0];
const endFieldName = ref.endpoints[1].fieldNames[0];
const startTableId = tables.findIndex((t) => t.name === startTable);
if (startTableId === -1) continue;
const startTable = tables.find((t) => t.name === startTableName);
if (!startTable) continue;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) continue;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) continue;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find((f) => f.name === endFieldName);
if (!endField) continue;
const startField = startTable.fields.find(
(f) => f.name === startFieldName,
);
if (endFieldId === -1) continue;
const startFieldId = tables[startTableId].fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) continue;
if (!startField) continue;
const relationship = {};
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.startTableId = startTable.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
relationship.id = relationships.length;
relationship.updateConstraint = ref.onDelete

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared";
@@ -32,10 +33,11 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.create_definitions.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.id = nanoid();
field.name = d.column.column;
let type = d.definition.dataType;
@@ -108,30 +110,29 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
} else if (d.constraint_type.toLowerCase() === "foreign key") {
const relationship = {};
const startTableId = table.id;
const startTable = e.table[0].table;
const startField = d.definition[0].column;
const endTable = d.reference_definition.table[0].table;
const endField = d.reference_definition.definition[0].column;
const startTableName = e.table[0].table;
const startFieldName = d.definition[0].column;
const endTableName = d.reference_definition.table[0].table;
const endFieldName = d.reference_definition.definition[0].column;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
const startField = table.fields.find(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTableId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
let updateConstraint = "No action";
let deleteConstraint = "No action";
d.reference_definition.on_action.forEach((c) => {
@@ -151,7 +152,7 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -168,28 +169,22 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
}
});
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
index.unique = false;
if (e.index_type === "unique") index.unique = true;
index.fields = [];
e.index_columns.forEach((f) => index.fields.push(f.column));
const index = {
name: e.index,
unique: e.index_type === "unique",
fields: e.index_columns.map((f) => f.column),
};
let found = -1;
tables.forEach((t, i) => {
if (found !== -1) return;
if (t.name === e.table.table) {
t.indices.push(index);
found = i;
}
});
const table = tables.find((t) => t.name === e.table.table);
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
if (table) {
table.indices.push(index);
table.indices.forEach((i, j) => {
i.id = j;
});
}
}
} else if (e.type === "alter") {
e.expr.forEach((expr) => {
@@ -199,11 +194,11 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
"foreign key"
) {
const relationship = {};
const startTable = e.table[0].table;
const startField = expr.create_definitions.definition[0].column;
const endTable =
const startTableName = e.table[0].table;
const startFieldName = expr.create_definitions.definition[0].column;
const endTableName =
expr.create_definitions.reference_definition.table[0].table;
const endField =
const endFieldName =
expr.create_definitions.reference_definition.definition[0].column;
let updateConstraint = "No action";
let deleteConstraint = "No action";
@@ -223,32 +218,30 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
},
);
const startTableId = tables.findIndex((t) => t.name === startTable);
if (startTable === -1) return;
const startTable = tables.find((t) => t.name === startTableName);
if (!startTable) return;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find((f) => f.name === endFieldName);
if (!endField) return;
const startField = startTable.fields.find(
(f) => f.name === startFieldName,
);
if (endFieldId === -1) return;
const startFieldId = tables[startTableId].fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
"fk_" + startTableName + "_" + startFieldName + "_" + endTableName;
relationship.startTableId = startTable.id;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (tables[startTableId].fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared";
@@ -44,10 +45,11 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.create_definitions.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.id = nanoid();
field.name = d.column.column;
let type = d.definition.dataType;
@@ -120,30 +122,29 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
} else if (d.constraint_type.toLowerCase() === "foreign key") {
const relationship = {};
const startTableId = table.id;
const startTable = e.table[0].table;
const startField = d.definition[0].column;
const endTable = d.reference_definition.table[0].table;
const endField = d.reference_definition.definition[0].column;
const startTableName = e.table[0].table;
const startFieldName = d.definition[0].column;
const endTableName = d.reference_definition.table[0].table;
const endFieldName = d.reference_definition.definition[0].column;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (!endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
const startField = table.fields.find(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTableId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
let updateConstraint = "No action";
let deleteConstraint = "No action";
d.reference_definition.on_action.forEach((c) => {
@@ -163,7 +164,7 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -173,28 +174,22 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
}
}
});
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
index.unique = false;
if (e.index_type === "unique") index.unique = true;
index.fields = [];
e.index_columns.forEach((f) => index.fields.push(f.column));
const index = {
name: e.index,
unique: e.index_type === "unique",
fields: e.index_columns.map((f) => f.column),
};
let found = -1;
tables.forEach((t, i) => {
if (found !== -1) return;
if (t.name === e.table.table) {
t.indices.push(index);
found = i;
}
});
const table = tables.find((t) => t.name === e.table.table);
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
if (table) {
table.indices.push(index);
table.indices.forEach((i, j) => {
i.id = j;
});
}
}
} else if (e.type === "alter") {
e.expr.forEach((expr) => {
@@ -204,11 +199,11 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
"foreign key"
) {
const relationship = {};
const startTable = e.table[0].table;
const startField = expr.create_definitions.definition[0].column;
const endTable =
const startTableName = e.table[0].table;
const startFieldName = expr.create_definitions.definition[0].column;
const endTableName =
expr.create_definitions.reference_definition.table[0].table;
const endField =
const endFieldName =
expr.create_definitions.reference_definition.definition[0].column;
let updateConstraint = "No action";
let deleteConstraint = "No action";
@@ -228,32 +223,29 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
},
);
const startTableId = tables.findIndex((t) => t.name === startTable);
if (startTable === -1) return;
const startTable = tables.find((t) => t.name === startTableName);
if (!startTable) return;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find((f) => f.name === endFieldName);
if (!endField) return;
const startField = startTable.fields.find(
(f) => f.name === startFieldName,
);
if (endFieldId === -1) return;
if (!startField) return;
const startFieldId = tables[startTableId].fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.name = `fk_${startTable}_${startField}_${endTable}`;
relationship.startTableId = startTable.id;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (tables[startTableId].fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared";
@@ -32,10 +33,11 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.create_definitions.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.id = nanoid();
field.name = d.column.column;
let type = d.definition.dataType;
@@ -107,31 +109,29 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
});
} else if (d.constraint_type.toLowerCase() === "foreign key") {
const relationship = {};
const startTableId = table.id;
const startTable = e.table[0].table;
const startField = d.definition[0].column;
const endTable = d.reference_definition.table[0].table;
const endField = d.reference_definition.definition[0].column;
const startTableName = e.table[0].table;
const startFieldName = d.definition[0].column;
const endTableName = d.reference_definition.table[0].table;
const endFieldName = d.reference_definition.definition[0].column;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (!endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
const startField = table.fields.find(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = table.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
let updateConstraint = "No action";
let deleteConstraint = "No action";
d.reference_definition.on_action.forEach((c) => {
@@ -151,7 +151,7 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -164,7 +164,7 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
e.table_options.forEach((opt) => {
if (opt.keyword === "comment") {
table.comment = opt.value.replace(/^["']|["']$/g, '');
table.comment = opt.value.replace(/^["']|["']$/g, "");
}
});
@@ -173,23 +173,20 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
});
tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
index.unique = false;
if (e.index_type === "unique") index.unique = true;
index.fields = [];
e.index_columns.forEach((f) => index.fields.push(f.column));
const index = {
name: e.index,
unique: e.index_type === "unique",
fields: e.index_columns.map((f) => f.column),
};
let found = -1;
tables.forEach((t, i) => {
if (found !== -1) return;
if (t.name === e.table.table) {
t.indices.push(index);
found = i;
}
});
const table = tables.find((t) => t.name === e.table.table);
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
if (table) {
table.indices.push(index);
table.indices.forEach((i, j) => {
i.id = j;
});
}
}
} else if (e.type === "alter") {
e.expr.forEach((expr) => {
@@ -199,11 +196,11 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
"foreign key"
) {
const relationship = {};
const startTable = e.table[0].table;
const startField = expr.create_definitions.definition[0].column;
const endTable =
const startTableName = e.table[0].table;
const startFieldName = expr.create_definitions.definition[0].column;
const endTableName =
expr.create_definitions.reference_definition.table[0].table;
const endField =
const endFieldName =
expr.create_definitions.reference_definition.definition[0].column;
let updateConstraint = "No action";
let deleteConstraint = "No action";
@@ -223,32 +220,29 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
},
);
const startTableId = tables.findIndex((t) => t.name === startTable);
if (startTable === -1) return;
const startTable = tables.find((t) => t.name === startTableName);
if (!startTable) return;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find((f) => f.name === endFieldName);
if (!endField) return;
const startField = startTable.fields.find(
(f) => f.name === startFieldName,
);
if (endFieldId === -1) return;
if (!startField) return;
const startFieldId = tables[startTableId].fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTable.id;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (tables[startTableId].fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, Constraint, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
@@ -33,10 +34,11 @@ export function fromOracleSQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.table.relational_properties.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.id = nanoid();
field.name = d.name;
let type = d.type.type.toUpperCase();
@@ -78,33 +80,32 @@ export function fromOracleSQL(ast, diagramDb = DB.GENERIC) {
table.fields.push(field);
} else if (d.resource === "constraint") {
const relationship = {};
const startTableId = table.id;
const startField = d.constraint.columns[0];
const endField = d.constraint.reference.columns[0];
const endTable = d.constraint.reference.object.name;
const startFieldName = d.constraint.columns[0];
const endFieldName = d.constraint.reference.columns[0];
const endTableName = d.constraint.reference.object.name;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (!endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
const startField = table.fields.find(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startTableId = table.id;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = Constraint.NONE;
relationship.name =
d.name && Boolean(d.name.trim())
? d.name
: "fk_" + table.name + "_" + startField + "_" + endTable;
: `fk_${table.name}_${startFieldName}_${endTableName}`;
relationship.deleteConstraint =
d.constraint.reference.on_delete &&
Boolean(d.constraint.reference.on_delete.trim())
@@ -112,7 +113,7 @@ export function fromOracleSQL(ast, diagramDb = DB.GENERIC) {
d.constraint.reference.on_delete.substring(1)
: Constraint.NONE;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared";
@@ -32,10 +33,11 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.create_definitions.forEach((d) => {
const field = {};
if (d.resource === "column") {
field.id = nanoid();
field.name = d.column.column.expr.value;
let type = types.find((t) =>
@@ -118,31 +120,28 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
} else if (d.constraint_type.toLowerCase() === "foreign key") {
const relationship = {};
const startTableId = table.id;
const startTable = e.table[0].table;
const startField = d.definition[0].column.expr.value;
const endTable = d.reference_definition.table[0].table;
const endField =
const startTableName = e.table[0].table;
const startFieldName = d.definition[0].column.expr.value;
const endTableName = d.reference_definition.table[0].table;
const endFieldName =
d.reference_definition.definition[0].column.expr.value;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (!endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) return;
const startField = table.find((f) => f.name === startFieldName);
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTableId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
let updateConstraint = "No action";
let deleteConstraint = "No action";
d.reference_definition.on_action.forEach((c) => {
@@ -161,7 +160,7 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -172,10 +171,10 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
if (d.reference_definition) {
const relationship = {};
const startTable = table.name;
const startField = field.name;
const endTable = d.reference_definition.table[0].table;
const endField =
const startTableName = table.name;
const startFieldName = field.name;
const endTableName = d.reference_definition.table[0].table;
const endFieldName =
d.reference_definition.definition[0].column.expr.value;
let updateConstraint = "No action";
let deleteConstraint = "No action";
@@ -195,29 +194,28 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
const startTableId = tables.length;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.findIndex(
(f) => f.name === endFieldName,
);
if (endFieldId === -1) return;
if (!endField) return;
const startFieldId = table.fields.findIndex(
(f) => f.name === startField,
const startField = table.fields.find(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
if (!startField) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (table.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -233,23 +231,20 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
});
tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
index.unique = false;
if (e.index_type === "unique") index.unique = true;
index.fields = [];
e.index_columns.forEach((f) => index.fields.push(f.column.expr.value));
const index = {
name: e.index,
unique: e.index_type === "unique",
fields: e.index_columns.map((f) => f.column),
};
let found = -1;
tables.forEach((t, i) => {
if (found !== -1) return;
if (t.name === e.table.table) {
t.indices.push(index);
found = i;
}
});
const table = tables.find((t) => t.name === e.table.table);
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
if (table) {
table.indices.push(index);
table.indices.forEach((i, j) => {
i.id = j;
});
}
} else if (e.keyword === "type") {
if (e.resource === "enum") {
const newEnum = {
@@ -294,12 +289,12 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
"foreign key"
) {
const relationship = {};
const startTable = e.table[0].table;
const startField =
const startTableName = e.table[0].table;
const startFieldName =
expr.create_definitions.definition[0].column.expr.value;
const endTable =
const endTableName =
expr.create_definitions.reference_definition.table[0].table;
const endField =
const endFieldName =
expr.create_definitions.reference_definition.definition[0].column
.expr.value;
let updateConstraint = "No action";
@@ -320,33 +315,30 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
},
);
const startTableId = tables.findIndex((t) => t.name === startTable);
if (startTable === -1) return;
const startTable = tables.find((t) => t.name === startTableName);
if (!startTable) return;
const endTableId = tables.findIndex((t) => t.name === endTable);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
const endField = endTable.fields.find((f) => f.name === endFieldName);
if (!endField) return;
const startField = startTable.fields.find(
(f) => f.name === startFieldName,
);
if (endFieldId === -1) return;
if (!startField) return;
const startFieldId = tables[startTableId].fields.findIndex(
(f) => f.name === startField,
);
if (startFieldId === -1) return;
relationship.name =
"fk_" + startTable + "_" + startField + "_" + endTable;
relationship.startTableId = startTableId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
relationship.startTableId = startTable.id;
relationship.startFieldId = startField.id;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
relationship.cardinality = Cardinality.ONE_TO_ONE;
if (tables[startTableId].fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared";
@@ -47,27 +48,23 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
) => {
const relationship = {};
const endTableName = referenceDefinition.table[0].table;
const endField = referenceDefinition.definition[0].column;
const endFieldName = referenceDefinition.definition[0].column;
const endTableId = tables.findIndex((t) => t.name === endTableName);
if (endTableId === -1) return;
const endTable = tables.find((t) => t.name === endTableName);
if (!endTable) return;
const endFieldId = tables[endTableId].fields.findIndex(
(f) => f.name === endField,
);
if (endFieldId === -1) return;
const endField = endTable.fields.findIndex((f) => f.name === endFieldName);
if (!endField) return;
const startFieldId = startTable.fields.findIndex(
(f) => f.name === startFieldName,
);
if (startFieldId === -1) return;
const startField = startTable.fields.find((f) => f.name === startFieldName);
if (!startField) return;
relationship.name =
"fk_" + startTable.name + "_" + startFieldName + "_" + endTableName;
relationship.startTableId = startTable.id;
relationship.endTableId = endTableId;
relationship.endFieldId = endFieldId;
relationship.startFieldId = startFieldId;
relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id;
relationship.startFieldId = startField.id;
let updateConstraint = "No action";
let deleteConstraint = "No action";
referenceDefinition.on_action.forEach((c) => {
@@ -85,7 +82,7 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
relationship.updateConstraint = updateConstraint;
relationship.deleteConstraint = deleteConstraint;
if (startTable.fields[startFieldId].unique) {
if (startField.unique) {
relationship.cardinality = Cardinality.ONE_TO_ONE;
} else {
relationship.cardinality = Cardinality.MANY_TO_ONE;
@@ -102,10 +99,11 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
table.id = tables.length;
table.id = nanoid();
e.create_definitions.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.id = nanoid();
field.name = d.column.column;
let type = d.definition.dataType;
@@ -191,28 +189,22 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
}
}
});
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
index.unique = false;
if (e.index_type === "unique") index.unique = true;
index.fields = [];
e.index_columns.forEach((f) => index.fields.push(f.column));
const index = {
name: e.index,
unique: e.index_type === "unique",
fields: e.index_columns.map((f) => f.column),
};
let found = -1;
tables.forEach((t, i) => {
if (found !== -1) return;
if (t.name === e.table.table) {
t.indices.push(index);
found = i;
}
});
const table = tables.find((t) => t.name === e.table.table);
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
if (table) {
table.indices.push(index);
table.indices.forEach((i, j) => {
i.id = j;
});
}
}
}
};

View File

@@ -213,7 +213,7 @@ export function getIssues(diagram) {
if (visited.includes(tableId)) {
issues.push(
i18n.t("circular_dependency", {
refName: diagram.tables[tableId].name,
refName: diagram.tables.find((t) => t.id === tableId)?.name,
}),
);
return;