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

View file

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

View file

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

View file

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

View file

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

View file

@ -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", () => { describe("should sync when fractional index is not defined", () => {
testMovedIndicesSync({ testMovedIndicesSync({
elements: [{ id: "A" }], 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 fractions for explicitly moved elements", () => {
describe("should generate a fraction between 'A' and 'C'", () => { describe("should generate a fraction between 'A' and 'C'", () => {
testMovedIndicesSync({ testMovedIndicesSync({