Browse Source

use string as fractional index value

Ryan Di 1 year ago
parent
commit
00ffa08e28

+ 1 - 0
package.json

@@ -37,6 +37,7 @@
     "eslint-plugin-react": "7.32.2",
     "eslint-plugin-react": "7.32.2",
     "fake-indexeddb": "3.1.7",
     "fake-indexeddb": "3.1.7",
     "firebase": "8.3.3",
     "firebase": "8.3.3",
+    "fractional-indexing": "3.2.0",
     "i18next-browser-languagedetector": "6.1.4",
     "i18next-browser-languagedetector": "6.1.4",
     "idb-keyval": "6.0.3",
     "idb-keyval": "6.0.3",
     "image-blob-reduce": "3.0.1",
     "image-blob-reduce": "3.0.1",

+ 1 - 1
src/data/restore.ts

@@ -120,7 +120,7 @@ const restoreElementWithProperties = <
     version: element.version || 1,
     version: element.version || 1,
     versionNonce: element.versionNonce ?? 0,
     versionNonce: element.versionNonce ?? 0,
     // TODO: think about this more
     // TODO: think about this more
-    fractionalIndex: element.fractionalIndex ?? Infinity,
+    fractionalIndex: element.fractionalIndex ?? null,
     isDeleted: element.isDeleted ?? false,
     isDeleted: element.isDeleted ?? false,
     id: element.id || randomId(),
     id: element.id || randomId(),
     fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
     fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,

+ 1 - 1
src/element/newElement.ts

@@ -90,7 +90,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
     groupIds = [],
     groupIds = [],
     frameId = null,
     frameId = null,
     // TODO: think about this more
     // TODO: think about this more
-    fractionalIndex = Infinity,
+    fractionalIndex = null,
     roundness = null,
     roundness = null,
     boundElements = null,
     boundElements = null,
     link = null,
     link = null,

+ 1 - 1
src/element/types.ts

@@ -50,7 +50,7 @@ type _ExcalidrawElementBase = Readonly<{
       Used for deterministic reconciliation of updates during collaboration,
       Used for deterministic reconciliation of updates during collaboration,
       in case the versions (see above) are identical. */
       in case the versions (see above) are identical. */
   versionNonce: number;
   versionNonce: number;
-  fractionalIndex: number;
+  fractionalIndex: string | null;
   isDeleted: boolean;
   isDeleted: boolean;
   /** List of groups the element belongs to.
   /** List of groups the element belongs to.
       Ordered from deepest to shallowest. */
       Ordered from deepest to shallowest. */

+ 1 - 1
src/tests/fixtures/elementFixture.ts

@@ -17,7 +17,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
   groupIds: [],
   groupIds: [],
   frameId: null,
   frameId: null,
   roundness: null,
   roundness: null,
-  fractionalIndex: Infinity,
+  fractionalIndex: "",
   seed: 1041657908,
   seed: 1041657908,
   version: 120,
   version: 120,
   versionNonce: 1188004276,
   versionNonce: 1188004276,

+ 1 - 1
src/tests/helpers/api.ts

@@ -168,7 +168,7 @@ export class API {
       x,
       x,
       y,
       y,
       frameId: rest.frameId ?? null,
       frameId: rest.frameId ?? null,
-      fractionalIndex: rest.fractionalIndex ?? Infinity,
+      fractionalIndex: rest.fractionalIndex ?? null,
       angle: rest.angle ?? 0,
       angle: rest.angle ?? 0,
       strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
       strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
       backgroundColor:
       backgroundColor:

+ 86 - 65
src/zindex.ts

@@ -6,6 +6,7 @@ import { getSelectedElements } from "./scene";
 import Scene from "./scene/Scene";
 import Scene from "./scene/Scene";
 import { AppState } from "./types";
 import { AppState } from "./types";
 import { arrayToMap, findIndex, findLastIndex } from "./utils";
 import { arrayToMap, findIndex, findLastIndex } from "./utils";
+import { generateKeyBetween } from "fractional-indexing";
 
 
 const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
 const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
   return element.frameId === frameId || element.id === frameId;
   return element.frameId === frameId || element.id === frameId;
@@ -487,63 +488,76 @@ function shiftElementsAccountingForFrames(
 
 
 // fractional indexing
 // fractional indexing
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
-const FRACTIONAL_INDEX_FLOOR = 0;
-const FRACTIONAL_INDEX_CEILING = 1;
+type FractionalIndex = ExcalidrawElement["fractionalIndex"];
 
 
-const isFractionalIndexInValidRange = (index: number) => {
-  return index > FRACTIONAL_INDEX_FLOOR && index < FRACTIONAL_INDEX_CEILING;
-};
+const fractionalIndexCompare = {
+  isSmallerThan(indexA: string, indexB: string) {
+    return indexA < indexB;
+  },
 
 
-const getFractionalIndex = (
-  element: ExcalidrawElement | undefined,
-  fallbackValue: number,
-) => {
-  return element && isFractionalIndexInValidRange(element.fractionalIndex)
-    ? element.fractionalIndex
-    : fallbackValue;
+  isGreaterThan(indexA: string, indexB: string) {
+    return indexA > indexB;
+  },
 };
 };
 
 
 const isValidFractionalIndex = (
 const isValidFractionalIndex = (
-  index: number,
-  predecessorElement: ExcalidrawElement | undefined,
-  successorElement: ExcalidrawElement | undefined,
+  index: FractionalIndex,
+  predecessorFractionalIndex: FractionalIndex,
+  successorFractionalIndex: FractionalIndex,
 ) => {
 ) => {
-  return (
-    isFractionalIndexInValidRange(index) &&
-    index > getFractionalIndex(predecessorElement, FRACTIONAL_INDEX_FLOOR) &&
-    index < getFractionalIndex(successorElement, FRACTIONAL_INDEX_CEILING)
-  );
-};
+  if (index) {
+    if (predecessorFractionalIndex) {
+      if (successorFractionalIndex) {
+        return (
+          fractionalIndexCompare.isGreaterThan(
+            index,
+            predecessorFractionalIndex,
+          ) &&
+          fractionalIndexCompare.isSmallerThan(index, successorFractionalIndex)
+        );
+      }
+      return fractionalIndexCompare.isGreaterThan(
+        index,
+        predecessorFractionalIndex,
+      );
+    }
 
 
-const randomNumInBetween = (start: number, end: number) => {
-  return Math.random() * (end - start) + start;
-};
+    if (successorFractionalIndex) {
+      return fractionalIndexCompare.isSmallerThan(
+        index,
+        successorFractionalIndex,
+      );
+    }
 
 
-export const generateFractionalIndex = (
-  predecessorElement: ExcalidrawElement | undefined,
-  successorElement: ExcalidrawElement | undefined,
-) => {
-  const start = getFractionalIndex(predecessorElement, FRACTIONAL_INDEX_FLOOR);
-  const end = getFractionalIndex(successorElement, FRACTIONAL_INDEX_CEILING);
+    return index.length > 0;
+  }
 
 
-  const nextTemp = randomNumInBetween(start, end);
-  return (
-    (randomNumInBetween(nextTemp, end) + randomNumInBetween(start, nextTemp)) /
-    2
-  );
+  return false;
 };
 };
 
 
-/**
- *
- */
-export const getNextFractionalIndexAt = (
-  index: number,
-  allElements: ExcalidrawElement[],
+const generateFractionalIndex = (
+  predecessorFractionalIndex: string | null,
+  successorFractionalIndex: string | null,
 ) => {
 ) => {
-  const predecessor = allElements[index - 1];
-  const successor = allElements[index + 1];
+  if (predecessorFractionalIndex && successorFractionalIndex) {
+    if (predecessorFractionalIndex < successorFractionalIndex) {
+      return generateKeyBetween(
+        predecessorFractionalIndex,
+        successorFractionalIndex,
+      );
+    } else if (predecessorFractionalIndex > successorFractionalIndex) {
+      return generateKeyBetween(
+        successorFractionalIndex,
+        predecessorFractionalIndex,
+      );
+    }
+    return generateKeyBetween(predecessorFractionalIndex, null);
+  }
 
 
-  return generateFractionalIndex(predecessor, successor);
+  return generateKeyBetween(
+    predecessorFractionalIndex,
+    successorFractionalIndex,
+  );
 };
 };
 
 
 /**
 /**
@@ -557,37 +571,44 @@ export const normalizeFractionalIndexing = (
   let predecessor = -1;
   let predecessor = -1;
   let successor = 1;
   let successor = 1;
 
 
-  const normalizedElements: ExcalidrawElement[] = [];
+  const normalizedElementsMap = arrayToMap(allElements);
 
 
   for (const element of allElements) {
   for (const element of allElements) {
-    const predecessorElement = allElements[predecessor];
-    const successorElement = allElements[successor];
+    const predecessorFractionalIndex =
+      normalizedElementsMap.get(allElements[predecessor]?.id)
+        ?.fractionalIndex || null;
 
 
-    if (
-      !isValidFractionalIndex(
-        element.fractionalIndex,
-        predecessorElement,
-        successorElement,
-      )
-    ) {
-      const nextFractionalIndex = generateFractionalIndex(
-        predecessorElement,
-        successorElement,
-      );
+    const successorFractionalIndex =
+      normalizedElementsMap.get(allElements[successor]?.id)?.fractionalIndex ||
+      null;
 
 
-      normalizedElements.push({
-        ...element,
-        fractionalIndex: nextFractionalIndex,
-      });
-    } else {
-      normalizedElements.push(element);
+    try {
+      if (
+        !isValidFractionalIndex(
+          element.fractionalIndex,
+          predecessorFractionalIndex,
+          successorFractionalIndex,
+        )
+      ) {
+        const nextFractionalIndex = generateFractionalIndex(
+          predecessorFractionalIndex,
+          successorFractionalIndex,
+        );
+
+        normalizedElementsMap.set(element.id, {
+          ...element,
+          fractionalIndex: nextFractionalIndex,
+        });
+      }
+    } catch (e) {
+      console.error(e);
     }
     }
 
 
     predecessor++;
     predecessor++;
     successor++;
     successor++;
   }
   }
 
 
-  return normalizedElements;
+  return [...normalizedElementsMap.values()];
 };
 };
 
 
 // public API
 // public API

+ 5 - 0
yarn.lock

@@ -4891,6 +4891,11 @@ form-data@^4.0.0:
     combined-stream "^1.0.8"
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
     mime-types "^2.1.12"
 
 
[email protected]:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
+  integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
+
 fs-extra@^11.1.0:
 fs-extra@^11.1.0:
   version "11.1.1"
   version "11.1.1"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"