1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2025-02-18 13:29:36 +01:00

fix: always re-generate index of defined moved elements (#8040)

This commit is contained in:
Marcel Mraz 2024-05-20 22:23:42 +01:00 committed by GitHub
parent 2f9526da24
commit eddbe55f50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 125 deletions

@ -1477,19 +1477,28 @@ export class ElementsChange implements Change<SceneElementsMap> {
return elements;
}
const previous = Array.from(elements.values());
const reordered = orderByFractionalIndex([...previous]);
const unordered = Array.from(elements.values());
const ordered = orderByFractionalIndex([...unordered]);
const moved = Delta.getRightDifferences(unordered, ordered, true).reduce(
(acc, arrayIndex) => {
const candidate = unordered[Number(arrayIndex)];
if (candidate && changed.has(candidate.id)) {
acc.set(candidate.id, candidate);
}
if (
!flags.containsVisibleDifference &&
Delta.isRightDifferent(previous, reordered, true)
) {
return acc;
},
new Map(),
);
if (!flags.containsVisibleDifference && moved.size) {
// we found a difference in order!
flags.containsVisibleDifference = true;
}
// let's synchronize all invalid indices of moved elements
return arrayToMap(syncMovedIndices(reordered, changed)) as typeof elements;
// synchronize all elements that were actually moved
// could fallback to synchronizing all invalid indices
return arrayToMap(syncMovedIndices(ordered, moved)) as typeof elements;
}
/**

@ -133,27 +133,11 @@ const getMovedIndicesGroups = (
let i = 0;
while (i < elements.length) {
if (
movedElements.has(elements[i].id) &&
!isValidFractionalIndex(
elements[i]?.index,
elements[i - 1]?.index,
elements[i + 1]?.index,
)
) {
if (movedElements.has(elements[i].id)) {
const indicesGroup = [i - 1, i]; // push the lower bound index as the first item
while (++i < elements.length) {
if (
!(
movedElements.has(elements[i].id) &&
!isValidFractionalIndex(
elements[i]?.index,
elements[i - 1]?.index,
elements[i + 1]?.index,
)
)
) {
if (!movedElements.has(elements[i].id)) {
break;
}

@ -17320,7 +17320,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 6,
"version": 7,
"width": 10,
"x": 10,
"y": 0,
@ -17352,7 +17352,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 9,
"version": 10,
"width": 10,
"x": 40,
"y": 40,
@ -17604,7 +17604,7 @@ History {
"index": "a2",
},
"inserted": {
"index": "a0",
"index": "Zz",
},
},
"id41" => Delta {
@ -17612,7 +17612,7 @@ History {
"index": "a3",
},
"inserted": {
"index": "a0V",
"index": "a0",
},
},
},

@ -336,7 +336,7 @@ History {
"groupIds": [
"id5",
],
"index": "a1V",
"index": "a2",
},
"inserted": {
"groupIds": [],
@ -348,9 +348,11 @@ History {
"groupIds": [
"id5",
],
"index": "a3",
},
"inserted": {
"groupIds": [],
"index": "a2",
},
},
},
@ -719,7 +721,7 @@ History {
"groupIds": [
"id4",
],
"index": "a1V",
"index": "a2",
},
"inserted": {
"groupIds": [],
@ -731,9 +733,11 @@ History {
"groupIds": [
"id4",
],
"index": "a3",
},
"inserted": {
"groupIds": [],
"index": "a2",
},
},
},
@ -1856,7 +1860,7 @@ History {
"groupIds": [
"id5",
],
"index": "a1V",
"index": "a2",
},
"inserted": {
"groupIds": [],
@ -1868,9 +1872,11 @@ History {
"groupIds": [
"id5",
],
"index": "a3",
},
"inserted": {
"groupIds": [],
"index": "a2",
},
},
},
@ -12810,7 +12816,7 @@ History {
"id5",
"id3",
],
"index": "a1V",
"index": "a2",
},
"inserted": {
"groupIds": [
@ -12825,11 +12831,13 @@ History {
"id5",
"id3",
],
"index": "a3",
},
"inserted": {
"groupIds": [
"id3",
],
"index": "a2",
},
},
},

@ -77,97 +77,6 @@ describe("sync invalid indices with array order", () => {
});
});
describe("should NOT sync index when it is already valid", () => {
testMovedIndicesSync({
elements: [
{ id: "A", index: "a2" },
{ id: "B", index: "a4" },
],
movedElements: ["A"],
expect: {
validInput: true,
unchangedElements: ["A", "B"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A", index: "a2" },
{ id: "B", index: "a4" },
],
movedElements: ["B"],
expect: {
validInput: true,
unchangedElements: ["A", "B"],
},
});
});
describe("should NOT sync indices when they are already valid", () => {
{
testMovedIndicesSync({
elements: [
{ id: "A", index: "a1" },
{ id: "B", index: "a0" },
{ id: "C", index: "a2" },
],
movedElements: ["B", "C"],
expect: {
// this should not sync 'C'
unchangedElements: ["A", "C"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A", index: "a1" },
{ id: "B", index: "a0" },
{ id: "C", index: "a2" },
],
movedElements: ["A", "B"],
expect: {
// but this should sync 'A' as it's invalid!
unchangedElements: ["C"],
},
});
}
testMovedIndicesSync({
elements: [
{ id: "A", index: "a0" },
{ id: "B", index: "a2" },
{ id: "C", index: "a1" },
{ id: "D", index: "a1" },
{ id: "E", index: "a2" },
],
movedElements: ["B", "D", "E"],
expect: {
// should not sync 'E'
unchangedElements: ["A", "C", "E"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A" },
{ id: "B" },
{ id: "C", index: "a0" },
{ id: "D", index: "a2" },
{ id: "E" },
{ id: "F", index: "a3" },
{ id: "G" },
{ id: "H", index: "a1" },
{ id: "I", index: "a2" },
{ id: "J" },
],
movedElements: ["A", "B", "D", "E", "F", "G", "J"],
expect: {
// should not sync 'D' and 'F'
unchangedElements: ["C", "D", "F"],
},
});
});
describe("should sync when fractional index is not defined", () => {
testMovedIndicesSync({
elements: [{ id: "A" }],
@ -384,6 +293,122 @@ describe("sync invalid indices with array order", () => {
});
});
describe("should sync all moved elements regardless of their validity", () => {
testMovedIndicesSync({
elements: [
{ id: "A", index: "a2" },
{ id: "B", index: "a4" },
],
movedElements: ["A"],
expect: {
validInput: true,
unchangedElements: ["B"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A", index: "a2" },
{ id: "B", index: "a4" },
],
movedElements: ["B"],
expect: {
validInput: true,
unchangedElements: ["A"],
},
});
testMovedIndicesSync({
elements: [
{ id: "C", index: "a2" },
{ id: "D", index: "a3" },
{ id: "A", index: "a0" },
{ id: "B", index: "a1" },
],
movedElements: ["C", "D"],
expect: {
unchangedElements: ["A", "B"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A", index: "a1" },
{ id: "B", index: "a2" },
{ id: "D", index: "a4" },
{ id: "C", index: "a3" },
{ id: "F", index: "a6" },
{ id: "E", index: "a5" },
{ id: "H", index: "a8" },
{ id: "G", index: "a7" },
{ id: "I", index: "a9" },
],
movedElements: ["D", "F", "H"],
expect: {
unchangedElements: ["A", "B", "C", "E", "G", "I"],
},
});
{
testMovedIndicesSync({
elements: [
{ id: "A", index: "a1" },
{ id: "B", index: "a0" },
{ id: "C", index: "a2" },
],
movedElements: ["B", "C"],
expect: {
unchangedElements: ["A"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A", index: "a1" },
{ id: "B", index: "a0" },
{ id: "C", index: "a2" },
],
movedElements: ["A", "B"],
expect: {
unchangedElements: ["C"],
},
});
}
testMovedIndicesSync({
elements: [
{ id: "A", index: "a0" },
{ id: "B", index: "a2" },
{ id: "C", index: "a1" },
{ id: "D", index: "a1" },
{ id: "E", index: "a2" },
],
movedElements: ["B", "D", "E"],
expect: {
unchangedElements: ["A", "C"],
},
});
testMovedIndicesSync({
elements: [
{ id: "A" },
{ id: "B" },
{ id: "C", index: "a0" },
{ id: "D", index: "a2" },
{ id: "E" },
{ id: "F", index: "a3" },
{ id: "G" },
{ id: "H", index: "a1" },
{ id: "I", index: "a2" },
{ id: "J" },
],
movedElements: ["A", "B", "D", "E", "F", "G", "J"],
expect: {
unchangedElements: ["C", "H", "I"],
},
});
});
describe("should generate fractions for explicitly moved elements", () => {
describe("should generate a fraction between 'A' and 'C'", () => {
testMovedIndicesSync({