feat: fractional indexing (#7359)

* Introducing fractional indices as part of `element.index`

* Ensuring invalid fractional indices are always synchronized with the array order

* Simplifying reconciliation based on the fractional indices

* Moving reconciliation inside the `@excalidraw/excalidraw` package

---------

Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di 2024-04-04 20:51:11 +08:00 committed by GitHub
parent bbdcd30a73
commit 32df5502ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 3640 additions and 2047 deletions

View file

@ -20,6 +20,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"groupIds": [],
"height": 300,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
@ -32,7 +33,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 300,
"x": 630,
@ -56,6 +57,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"link": null,
"locked": false,
@ -68,7 +70,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 140,
"x": 96,
@ -93,6 +95,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"groupIds": [],
"height": 35,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -122,7 +125,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 395,
"x": 247,
@ -147,6 +150,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -176,7 +180,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 400,
"x": 227,
@ -200,6 +204,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"groupIds": [],
"height": 300,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"link": null,
"locked": false,
@ -212,7 +217,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 300,
"x": -53,
@ -239,6 +244,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -255,7 +261,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 70,
@ -283,6 +289,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -299,7 +306,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 100,
@ -330,6 +337,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -359,7 +367,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 100,
"x": 255,
@ -381,6 +389,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -397,7 +406,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,
@ -428,6 +437,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -457,7 +467,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 100,
"x": 255,
@ -479,6 +489,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -495,7 +506,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,
@ -520,6 +531,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"link": null,
"locked": false,
@ -532,7 +544,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 100,
"x": 155,
@ -556,6 +568,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"link": null,
"locked": false,
@ -568,7 +581,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 100,
"x": 355,
@ -598,6 +611,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -627,7 +641,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 100,
"x": 255,
@ -649,6 +663,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -665,7 +680,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,
@ -693,6 +708,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -709,7 +725,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 70,
@ -737,6 +753,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -753,7 +770,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 100,
@ -773,6 +790,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
"groupIds": [],
"height": 200,
"id": "rect-1",
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
@ -785,7 +803,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 300,
@ -806,6 +824,7 @@ exports[`Test Transform > should transform linear elements 1`] = `
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -831,7 +850,7 @@ exports[`Test Transform > should transform linear elements 1`] = `
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -852,6 +871,7 @@ exports[`Test Transform > should transform linear elements 2`] = `
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -877,7 +897,7 @@ exports[`Test Transform > should transform linear elements 2`] = `
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 450,
@ -898,6 +918,7 @@ exports[`Test Transform > should transform linear elements 3`] = `
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -923,7 +944,7 @@ exports[`Test Transform > should transform linear elements 3`] = `
"strokeWidth": 2,
"type": "line",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -944,6 +965,7 @@ exports[`Test Transform > should transform linear elements 4`] = `
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -969,7 +991,7 @@ exports[`Test Transform > should transform linear elements 4`] = `
"strokeWidth": 2,
"type": "line",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 450,
@ -988,6 +1010,7 @@ exports[`Test Transform > should transform regular shapes 1`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
@ -1000,7 +1023,7 @@ exports[`Test Transform > should transform regular shapes 1`] = `
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1019,6 +1042,7 @@ exports[`Test Transform > should transform regular shapes 2`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"link": null,
"locked": false,
@ -1031,7 +1055,7 @@ exports[`Test Transform > should transform regular shapes 2`] = `
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1050,6 +1074,7 @@ exports[`Test Transform > should transform regular shapes 3`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"link": null,
"locked": false,
@ -1062,7 +1087,7 @@ exports[`Test Transform > should transform regular shapes 3`] = `
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1081,6 +1106,7 @@ exports[`Test Transform > should transform regular shapes 4`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"link": null,
"locked": false,
@ -1093,7 +1119,7 @@ exports[`Test Transform > should transform regular shapes 4`] = `
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 200,
"x": 300,
@ -1112,6 +1138,7 @@ exports[`Test Transform > should transform regular shapes 5`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"link": null,
"locked": false,
@ -1124,7 +1151,7 @@ exports[`Test Transform > should transform regular shapes 5`] = `
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 200,
"x": 300,
@ -1143,6 +1170,7 @@ exports[`Test Transform > should transform regular shapes 6`] = `
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a5",
"isDeleted": false,
"link": null,
"locked": false,
@ -1155,7 +1183,7 @@ exports[`Test Transform > should transform regular shapes 6`] = `
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 200,
"x": 300,
@ -1177,6 +1205,7 @@ exports[`Test Transform > should transform text element 1`] = `
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1193,7 +1222,7 @@ exports[`Test Transform > should transform text element 1`] = `
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 120,
@ -1216,6 +1245,7 @@ exports[`Test Transform > should transform text element 2`] = `
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1232,7 +1262,7 @@ exports[`Test Transform > should transform text element 2`] = `
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 190,
@ -1259,6 +1289,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -1284,7 +1315,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1310,6 +1341,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -1335,7 +1367,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1361,6 +1393,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -1386,7 +1419,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1412,6 +1445,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
@ -1437,7 +1471,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 100,
"x": 100,
@ -1459,6 +1493,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1475,7 +1510,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,
@ -1498,6 +1533,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a5",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1514,7 +1550,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 200,
@ -1537,6 +1573,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 50,
"id": Any<String>,
"index": "a6",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1554,7 +1591,7 @@ LABELLED ARROW",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 150,
@ -1577,6 +1614,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
"groupIds": [],
"height": 50,
"id": Any<String>,
"index": "a7",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1594,7 +1632,7 @@ LABELLED ARROW",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 150,
@ -1619,6 +1657,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 35,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
@ -1631,7 +1670,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"version": 4,
"versionNonce": Any<Number>,
"width": 250,
"x": 100,
@ -1655,6 +1694,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 85,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"link": null,
"locked": false,
@ -1667,7 +1707,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 200,
"x": 500,
@ -1691,6 +1731,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 170,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"link": null,
"locked": false,
@ -1703,7 +1744,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 280,
"x": 100,
@ -1727,6 +1768,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 120,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"link": null,
"locked": false,
@ -1739,7 +1781,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 300,
"x": 100,
@ -1763,6 +1805,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 85,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"link": null,
"locked": false,
@ -1775,7 +1818,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 200,
"x": 500,
@ -1799,6 +1842,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 120,
"id": Any<String>,
"index": "a5",
"isDeleted": false,
"link": null,
"locked": false,
@ -1811,7 +1855,7 @@ exports[`Test Transform > should transform to text containers when label provide
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"width": 200,
"x": 500,
@ -1833,6 +1877,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "a6",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1849,7 +1894,7 @@ exports[`Test Transform > should transform to text containers when label provide
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 240,
@ -1872,6 +1917,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 50,
"id": Any<String>,
"index": "a7",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1889,7 +1935,7 @@ CONTAINER",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,
@ -1912,6 +1958,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 75,
"id": Any<String>,
"index": "a8",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1931,7 +1978,7 @@ CONTAINER",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 90,
@ -1954,6 +2001,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 50,
"id": Any<String>,
"index": "a9",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -1971,7 +2019,7 @@ TEXT CONTAINER",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 140,
@ -1994,6 +2042,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 75,
"id": Any<String>,
"index": "aA",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -2012,7 +2061,7 @@ CONTAINER",
"textAlign": "left",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 170,
@ -2035,6 +2084,7 @@ exports[`Test Transform > should transform to text containers when label provide
"groupIds": [],
"height": 75,
"id": Any<String>,
"index": "aB",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
@ -2053,7 +2103,7 @@ CONTAINER",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 2,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 130,

View file

@ -0,0 +1,79 @@
import { OrderedExcalidrawElement } from "../element/types";
import { orderByFractionalIndex, syncInvalidIndices } from "../fractionalIndex";
import { AppState } from "../types";
import { MakeBrand } from "../utility-types";
import { arrayToMap } from "../utils";
export type ReconciledExcalidrawElement = OrderedExcalidrawElement &
MakeBrand<"ReconciledElement">;
export type RemoteExcalidrawElement = OrderedExcalidrawElement &
MakeBrand<"RemoteExcalidrawElement">;
const shouldDiscardRemoteElement = (
localAppState: AppState,
local: OrderedExcalidrawElement | undefined,
remote: RemoteExcalidrawElement,
): boolean => {
if (
local &&
// local element is being edited
(local.id === localAppState.editingElement?.id ||
local.id === localAppState.resizingElement?.id ||
local.id === localAppState.draggingElement?.id ||
// local element is newer
local.version > remote.version ||
// resolve conflicting edits deterministically by taking the one with
// the lowest versionNonce
(local.version === remote.version &&
local.versionNonce < remote.versionNonce))
) {
return true;
}
return false;
};
export const reconcileElements = (
localElements: readonly OrderedExcalidrawElement[],
remoteElements: readonly RemoteExcalidrawElement[],
localAppState: AppState,
): ReconciledExcalidrawElement[] => {
const localElementsMap = arrayToMap(localElements);
const reconciledElements: OrderedExcalidrawElement[] = [];
const added = new Set<string>();
// process remote elements
for (const remoteElement of remoteElements) {
if (!added.has(remoteElement.id)) {
const localElement = localElementsMap.get(remoteElement.id);
const discardRemoteElement = shouldDiscardRemoteElement(
localAppState,
localElement,
remoteElement,
);
if (localElement && discardRemoteElement) {
reconciledElements.push(localElement);
added.add(localElement.id);
} else {
reconciledElements.push(remoteElement);
added.add(remoteElement.id);
}
}
}
// process remaining local elements
for (const localElement of localElements) {
if (!added.has(localElement.id)) {
reconciledElements.push(localElement);
added.add(localElement.id);
}
}
const orderedElements = orderByFractionalIndex(reconciledElements);
// de-duplicate indices
syncInvalidIndices(orderedElements);
return orderedElements as ReconciledExcalidrawElement[];
};

View file

@ -4,6 +4,7 @@ import {
ExcalidrawSelectionElement,
ExcalidrawTextElement,
FontFamilyValues,
OrderedExcalidrawElement,
PointBinding,
StrokeRoundness,
} from "../element/types";
@ -26,7 +27,6 @@ import {
DEFAULT_FONT_FAMILY,
DEFAULT_TEXT_ALIGN,
DEFAULT_VERTICAL_ALIGN,
PRECEDING_ELEMENT_KEY,
FONT_FAMILY,
ROUNDNESS,
DEFAULT_SIDEBAR,
@ -44,6 +44,7 @@ import {
getDefaultLineHeight,
} from "../element/textElement";
import { normalizeLink } from "./url";
import { syncInvalidIndices } from "../fractionalIndex";
type RestoredAppState = Omit<
AppState,
@ -73,7 +74,7 @@ export const AllowedExcalidrawActiveTools: Record<
};
export type RestoredDataState = {
elements: ExcalidrawElement[];
elements: OrderedExcalidrawElement[];
appState: RestoredAppState;
files: BinaryFiles;
};
@ -101,8 +102,6 @@ const restoreElementWithProperties = <
boundElementIds?: readonly ExcalidrawElement["id"][];
/** @deprecated */
strokeSharpness?: StrokeRoundness;
/** metadata that may be present in elements during collaboration */
[PRECEDING_ELEMENT_KEY]?: string;
},
K extends Pick<T, keyof Omit<Required<T>, keyof ExcalidrawElement>>,
>(
@ -115,14 +114,13 @@ const restoreElementWithProperties = <
> &
Partial<Pick<ExcalidrawElement, "type" | "x" | "y" | "customData">>,
): T => {
const base: Pick<T, keyof ExcalidrawElement> & {
[PRECEDING_ELEMENT_KEY]?: string;
} = {
const base: Pick<T, keyof ExcalidrawElement> = {
type: extra.type || element.type,
// all elements must have version > 0 so getSceneVersion() will pick up
// newly added elements
version: element.version || 1,
versionNonce: element.versionNonce ?? 0,
index: element.index ?? null,
isDeleted: element.isDeleted ?? false,
id: element.id || randomId(),
fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
@ -166,10 +164,6 @@ const restoreElementWithProperties = <
"customData" in extra ? extra.customData : element.customData;
}
if (PRECEDING_ELEMENT_KEY in element) {
base[PRECEDING_ELEMENT_KEY] = element[PRECEDING_ELEMENT_KEY];
}
return {
...base,
...getNormalizedDimensions(base),
@ -407,30 +401,35 @@ export const restoreElements = (
/** NOTE doesn't serve for reconciliation */
localElements: readonly ExcalidrawElement[] | null | undefined,
opts?: { refreshDimensions?: boolean; repairBindings?: boolean } | undefined,
): ExcalidrawElement[] => {
): OrderedExcalidrawElement[] => {
// used to detect duplicate top-level element ids
const existingIds = new Set<string>();
const localElementsMap = localElements ? arrayToMap(localElements) : null;
const restoredElements = (elements || []).reduce((elements, element) => {
// filtering out selection, which is legacy, no longer kept in elements,
// and causing issues if retained
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
let migratedElement: ExcalidrawElement | null = restoreElement(element);
if (migratedElement) {
const localElement = localElementsMap?.get(element.id);
if (localElement && localElement.version > migratedElement.version) {
migratedElement = bumpVersion(migratedElement, localElement.version);
}
if (existingIds.has(migratedElement.id)) {
migratedElement = { ...migratedElement, id: randomId() };
}
existingIds.add(migratedElement.id);
const restoredElements = syncInvalidIndices(
(elements || []).reduce((elements, element) => {
// filtering out selection, which is legacy, no longer kept in elements,
// and causing issues if retained
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
let migratedElement: ExcalidrawElement | null = restoreElement(element);
if (migratedElement) {
const localElement = localElementsMap?.get(element.id);
if (localElement && localElement.version > migratedElement.version) {
migratedElement = bumpVersion(
migratedElement,
localElement.version,
);
}
if (existingIds.has(migratedElement.id)) {
migratedElement = { ...migratedElement, id: randomId() };
}
existingIds.add(migratedElement.id);
elements.push(migratedElement);
elements.push(migratedElement);
}
}
}
return elements;
}, [] as ExcalidrawElement[]);
return elements;
}, [] as ExcalidrawElement[]),
);
if (!opts?.repairBindings) {
return restoredElements;

View file

@ -44,9 +44,16 @@ import {
VerticalAlign,
} from "../element/types";
import { MarkOptional } from "../utility-types";
import { assertNever, cloneJSON, getFontString, toBrandedType } from "../utils";
import {
arrayToMap,
assertNever,
cloneJSON,
getFontString,
toBrandedType,
} from "../utils";
import { getSizeFromPoints } from "../points";
import { randomId } from "../random";
import { syncInvalidIndices } from "../fractionalIndex";
export type ValidLinearElement = {
type: "arrow" | "line";
@ -457,12 +464,15 @@ class ElementStore {
this.excalidrawElements.set(ele.id, ele);
};
getElements = () => {
return Array.from(this.excalidrawElements.values());
return syncInvalidIndices(Array.from(this.excalidrawElements.values()));
};
getElementsMap = () => {
return toBrandedType<NonDeletedSceneElementsMap>(this.excalidrawElements);
return toBrandedType<NonDeletedSceneElementsMap>(
arrayToMap(this.getElements()),
);
};
getElement = (id: string) => {