diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx index 82181f9c19..45ddd4660f 100644 --- a/excalidraw-app/collab/Portal.tsx +++ b/excalidraw-app/collab/Portal.tsx @@ -18,7 +18,6 @@ import throttle from "lodash.throttle"; import { newElementWith } from "../../src/element/mutateElement"; import { BroadcastedExcalidrawElement } from "./reconciliation"; import { encryptData } from "../../src/data/encryption"; -import { normalizeFractionalIndexing } from "../../src/zindex"; class Portal { collab: TCollabClass; @@ -160,7 +159,7 @@ class Portal { const data: SocketUpdateDataSource[typeof updateType] = { type: updateType, payload: { - elements: normalizeFractionalIndexing(syncableElements), + elements: syncableElements, }, }; diff --git a/excalidraw-app/collab/reconciliation.ts b/excalidraw-app/collab/reconciliation.ts index 8c873f1023..f65fb91c88 100644 --- a/excalidraw-app/collab/reconciliation.ts +++ b/excalidraw-app/collab/reconciliation.ts @@ -1,7 +1,7 @@ import { ExcalidrawElement } from "../../src/element/types"; import { AppState } from "../../src/types"; import { arrayToMap, arrayToMapWithIndex } from "../../src/utils"; -import { orderByFractionalIndex } from "../../src/zindex"; +import { orderByFractionalIndex } from "../../src/fractionalIndex"; export type ReconciledElements = readonly ExcalidrawElement[] & { _brand: "reconciledElements"; diff --git a/src/fractionalIndex.ts b/src/fractionalIndex.ts new file mode 100644 index 0000000000..9320379944 --- /dev/null +++ b/src/fractionalIndex.ts @@ -0,0 +1,123 @@ +import { mutateElement } from "./element/mutateElement"; +import { ExcalidrawElement } from "./element/types"; +import { generateKeyBetween } from "fractional-indexing"; + +type FractionalIndex = ExcalidrawElement["fractionalIndex"]; + +const isValidFractionalIndex = ( + index: FractionalIndex, + predecessor: FractionalIndex, + successor: FractionalIndex, +) => { + if (index) { + if (!predecessor && !successor) { + return index.length > 0; + } + + if (!predecessor) { + // first element + return index < successor!; + } + + if (!successor) { + // last element + return predecessor! < index; + } + } + + return false; +}; + +const generateFractionalIndex = ( + index: FractionalIndex, + predecessor: FractionalIndex, + successor: FractionalIndex, +) => { + if (index) { + if (!predecessor && !successor) { + return index; + } + + if (!predecessor) { + // first element in the array + // insert before successor + return generateKeyBetween(null, successor); + } + + if (!successor) { + // last element in the array + // insert after predecessor + return generateKeyBetween(predecessor, null); + } + + // both predecessor and successor exist + // insert after predecessor + return generateKeyBetween(predecessor, null); + } + + 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; + }); +}; + +/** + * normalize the fractional indicies of the elements in the given array such that + * every element in the array has a fractional index smaller than its successor's + * + * note that this function is not pure, it mutates elements whose fractional indicies + * need updating + */ +export const normalizeFractionalIndicies = ( + allElements: readonly ExcalidrawElement[], +) => { + let pre = -1; + let suc = 1; + + for (const element of allElements) { + const predecessor = allElements[pre]?.fractionalIndex || null; + const successor = allElements[suc]?.fractionalIndex || null; + + if ( + !isValidFractionalIndex(element.fractionalIndex, predecessor, successor) + ) { + try { + const nextFractionalIndex = generateFractionalIndex( + element.fractionalIndex, + predecessor, + successor, + ); + + mutateElement( + element, + { + fractionalIndex: nextFractionalIndex, + }, + false, + ); + } catch (e) { + console.error("normalizing fractional index", e); + } + } + pre++; + suc++; + } + + return allElements; +}; diff --git a/src/scene/Scene.ts b/src/scene/Scene.ts index 7b5599fa8e..9399a15c3d 100644 --- a/src/scene/Scene.ts +++ b/src/scene/Scene.ts @@ -11,7 +11,7 @@ import { getSelectedElements } from "./selection"; import { AppState } from "../types"; import { Assert, SameType } from "../utility-types"; import { randomInteger } from "../random"; -import { normalizeFractionalIndexing } from "../zindex"; +import { normalizeFractionalIndicies } from "../fractionalIndex"; type ElementIdKey = InstanceType["elementId"]; type ElementKey = ExcalidrawElement | ElementIdKey; @@ -232,7 +232,7 @@ class Scene { nextElements: readonly ExcalidrawElement[], mapElementIds = true, ) { - const _nextElements = normalizeFractionalIndexing(nextElements); + const _nextElements = normalizeFractionalIndicies(nextElements); this.elements = _nextElements; const nextFrameLikes: ExcalidrawFrameLikeElement[] = []; diff --git a/src/zindex.ts b/src/zindex.ts index 4345e53917..a46c8b9a7c 100644 --- a/src/zindex.ts +++ b/src/zindex.ts @@ -1,4 +1,4 @@ -import { bumpVersion, mutateElement } from "./element/mutateElement"; +import { bumpVersion } from "./element/mutateElement"; import { isFrameLikeElement } from "./element/typeChecks"; import { ExcalidrawElement, ExcalidrawFrameLikeElement } from "./element/types"; import { getElementsInGroup } from "./groups"; @@ -6,7 +6,6 @@ import { getSelectedElements } from "./scene"; import Scene from "./scene/Scene"; import { AppState } from "./types"; import { arrayToMap, findIndex, findLastIndex } from "./utils"; -import { generateKeyBetween } from "fractional-indexing"; const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => { return element.frameId === frameId || element.id === frameId; @@ -486,124 +485,6 @@ function shiftElementsAccountingForFrames( ); } -// fractional indexing -// ----------------------------------------------------------------------------- -type FractionalIndex = ExcalidrawElement["fractionalIndex"]; - -const isValidFractionalIndex = ( - index: FractionalIndex, - predecessor: FractionalIndex, - successor: FractionalIndex, -) => { - if (index) { - if (!predecessor && !successor) { - return index.length > 0; - } - - if (!predecessor) { - // first element - return index < successor!; - } - - if (!successor) { - // last element - return predecessor! < index; - } - } - - return false; -}; - -const generateFractionalIndex = ( - index: FractionalIndex, - predecessor: FractionalIndex, - successor: FractionalIndex, -) => { - if (index) { - if (!predecessor && !successor) { - return index; - } - - 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(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; - }); -}; - -/** - * normalize the fractional indicies of the elements in the given array such that - * a. all elements have a fraction index between floor and ceiling as defined above - * b. for every element, its fractional index is greater than its predecessor's and smaller than its successor's - */ -export const normalizeFractionalIndexing = ( - allElements: readonly ExcalidrawElement[], -) => { - let pre = -1; - let suc = 1; - - for (const element of allElements) { - const predecessor = allElements[pre]?.fractionalIndex || null; - const successor = allElements[suc]?.fractionalIndex || null; - - if ( - !isValidFractionalIndex(element.fractionalIndex, predecessor, successor) - ) { - try { - const nextFractionalIndex = generateFractionalIndex( - element.fractionalIndex, - predecessor, - successor, - ); - - mutateElement( - element, - { - fractionalIndex: nextFractionalIndex, - }, - false, - ); - } catch (e) { - console.error(e); - } - } - pre++; - suc++; - } - - return allElements; -}; - // public API // -----------------------------------------------------------------------------