mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
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:
parent
bbdcd30a73
commit
32df5502ae
50 changed files with 3640 additions and 2047 deletions
|
@ -182,6 +182,7 @@ import {
|
|||
IframeData,
|
||||
ExcalidrawIframeElement,
|
||||
ExcalidrawEmbeddableElement,
|
||||
Ordered,
|
||||
} from "../element/types";
|
||||
import { getCenter, getDistance } from "../gesture";
|
||||
import {
|
||||
|
@ -276,6 +277,7 @@ import {
|
|||
muteFSAbortError,
|
||||
isTestEnv,
|
||||
easeOut,
|
||||
arrayToMap,
|
||||
updateStable,
|
||||
addEventListener,
|
||||
normalizeEOL,
|
||||
|
@ -407,7 +409,6 @@ import { ElementCanvasButton } from "./MagicButton";
|
|||
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
||||
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
||||
import FollowMode from "./FollowMode/FollowMode";
|
||||
|
||||
import { AnimationFrameHandler } from "../animation-frame-handler";
|
||||
import { AnimatedTrail } from "../animated-trail";
|
||||
import { LaserTrails } from "../laser-trails";
|
||||
|
@ -422,6 +423,7 @@ import {
|
|||
} from "../element/collision";
|
||||
import { textWysiwyg } from "../element/textWysiwyg";
|
||||
import { isOverScrollBars } from "../scene/scrollbars";
|
||||
import { syncInvalidIndices, syncMovedIndices } from "../fractionalIndex";
|
||||
import {
|
||||
isPointHittingLink,
|
||||
isPointHittingLinkIcon,
|
||||
|
@ -948,7 +950,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const embeddableElements = this.scene
|
||||
.getNonDeletedElements()
|
||||
.filter(
|
||||
(el): el is NonDeleted<ExcalidrawIframeLikeElement> =>
|
||||
(el): el is Ordered<NonDeleted<ExcalidrawIframeLikeElement>> =>
|
||||
(isEmbeddableElement(el) &&
|
||||
this.embedsValidationStatus.get(el.id) === true) ||
|
||||
isIframeElement(el),
|
||||
|
@ -2056,7 +2058,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
locked: false,
|
||||
});
|
||||
|
||||
this.scene.addNewElement(frame);
|
||||
this.scene.insertElement(frame);
|
||||
|
||||
for (const child of selectedElements) {
|
||||
mutateElement(child, { frameId: frame.id });
|
||||
|
@ -3115,10 +3117,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
},
|
||||
);
|
||||
|
||||
const allElements = [
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
...newElements,
|
||||
];
|
||||
const prevElements = this.scene.getElementsIncludingDeleted();
|
||||
const nextElements = [...prevElements, ...newElements];
|
||||
|
||||
syncMovedIndices(nextElements, arrayToMap(newElements));
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
|
||||
|
||||
|
@ -3127,10 +3129,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
newElements,
|
||||
topLayerFrame,
|
||||
);
|
||||
addElementsToFrame(allElements, eligibleElements, topLayerFrame);
|
||||
addElementsToFrame(nextElements, eligibleElements, topLayerFrame);
|
||||
}
|
||||
|
||||
this.scene.replaceAllElements(allElements);
|
||||
this.scene.replaceAllElements(nextElements);
|
||||
|
||||
newElements.forEach((newElement) => {
|
||||
if (isTextElement(newElement) && isBoundToContainer(newElement)) {
|
||||
|
@ -3361,19 +3363,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const frameId = textElements[0].frameId;
|
||||
|
||||
if (frameId) {
|
||||
this.scene.insertElementsAtIndex(
|
||||
textElements,
|
||||
this.scene.getElementIndex(frameId),
|
||||
);
|
||||
} else {
|
||||
this.scene.replaceAllElements([
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
...textElements,
|
||||
]);
|
||||
}
|
||||
this.scene.insertElements(textElements);
|
||||
|
||||
this.setState({
|
||||
selectedElementIds: makeNextSelectedElementIds(
|
||||
|
@ -4489,7 +4479,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
includeBoundTextElement: boolean = false,
|
||||
includeLockedElements: boolean = false,
|
||||
): NonDeleted<ExcalidrawElement>[] {
|
||||
const iframeLikes: ExcalidrawIframeElement[] = [];
|
||||
const iframeLikes: Ordered<ExcalidrawIframeElement>[] = [];
|
||||
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
|
@ -4758,7 +4748,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const containerIndex = this.scene.getElementIndex(container.id);
|
||||
this.scene.insertElementAtIndex(element, containerIndex + 1);
|
||||
} else {
|
||||
this.scene.addNewElement(element);
|
||||
this.scene.insertElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6639,7 +6629,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
pointerDownState.origin,
|
||||
this,
|
||||
);
|
||||
this.scene.addNewElement(element);
|
||||
this.scene.insertElement(element);
|
||||
this.setState({
|
||||
draggingElement: element,
|
||||
editingElement: element,
|
||||
|
@ -6684,10 +6674,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
height,
|
||||
});
|
||||
|
||||
this.scene.replaceAllElements([
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
element,
|
||||
]);
|
||||
this.scene.insertElement(element);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
@ -6741,10 +6728,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
link,
|
||||
});
|
||||
|
||||
this.scene.replaceAllElements([
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
element,
|
||||
]);
|
||||
this.scene.insertElement(element);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
@ -6908,7 +6892,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this,
|
||||
);
|
||||
|
||||
this.scene.addNewElement(element);
|
||||
this.scene.insertElement(element);
|
||||
this.setState({
|
||||
draggingElement: element,
|
||||
editingElement: element,
|
||||
|
@ -6987,7 +6971,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
draggingElement: element,
|
||||
});
|
||||
} else {
|
||||
this.scene.addNewElement(element);
|
||||
this.scene.insertElement(element);
|
||||
this.setState({
|
||||
multiElement: null,
|
||||
draggingElement: element,
|
||||
|
@ -7021,10 +7005,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
? newMagicFrameElement(constructorOpts)
|
||||
: newFrameElement(constructorOpts);
|
||||
|
||||
this.scene.replaceAllElements([
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
frame,
|
||||
]);
|
||||
this.scene.insertElement(frame);
|
||||
|
||||
this.setState({
|
||||
multiElement: null,
|
||||
|
@ -7437,7 +7418,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||
nextElements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
const nextSceneElements = [...nextElements, ...elementsToAppend];
|
||||
|
||||
syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
|
||||
|
||||
bindTextToShapeAfterDuplication(
|
||||
nextElements,
|
||||
elementsToAppend,
|
||||
|
@ -7454,6 +7439,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
elementsToAppend,
|
||||
oldIdToDuplicatedId,
|
||||
);
|
||||
|
||||
this.scene.replaceAllElements(nextSceneElements);
|
||||
this.maybeCacheVisibleGaps(event, selectedElements, true);
|
||||
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
|
||||
|
@ -8628,7 +8614,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
return;
|
||||
}
|
||||
|
||||
this.scene.addNewElement(imageElement);
|
||||
this.scene.insertElement(imageElement);
|
||||
|
||||
try {
|
||||
return await this.initializeImage({
|
||||
|
@ -9792,7 +9778,9 @@ export const createTestHook = () => {
|
|||
return this.app?.scene.getElementsIncludingDeleted();
|
||||
},
|
||||
set(elements: ExcalidrawElement[]) {
|
||||
return this.app?.scene.replaceAllElements(elements);
|
||||
return this.app?.scene.replaceAllElements(
|
||||
syncInvalidIndices(elements),
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue