diff --git a/src/data/restore.ts b/src/data/restore.ts index e04f0b6c03..06aed01136 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -43,7 +43,6 @@ import { measureBaseline, } from "../element/textElement"; import { normalizeLink } from "./url"; -import { normalizeFractionalIndexing } from "../zindex"; type RestoredAppState = Omit< AppState, @@ -583,9 +582,7 @@ export const restore = ( elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean }, ): RestoredDataState => { return { - elements: normalizeFractionalIndexing( - restoreElements(data?.elements, localElements, elementsConfig), - ), + elements: restoreElements(data?.elements, localElements, elementsConfig), appState: restoreAppState(data?.appState, localAppState || null), files: data?.files || {}, }; diff --git a/src/scene/Scene.ts b/src/scene/Scene.ts index 814638e7e3..7b5599fa8e 100644 --- a/src/scene/Scene.ts +++ b/src/scene/Scene.ts @@ -11,6 +11,7 @@ import { getSelectedElements } from "./selection"; import { AppState } from "../types"; import { Assert, SameType } from "../utility-types"; import { randomInteger } from "../random"; +import { normalizeFractionalIndexing } from "../zindex"; type ElementIdKey = InstanceType["elementId"]; type ElementKey = ExcalidrawElement | ElementIdKey; @@ -231,10 +232,12 @@ class Scene { nextElements: readonly ExcalidrawElement[], mapElementIds = true, ) { - this.elements = nextElements; + const _nextElements = normalizeFractionalIndexing(nextElements); + + this.elements = _nextElements; const nextFrameLikes: ExcalidrawFrameLikeElement[] = []; this.elementsMap.clear(); - nextElements.forEach((element) => { + _nextElements.forEach((element) => { if (isFrameLikeElement(element)) { nextFrameLikes.push(element); } diff --git a/src/zindex.ts b/src/zindex.ts index b33aa6bd2a..4345e53917 100644 --- a/src/zindex.ts +++ b/src/zindex.ts @@ -1,4 +1,4 @@ -import { bumpVersion } from "./element/mutateElement"; +import { bumpVersion, mutateElement } from "./element/mutateElement"; import { isFrameLikeElement } from "./element/typeChecks"; import { ExcalidrawElement, ExcalidrawFrameLikeElement } from "./element/types"; import { getElementsInGroup } from "./groups"; @@ -490,74 +490,75 @@ function shiftElementsAccountingForFrames( // ----------------------------------------------------------------------------- type FractionalIndex = ExcalidrawElement["fractionalIndex"]; -const fractionalIndexCompare = { - isSmallerThan(indexA: string, indexB: string) { - return indexA < indexB; - }, - - isGreaterThan(indexA: string, indexB: string) { - return indexA > indexB; - }, -}; - const isValidFractionalIndex = ( index: FractionalIndex, - predecessorFractionalIndex: FractionalIndex, - successorFractionalIndex: FractionalIndex, + predecessor: FractionalIndex, + successor: FractionalIndex, ) => { if (index) { - if (predecessorFractionalIndex) { - if (successorFractionalIndex) { - return ( - fractionalIndexCompare.isGreaterThan( - index, - predecessorFractionalIndex, - ) && - fractionalIndexCompare.isSmallerThan(index, successorFractionalIndex) - ); - } - return fractionalIndexCompare.isGreaterThan( - index, - predecessorFractionalIndex, - ); + if (!predecessor && !successor) { + return index.length > 0; } - if (successorFractionalIndex) { - return fractionalIndexCompare.isSmallerThan( - index, - successorFractionalIndex, - ); + if (!predecessor) { + // first element + return index < successor!; } - return index.length > 0; + if (!successor) { + // last element + return predecessor! < index; + } } return false; }; const generateFractionalIndex = ( - predecessorFractionalIndex: string | null, - successorFractionalIndex: string | null, + index: FractionalIndex, + predecessor: FractionalIndex, + successor: FractionalIndex, ) => { - if (predecessorFractionalIndex && successorFractionalIndex) { - if (predecessorFractionalIndex < successorFractionalIndex) { - return generateKeyBetween( - predecessorFractionalIndex, - successorFractionalIndex, - ); - } else if (predecessorFractionalIndex > successorFractionalIndex) { - return generateKeyBetween( - successorFractionalIndex, - predecessorFractionalIndex, - ); + if (index) { + if (!predecessor && !successor) { + return index; } - return generateKeyBetween(predecessorFractionalIndex, null); + + if (!predecessor) { + // first element in the array + return generateKeyBetween(null, successor); + } + + if (!successor) { + // last element in the array + return generateKeyBetween(predecessor, null); + } + + // both predecessor and successor exist + // insert after predecessor + return generateKeyBetween(predecessor, null); } - return generateKeyBetween( - predecessorFractionalIndex, - successorFractionalIndex, - ); + return generateKeyBetween(null, null); +}; + +const compareStrings = (a: string, b: string) => { + return a < b ? -1 : 1; +}; + +export const orderByFractionalIndex = (allElements: ExcalidrawElement[]) => { + return allElements.sort((a, b) => { + if (a.fractionalIndex && b.fractionalIndex) { + if (a.fractionalIndex < b.fractionalIndex) { + return -1; + } else if (a.fractionalIndex > b.fractionalIndex) { + return 1; + } + return compareStrings(a.id, b.id); + } + + return 0; + }); }; /** @@ -568,47 +569,39 @@ const generateFractionalIndex = ( export const normalizeFractionalIndexing = ( allElements: readonly ExcalidrawElement[], ) => { - let predecessor = -1; - let successor = 1; - - const normalizedElementsMap = arrayToMap(allElements); + let pre = -1; + let suc = 1; for (const element of allElements) { - const predecessorFractionalIndex = - normalizedElementsMap.get(allElements[predecessor]?.id) - ?.fractionalIndex || null; - - const successorFractionalIndex = - normalizedElementsMap.get(allElements[successor]?.id)?.fractionalIndex || - null; + const predecessor = allElements[pre]?.fractionalIndex || null; + const successor = allElements[suc]?.fractionalIndex || null; - try { - if ( - !isValidFractionalIndex( - element.fractionalIndex, - predecessorFractionalIndex, - successorFractionalIndex, - ) - ) { + if ( + !isValidFractionalIndex(element.fractionalIndex, predecessor, successor) + ) { + try { const nextFractionalIndex = generateFractionalIndex( - predecessorFractionalIndex, - successorFractionalIndex, + element.fractionalIndex, + predecessor, + successor, ); - normalizedElementsMap.set(element.id, { - ...element, - fractionalIndex: nextFractionalIndex, - }); + mutateElement( + element, + { + fractionalIndex: nextFractionalIndex, + }, + false, + ); + } catch (e) { + console.error(e); } - } catch (e) { - console.error(e); } - - predecessor++; - successor++; + pre++; + suc++; } - return [...normalizedElementsMap.values()]; + return allElements; }; // public API @@ -618,31 +611,25 @@ export const moveOneLeft = ( allElements: readonly ExcalidrawElement[], appState: AppState, ) => { - return normalizeFractionalIndexing( - shiftElementsByOne(allElements, appState, "left"), - ); + return shiftElementsByOne(allElements, appState, "left"); }; export const moveOneRight = ( allElements: readonly ExcalidrawElement[], appState: AppState, ) => { - return normalizeFractionalIndexing( - shiftElementsByOne(allElements, appState, "right"), - ); + return shiftElementsByOne(allElements, appState, "right"); }; export const moveAllLeft = ( allElements: readonly ExcalidrawElement[], appState: AppState, ) => { - return normalizeFractionalIndexing( - shiftElementsAccountingForFrames( - allElements, - appState, - "left", - shiftElementsToEnd, - ), + return shiftElementsAccountingForFrames( + allElements, + appState, + "left", + shiftElementsToEnd, ); }; @@ -650,12 +637,10 @@ export const moveAllRight = ( allElements: readonly ExcalidrawElement[], appState: AppState, ) => { - return normalizeFractionalIndexing( - shiftElementsAccountingForFrames( - allElements, - appState, - "right", - shiftElementsToEnd, - ), + return shiftElementsAccountingForFrames( + allElements, + appState, + "right", + shiftElementsToEnd, ); };