Use nanoid instead of numberic ids for fields and tables

This commit is contained in:
1ilit 2025-05-11 20:48:03 +04:00
parent 9c40ae31f0
commit a1c60af3cd
20 changed files with 171 additions and 241 deletions

31
package-lock.json generated
View File

@ -30,6 +30,7 @@
"jspdf": "^3.0.1", "jspdf": "^3.0.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lexical": "^0.12.5", "lexical": "^0.12.5",
"nanoid": "^5.1.5",
"node-sql-parser": "^5.3.8", "node-sql-parser": "^5.3.8",
"oracle-sql-parser": "^0.1.0", "oracle-sql-parser": "^0.1.0",
"react": "^18.2.0", "react": "^18.2.0",
@ -6235,10 +6236,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "5.1.5",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -6247,10 +6247,10 @@
], ],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.js"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^18 || >=20"
} }
}, },
"node_modules/natural-compare": { "node_modules/natural-compare": {
@ -6592,6 +6592,25 @@
"node": "^10 || ^12 || >=14" "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -32,6 +32,7 @@
"jspdf": "^3.0.1", "jspdf": "^3.0.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lexical": "^0.12.5", "lexical": "^0.12.5",
"nanoid": "^5.1.5",
"node-sql-parser": "^5.3.8", "node-sql-parser": "^5.3.8",
"oracle-sql-parser": "^0.1.0", "oracle-sql-parser": "^0.1.0",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -55,7 +55,7 @@ export default function Canvas() {
} = useSelect(); } = useSelect();
const [dragging, setDragging] = useState({ const [dragging, setDragging] = useState({
element: ObjectType.NONE, element: ObjectType.NONE,
id: -1, id: null,
prevX: 0, prevX: 0,
prevY: 0, prevY: 0,
initialPositions: [], initialPositions: [],
@ -73,8 +73,8 @@ export default function Canvas() {
}); });
const [grabOffset, setGrabOffset] = useState({ x: 0, y: 0 }); const [grabOffset, setGrabOffset] = useState({ x: 0, y: 0 });
const [hoveredTable, setHoveredTable] = useState({ const [hoveredTable, setHoveredTable] = useState({
tableId: -1, tableId: null,
field: -2, fieldId: null,
}); });
const [panning, setPanning] = useState({ const [panning, setPanning] = useState({
isPanning: false, isPanning: false,
@ -264,7 +264,7 @@ export default function Canvas() {
}); });
} else if ( } else if (
dragging.element !== ObjectType.NONE && dragging.element !== ObjectType.NONE &&
dragging.id >= 0 && dragging.id !== null &&
bulkSelectedElements.length bulkSelectedElements.length
) { ) {
const currentX = pointer.spaces.diagram.x + grabOffset.x; const currentX = pointer.spaces.diagram.x + grabOffset.x;
@ -318,21 +318,21 @@ export default function Canvas() {
(panning.cursorStart.y - pointer.spaces.screen.y) / transform.zoom, (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, { updateTable(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y, y: pointer.spaces.diagram.y + grabOffset.y,
}); });
} else if ( } else if (
dragging.element === ObjectType.AREA && dragging.element === ObjectType.AREA &&
dragging.id >= 0 && dragging.id !== null &&
areaResize.id === -1 areaResize.id === -1
) { ) {
updateArea(dragging.id, { updateArea(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y, 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, { updateNote(dragging.id, {
x: pointer.spaces.diagram.x + grabOffset.x, x: pointer.spaces.diagram.x + grabOffset.x,
y: pointer.spaces.diagram.y + grabOffset.y, y: pointer.spaces.diagram.y + grabOffset.y,
@ -598,8 +598,8 @@ export default function Canvas() {
}; };
const handleLinking = () => { const handleLinking = () => {
if (hoveredTable.tableId < 0) return; if (hoveredTable.tableId === null) return;
if (hoveredTable.field < 0) return; if (hoveredTable.fieldId === null) return;
const { fields: startTableFields, name: startTableName } = tables.find( const { fields: startTableFields, name: startTableName } = tables.find(
(t) => t.id === linkingLine.startTableId, (t) => t.id === linkingLine.startTableId,
@ -611,7 +611,7 @@ export default function Canvas() {
(t) => t.id === hoveredTable.tableId, (t) => t.id === hoveredTable.tableId,
); );
const { type: endType } = endTableFields.find( const { type: endType } = endTableFields.find(
(f) => f.id === hoveredTable.field, (f) => f.id === hoveredTable.fieldId,
); );
if (!areFieldsCompatible(database, startType, endType)) { if (!areFieldsCompatible(database, startType, endType)) {
@ -620,14 +620,14 @@ export default function Canvas() {
} }
if ( if (
linkingLine.startTableId === hoveredTable.tableId && linkingLine.startTableId === hoveredTable.tableId &&
linkingLine.startFieldId === hoveredTable.field linkingLine.startFieldId === hoveredTable.fieldId
) )
return; return;
const newRelationship = { const newRelationship = {
...linkingLine, ...linkingLine,
endTableId: hoveredTable.tableId, endTableId: hoveredTable.tableId,
endFieldId: hoveredTable.field, endFieldId: hoveredTable.fieldId,
cardinality: Cardinality.ONE_TO_ONE, cardinality: Cardinality.ONE_TO_ONE,
updateConstraint: Constraint.NONE, updateConstraint: Constraint.NONE,
deleteConstraint: Constraint.NONE, deleteConstraint: Constraint.NONE,

View File

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

View File

@ -78,6 +78,7 @@ import { IdContext } from "../Workspace";
import { socials } from "../../data/socials"; import { socials } from "../../data/socials";
import { toDBML } from "../../utils/exportAs/dbml"; import { toDBML } from "../../utils/exportAs/dbml";
import { exportSavedData } from "../../utils/exportSavedData"; import { exportSavedData } from "../../utils/exportSavedData";
import { nanoid } from "nanoid";
export default function ControlPanel({ export default function ControlPanel({
diagramId, diagramId,
@ -150,7 +151,7 @@ export default function ControlPanel({
if (a.action === Action.ADD) { if (a.action === Action.ADD) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
deleteTable(tables[tables.length - 1].id, false); deleteTable(a.id, false);
} else if (a.element === ObjectType.AREA) { } else if (a.element === ObjectType.AREA) {
deleteArea(areas[areas.length - 1].id, false); deleteArea(areas[areas.length - 1].id, false);
} else if (a.element === ObjectType.NOTE) { } else if (a.element === ObjectType.NOTE) {
@ -165,10 +166,8 @@ export default function ControlPanel({
setRedoStack((prev) => [...prev, a]); setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) { } else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
setRedoStack((prev) => [ const { x, y } = tables.find((t) => t.id === a.id);
...prev, setRedoStack((prev) => [...prev, { ...a, x, y }]);
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
updateTable(a.id, { x: a.x, y: a.y }); updateTable(a.id, { x: a.x, y: a.y });
} else if (a.element === ObjectType.AREA) { } else if (a.element === ObjectType.AREA) {
setRedoStack((prev) => [ setRedoStack((prev) => [
@ -205,6 +204,7 @@ export default function ControlPanel({
} else if (a.element === ObjectType.NOTE) { } else if (a.element === ObjectType.NOTE) {
updateNote(a.nid, a.undo); updateNote(a.nid, a.undo);
} else if (a.element === ObjectType.TABLE) { } else if (a.element === ObjectType.TABLE) {
const table = tables.find((t) => t.id === a.tid);
if (a.component === "field") { if (a.component === "field") {
updateField(a.tid, a.fid, a.undo); updateField(a.tid, a.fid, a.undo);
} else if (a.component === "field_delete") { } else if (a.component === "field_delete") {
@ -213,65 +213,24 @@ export default function ControlPanel({
a.data.relationship.forEach((r) => { a.data.relationship.forEach((r) => {
temp.splice(r.id, 0, 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; return temp;
}); });
setTables((prev) => const updatedFields = table.fields.slice();
prev.map((t) => { updatedFields.splice(a.data.index, 0, a.data.field);
if (t.id === a.tid) { updateTable(a.tid, { fields: updatedFields });
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;
}),
);
} else if (a.component === "field_add") { } else if (a.component === "field_add") {
updateTable(a.tid, { updateTable(a.tid, {
fields: tables[a.tid].fields fields: table.fields.filter((e) => e.id !== a.fid),
.filter((e) => e.id !== tables[a.tid].fields.length - 1)
.map((t, i) => ({ ...t, id: i })),
}); });
} else if (a.component === "index_add") { } else if (a.component === "index_add") {
updateTable(a.tid, { updateTable(a.tid, {
indices: tables[a.tid].indices indices: table.indices
.filter((e) => e.id !== tables[a.tid].indices.length - 1) .filter((e) => e.id !== table.indices.length - 1)
.map((t, i) => ({ ...t, id: i })), .map((t, i) => ({ ...t, id: i })),
}); });
} else if (a.component === "index") { } else if (a.component === "index") {
updateTable(a.tid, { updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) => indices: table.indices.map((index) =>
index.id === a.iid index.id === a.iid
? { ? {
...index, ...index,
@ -281,19 +240,11 @@ export default function ControlPanel({
), ),
}); });
} else if (a.component === "index_delete") { } else if (a.component === "index_delete") {
setTables((prev) => const updatedIndices = table.indices.slice();
prev.map((table) => { updatedIndices.splice(a.data.id, 0, a.data);
if (table.id === a.tid) { updateTable(a.tid, {
const temp = table.indices.slice(); indices: updatedIndices.map((t, i) => ({ ...t, id: i })),
temp.splice(a.data.id, 0, a.data); });
return {
...table,
indices: temp.map((t, i) => ({ ...t, id: i })),
};
}
return table;
}),
);
} else if (a.component === "self") { } else if (a.component === "self") {
updateTable(a.tid, a.undo); updateTable(a.tid, a.undo);
} }
@ -390,10 +341,8 @@ export default function ControlPanel({
setUndoStack((prev) => [...prev, a]); setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.MOVE) { } else if (a.action === Action.MOVE) {
if (a.element === ObjectType.TABLE) { if (a.element === ObjectType.TABLE) {
setUndoStack((prev) => [ const { x, y } = tables.find((t) => t.id == a.id);
...prev, setUndoStack((prev) => [...prev, { ...a, x, y }]);
{ ...a, x: tables[a.id].x, y: tables[a.id].y },
]);
updateTable(a.id, { x: a.x, y: a.y }); updateTable(a.id, { x: a.x, y: a.y });
} else if (a.element === ObjectType.AREA) { } else if (a.element === ObjectType.AREA) {
setUndoStack((prev) => [ setUndoStack((prev) => [
@ -429,6 +378,7 @@ export default function ControlPanel({
} else if (a.element === ObjectType.NOTE) { } else if (a.element === ObjectType.NOTE) {
updateNote(a.nid, a.redo); updateNote(a.nid, a.redo);
} else if (a.element === ObjectType.TABLE) { } else if (a.element === ObjectType.TABLE) {
const table = tables.find((t) => t.id === a.tid);
if (a.component === "field") { if (a.component === "field") {
updateField(a.tid, a.fid, a.redo); updateField(a.tid, a.fid, a.redo);
} else if (a.component === "field_delete") { } else if (a.component === "field_delete") {
@ -436,7 +386,7 @@ export default function ControlPanel({
} else if (a.component === "field_add") { } else if (a.component === "field_add") {
updateTable(a.tid, { updateTable(a.tid, {
fields: [ fields: [
...tables[a.tid].fields, ...table.fields,
{ {
name: "", name: "",
type: "", type: "",
@ -447,16 +397,12 @@ export default function ControlPanel({
notNull: false, notNull: false,
increment: false, increment: false,
comment: "", comment: "",
id: tables[a.tid].fields.length, id: nanoid(),
}, },
], ],
}); });
} else if (a.component === "index_add") { } else if (a.component === "index_add") {
setTables((prev) => updateTable(a.tid, {
prev.map((table) => {
if (table.id === a.tid) {
return {
...table,
indices: [ indices: [
...table.indices, ...table.indices,
{ {
@ -465,14 +411,10 @@ export default function ControlPanel({
fields: [], fields: [],
}, },
], ],
}; });
}
return table;
}),
);
} else if (a.component === "index") { } else if (a.component === "index") {
updateTable(a.tid, { updateTable(a.tid, {
indices: tables[a.tid].indices.map((index) => indices: table.indices.map((index) =>
index.id === a.iid index.id === a.iid
? { ? {
...index, ...index,
@ -483,7 +425,7 @@ export default function ControlPanel({
}); });
} else if (a.component === "index_delete") { } else if (a.component === "index_delete") {
updateTable(a.tid, { updateTable(a.tid, {
indices: tables[a.tid].indices indices: table.indices
.filter((e) => e.id !== a.data.id) .filter((e) => e.id !== a.data.id)
.map((t, i) => ({ ...t, id: i })), .map((t, i) => ({ ...t, id: i })),
}); });

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
import { dbToTypes } from "../../../data/datatypes"; import { dbToTypes } from "../../../data/datatypes";
import { databases } from "../../../data/databases"; import { databases } from "../../../data/databases";
export default function FieldDetails({ data, tid, index }) { export default function FieldDetails({ data, tid }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { tables, database } = useDiagram(); const { tables, database } = useDiagram();
const { setUndoStack, setRedoStack } = useUndoRedo(); const { setUndoStack, setRedoStack } = useUndoRedo();
@ -41,7 +41,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { default: e.target.value }, redo: { default: e.target.value },
message: t("edit_table", { message: t("edit_table", {
@ -81,7 +81,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { values: data.values }, redo: { values: data.values },
message: t("edit_table", { message: t("edit_table", {
@ -113,7 +113,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { size: e.target.value }, redo: { size: e.target.value },
message: t("edit_table", { message: t("edit_table", {
@ -150,7 +150,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { size: e.target.value }, redo: { size: e.target.value },
message: t("edit_table", { message: t("edit_table", {
@ -183,7 +183,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { check: e.target.value }, redo: { check: e.target.value },
message: t("edit_table", { message: t("edit_table", {
@ -211,7 +211,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { undo: {
[checkedValues.target.value]: !checkedValues.target.checked, [checkedValues.target.value]: !checkedValues.target.checked,
}, },
@ -243,7 +243,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { undo: {
[checkedValues.target.value]: !checkedValues.target.checked, [checkedValues.target.value]: !checkedValues.target.checked,
}, },
@ -278,7 +278,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { undo: {
[checkedValues.target.value]: !checkedValues.target.checked, [checkedValues.target.value]: !checkedValues.target.checked,
}, },
@ -315,7 +315,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { undo: {
[checkedValues.target.value]: [checkedValues.target.value]:
!checkedValues.target.checked, !checkedValues.target.checked,
@ -356,7 +356,7 @@ export default function FieldDetails({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { comment: e.target.value }, redo: { comment: e.target.value },
message: t("edit_table", { message: t("edit_table", {

View File

@ -38,7 +38,7 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: editField, undo: editField,
redo: { name: e.target.value }, redo: { name: e.target.value },
message: t("edit_table", { message: t("edit_table", {
@ -81,7 +81,7 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { type: data.type }, undo: { type: data.type },
redo: { type: value }, redo: { type: value },
message: t("edit_table", { message: t("edit_table", {
@ -148,7 +148,7 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { notNull: data.notNull }, undo: { notNull: data.notNull },
redo: { notNull: !data.notNull }, redo: { notNull: !data.notNull },
message: t("edit_table", { message: t("edit_table", {
@ -177,7 +177,7 @@ export default function TableField({ data, tid, index }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field", component: "field",
tid: tid, tid: tid,
fid: index, fid: data.id,
undo: { primary: data.primary }, undo: { primary: data.primary },
redo: { primary: !data.primary }, redo: { primary: !data.primary },
message: t("edit_table", { message: t("edit_table", {
@ -196,7 +196,7 @@ export default function TableField({ data, tid, index }) {
<Popover <Popover
content={ content={
<div className="px-1 w-[240px] popover-theme"> <div className="px-1 w-[240px] popover-theme">
<FieldDetails data={data} index={index} tid={tid} /> <FieldDetails data={data} tid={tid} />
</div> </div>
} }
trigger="click" trigger="click"

View File

@ -14,6 +14,7 @@ import TableField from "./TableField";
import IndexDetails from "./IndexDetails"; import IndexDetails from "./IndexDetails";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { SortableList } from "../../SortableList/SortableList"; import { SortableList } from "../../SortableList/SortableList";
import { nanoid } from "nanoid";
export default function TableInfo({ data }) { export default function TableInfo({ data }) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -200,6 +201,7 @@ export default function TableInfo({ data }) {
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
const id = nanoid();
setUndoStack((prev) => [ setUndoStack((prev) => [
...prev, ...prev,
{ {
@ -207,6 +209,7 @@ export default function TableInfo({ data }) {
element: ObjectType.TABLE, element: ObjectType.TABLE,
component: "field_add", component: "field_add",
tid: data.id, tid: data.id,
fid: id,
message: t("edit_table", { message: t("edit_table", {
tableName: data.name, tableName: data.name,
extra: "[add field]", extra: "[add field]",
@ -218,6 +221,7 @@ export default function TableInfo({ data }) {
fields: [ fields: [
...data.fields, ...data.fields,
{ {
id,
name: "", name: "",
type: "", type: "",
default: "", default: "",
@ -227,7 +231,6 @@ export default function TableInfo({ data }) {
notNull: false, notNull: false,
increment: false, increment: false,
comment: "", comment: "",
id: data.fields.length,
}, },
], ],
}); });

View File

@ -40,7 +40,7 @@ export default function TablesTab() {
setSelectedElement((prev) => ({ setSelectedElement((prev) => ({
...prev, ...prev,
open: true, open: true,
id: parseInt(k), id: k[0],
element: ObjectType.TABLE, element: ObjectType.TABLE,
})) }))
} }

View File

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

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants"; import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared"; import { buildSQLFromAST } from "./shared";
@ -32,10 +33,11 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.create_definitions.forEach((d) => { e.create_definitions.forEach((d) => {
if (d.resource === "column") { if (d.resource === "column") {
const field = {}; const field = {};
field.id = nanoid();
field.name = d.column.column; field.name = d.column.column;
let type = d.definition.dataType; let type = d.definition.dataType;
@ -126,13 +128,7 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
); );
if (!startField) return; if (!startField) return;
relationship.name = relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
"fk_" +
startTableName +
"_" +
startFieldName +
"_" +
endTableName;
relationship.startTableId = startTableId; relationship.startTableId = startTableId;
relationship.endTableId = endTable.id; relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id; relationship.endFieldId = endField.id;
@ -173,9 +169,6 @@ export function fromMariaDB(ast, diagramDb = DB.GENERIC) {
} }
}); });
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table); tables.push(table);
} else if (e.keyword === "index") { } else if (e.keyword === "index") {
const index = { const index = {

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants"; import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared"; import { buildSQLFromAST } from "./shared";
@ -44,10 +45,11 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.create_definitions.forEach((d) => { e.create_definitions.forEach((d) => {
if (d.resource === "column") { if (d.resource === "column") {
const field = {}; const field = {};
field.id = nanoid();
field.name = d.column.column; field.name = d.column.column;
let type = d.definition.dataType; let type = d.definition.dataType;
@ -138,13 +140,7 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
); );
if (!startField) return; if (!startField) return;
relationship.name = relationship.name = `fk_${startTableName}_${startFieldName}_${endTableName}`;
"fk_" +
startTableName +
"_" +
startFieldName +
"_" +
endTableName;
relationship.startTableId = startTableId; relationship.startTableId = startTableId;
relationship.endTableId = endTable.id; relationship.endTableId = endTable.id;
relationship.endFieldId = endField.id; relationship.endFieldId = endField.id;
@ -178,9 +174,6 @@ export function fromMSSQL(ast, diagramDb = DB.GENERIC) {
} }
} }
}); });
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table); tables.push(table);
} else if (e.keyword === "index") { } else if (e.keyword === "index") {
const index = { const index = {

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants"; import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared"; import { buildSQLFromAST } from "./shared";
@ -32,10 +33,11 @@ export function fromMySQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.create_definitions.forEach((d) => { e.create_definitions.forEach((d) => {
if (d.resource === "column") { if (d.resource === "column") {
const field = {}; const field = {};
field.id = nanoid();
field.name = d.column.column; field.name = d.column.column;
let type = d.definition.dataType; let type = d.definition.dataType;

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, Constraint, DB } from "../../data/constants"; import { Cardinality, Constraint, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
@ -33,10 +34,11 @@ export function fromOracleSQL(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.table.relational_properties.forEach((d) => { e.table.relational_properties.forEach((d) => {
if (d.resource === "column") { if (d.resource === "column") {
const field = {}; const field = {};
field.id = nanoid();
field.name = d.name; field.name = d.name;
let type = d.type.type.toUpperCase(); let type = d.type.type.toUpperCase();
@ -104,7 +106,12 @@ export function fromOracleSQL(ast, diagramDb = DB.GENERIC) {
relationship.name = relationship.name =
d.name && Boolean(d.name.trim()) d.name && Boolean(d.name.trim())
? d.name ? d.name
: "fk_" + table.name + "_" + startFieldName + "_" + endTableName; : "fk_" +
table.name +
"_" +
startFieldName +
"_" +
endTableName;
relationship.deleteConstraint = relationship.deleteConstraint =
d.constraint.reference.on_delete && d.constraint.reference.on_delete &&
Boolean(d.constraint.reference.on_delete.trim()) Boolean(d.constraint.reference.on_delete.trim())

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants"; import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared"; import { buildSQLFromAST } from "./shared";
@ -32,10 +33,11 @@ export function fromPostgres(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.create_definitions.forEach((d) => { e.create_definitions.forEach((d) => {
const field = {}; const field = {};
if (d.resource === "column") { if (d.resource === "column") {
field.id = nanoid();
field.name = d.column.column.expr.value; field.name = d.column.column.expr.value;
let type = types.find((t) => let type = types.find((t) =>

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { Cardinality, DB } from "../../data/constants"; import { Cardinality, DB } from "../../data/constants";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { buildSQLFromAST } from "./shared"; import { buildSQLFromAST } from "./shared";
@ -98,10 +99,11 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
table.color = "#175e7a"; table.color = "#175e7a";
table.fields = []; table.fields = [];
table.indices = []; table.indices = [];
table.id = tables.length; table.id = nanoid();
e.create_definitions.forEach((d) => { e.create_definitions.forEach((d) => {
if (d.resource === "column") { if (d.resource === "column") {
const field = {}; const field = {};
field.id = nanoid();
field.name = d.column.column; field.name = d.column.column;
let type = d.definition.dataType; let type = d.definition.dataType;
@ -187,9 +189,6 @@ export function fromSQLite(ast, diagramDb = DB.GENERIC) {
} }
} }
}); });
table.fields.forEach((f, j) => {
f.id = j;
});
tables.push(table); tables.push(table);
} else if (e.keyword === "index") { } else if (e.keyword === "index") {
const index = { const index = {

View File

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