diff --git a/package-lock.json b/package-lock.json index 7b818d1..4d05d5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "jspdf": "^3.0.1", "jszip": "^3.10.1", "lexical": "^0.12.5", + "nanoid": "^5.1.5", "node-sql-parser": "^5.3.8", "oracle-sql-parser": "^0.1.0", "react": "^18.2.0", @@ -6235,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", @@ -6247,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": { @@ -6592,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", diff --git a/package.json b/package.json index 1d29a70..c99cf43 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "jspdf": "^3.0.1", "jszip": "^3.10.1", "lexical": "^0.12.5", + "nanoid": "^5.1.5", "node-sql-parser": "^5.3.8", "oracle-sql-parser": "^0.1.0", "react": "^18.2.0", diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx index 3d4bb74..1578e4f 100644 --- a/src/components/EditorCanvas/Canvas.jsx +++ b/src/components/EditorCanvas/Canvas.jsx @@ -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, @@ -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; @@ -318,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, @@ -598,8 +598,8 @@ export default function Canvas() { }; const handleLinking = () => { - if (hoveredTable.tableId < 0) return; - if (hoveredTable.field < 0) return; + if (hoveredTable.tableId === null) return; + if (hoveredTable.fieldId === null) return; const { fields: startTableFields, name: startTableName } = tables.find( (t) => t.id === linkingLine.startTableId, @@ -611,7 +611,7 @@ export default function Canvas() { (t) => t.id === hoveredTable.tableId, ); const { type: endType } = endTableFields.find( - (f) => f.id === hoveredTable.field, + (f) => f.id === hoveredTable.fieldId, ); if (!areFieldsCompatible(database, startType, endType)) { @@ -620,14 +620,14 @@ export default function Canvas() { } 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, diff --git a/src/components/EditorCanvas/Table.jsx b/src/components/EditorCanvas/Table.jsx index e0c3c3d..f082bd5 100644 --- a/src/components/EditorCanvas/Table.jsx +++ b/src/components/EditorCanvas/Table.jsx @@ -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} />
@@ -304,13 +305,17 @@ export default function Table(props) { setHoveredField(index); setHoveredTable({ tableId: tableData.id, - field: fieldData.id, + 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 diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index 0b0c6dc..d60b551 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -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, @@ -150,7 +151,7 @@ export default function ControlPanel({ 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 +166,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 +204,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 +213,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 +240,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 +341,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 +378,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 +386,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 +397,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 +425,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 })), }); diff --git a/src/components/EditorHeader/Modal/ImportDiagram.jsx b/src/components/EditorHeader/Modal/ImportDiagram.jsx index 8afe71f..3d744c4 100644 --- a/src/components/EditorHeader/Modal/ImportDiagram.jsx +++ b/src/components/EditorHeader/Modal/ImportDiagram.jsx @@ -83,10 +83,12 @@ export default function ImportDiagram({ let ok = true; jsonObject.relationships.forEach((rel) => { - if ( - !jsonObject.tables.find(rel.startTableId) || - !jsonObject.tables.find(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,10 +98,8 @@ export default function ImportDiagram({ } if ( - !jsonObject.tables - .find(rel.startTableId) - .fields.find(rel.startFieldId) || - !jsonObject.tables.find(rel.endTableId).fields.find(rel.endFieldId) + !startTable.fields.find((f) => f.id === rel.startFieldId) || + !endTable.fields.find((f) => f.id === rel.endFieldId) ) { setError({ type: STATUS.ERROR, diff --git a/src/components/EditorHeader/Modal/Modal.jsx b/src/components/EditorHeader/Modal/Modal.jsx index 812b07b..e6fc3b3 100644 --- a/src/components/EditorHeader/Modal/Modal.jsx +++ b/src/components/EditorHeader/Modal/Modal.jsx @@ -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, })), ); diff --git a/src/components/EditorHeader/Modal/Share.jsx b/src/components/EditorHeader/Modal/Share.jsx index 4ec4c78..87f843c 100644 --- a/src/components/EditorHeader/Modal/Share.jsx +++ b/src/components/EditorHeader/Modal/Share.jsx @@ -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, }); }, [ diff --git a/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx b/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx index dfaf709..0dc80cc 100644 --- a/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx +++ b/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx @@ -14,7 +14,7 @@ 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(); @@ -41,7 +41,7 @@ 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", { @@ -81,7 +81,7 @@ 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", { @@ -113,7 +113,7 @@ 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", { @@ -150,7 +150,7 @@ 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", { @@ -183,7 +183,7 @@ 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", { @@ -211,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, }, @@ -243,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, }, @@ -278,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, }, @@ -315,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, @@ -356,7 +356,7 @@ 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", { diff --git a/src/components/EditorSidePanel/TablesTab/TableField.jsx b/src/components/EditorSidePanel/TablesTab/TableField.jsx index 7365ea2..7c744f4 100644 --- a/src/components/EditorSidePanel/TablesTab/TableField.jsx +++ b/src/components/EditorSidePanel/TablesTab/TableField.jsx @@ -38,7 +38,7 @@ 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", { @@ -81,7 +81,7 @@ 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", { @@ -148,7 +148,7 @@ 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", { @@ -177,7 +177,7 @@ 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", { @@ -196,7 +196,7 @@ export default function TableField({ data, tid, index }) { - +
} trigger="click" diff --git a/src/components/EditorSidePanel/TablesTab/TableInfo.jsx b/src/components/EditorSidePanel/TablesTab/TableInfo.jsx index c042a96..b1cd626 100644 --- a/src/components/EditorSidePanel/TablesTab/TableInfo.jsx +++ b/src/components/EditorSidePanel/TablesTab/TableInfo.jsx @@ -14,6 +14,7 @@ import TableField from "./TableField"; import IndexDetails from "./IndexDetails"; import { useTranslation } from "react-i18next"; import { SortableList } from "../../SortableList/SortableList"; +import { nanoid } from "nanoid"; export default function TableInfo({ data }) { const { t } = useTranslation(); @@ -200,6 +201,7 @@ export default function TableInfo({ data }) {