Kaynağa Gözat

indices with jitter

Ryan Di 1 yıl önce
ebeveyn
işleme
bf53d90c68
5 değiştirilmiş dosya ile 87 ekleme ve 41 silme
  1. 1 1
      package.json
  2. 2 2
      src/data/restore.ts
  3. 70 33
      src/fractionalIndex.ts
  4. 10 1
      src/scene/Scene.ts
  5. 4 4
      yarn.lock

+ 1 - 1
package.json

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

+ 2 - 2
src/data/restore.ts

@@ -43,7 +43,7 @@ import {
   measureBaseline,
 } from "../element/textElement";
 import { normalizeLink } from "./url";
-import { normalizeFractionalIndicies } from "../fractionalIndex";
+import { restoreFractionalIndicies } from "../fractionalIndex";
 
 type RestoredAppState = Omit<
   AppState,
@@ -461,7 +461,7 @@ export const restoreElements = (
     }
   }
 
-  return normalizeFractionalIndicies(restoredElements) as ExcalidrawElement[];
+  return restoreFractionalIndicies(restoredElements) as ExcalidrawElement[];
 };
 
 const coalesceAppStateValue = <

+ 70 - 33
src/fractionalIndex.ts

@@ -1,6 +1,9 @@
 import { mutateElement } from "./element/mutateElement";
 import { ExcalidrawElement } from "./element/types";
-import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
+import {
+  generateKeyBetween,
+  generateNJitteredKeysBetween,
+} from "fractional-indexing-jittered";
 
 type FractionalIndex = ExcalidrawElement["fractionalIndex"];
 
@@ -10,18 +13,22 @@ const isValidFractionalIndex = (
   successor: FractionalIndex,
 ) => {
   if (index) {
-    if (!predecessor && !successor) {
-      return index.length > 0;
+    if (predecessor && successor) {
+      return predecessor < index && index < successor;
     }
 
-    if (!predecessor) {
+    if (successor && !predecessor) {
       // first element
-      return index < successor!;
+      return index < successor;
     }
 
-    if (!successor) {
+    if (predecessor && !successor) {
       // last element
-      return predecessor! < index;
+      return predecessor < index;
+    }
+
+    if (!predecessor && !successor) {
+      return index.length > 0;
     }
   }
 
@@ -89,7 +96,7 @@ export const fixFractionalIndices = (
         elements[movedIndices[movedIndices.length - 1] + 1]?.fractionalIndex ||
         null;
 
-      const newKeys = generateNKeysBetween(
+      const newKeys = generateNJitteredKeysBetween(
         predecessor,
         successor,
         movedIndices.length,
@@ -114,7 +121,26 @@ export const fixFractionalIndices = (
   return elements as ExcalidrawElement[];
 };
 
-const generateFractionalIndex = (
+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;
+  });
+};
+
+const restoreFractionalIndex = (
   index: FractionalIndex,
   predecessor: FractionalIndex,
   successor: FractionalIndex,
@@ -124,13 +150,13 @@ const generateFractionalIndex = (
       return index;
     }
 
-    if (!predecessor) {
+    if (successor && !predecessor) {
       // first element in the array
       // insert before successor
       return generateKeyBetween(null, successor);
     }
 
-    if (!successor) {
+    if (predecessor && !successor) {
       // last element in the array
       // insert after predecessor
       return generateKeyBetween(predecessor, null);
@@ -144,25 +170,6 @@ const generateFractionalIndex = (
   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
@@ -170,7 +177,7 @@ export const orderByFractionalIndex = (allElements: ExcalidrawElement[]) => {
  * note that this function is not pure, it mutates elements whose fractional indicies
  * need updating
  */
-export const normalizeFractionalIndicies = (
+export const restoreFractionalIndicies = (
   allElements: readonly ExcalidrawElement[],
 ) => {
   let pre = -1;
@@ -186,7 +193,7 @@ export const normalizeFractionalIndicies = (
       !isValidFractionalIndex(element.fractionalIndex, predecessor, successor)
     ) {
       try {
-        const nextFractionalIndex = generateFractionalIndex(
+        const nextFractionalIndex = restoreFractionalIndex(
           element.fractionalIndex,
           predecessor,
           successor,
@@ -197,7 +204,6 @@ export const normalizeFractionalIndicies = (
           fractionalIndex: nextFractionalIndex,
         });
       } catch (e) {
-        console.error("normalizing fractional index", e);
         normalized.push(element);
       }
     } else {
@@ -209,3 +215,34 @@ export const normalizeFractionalIndicies = (
 
   return normalized;
 };
+
+export const validateFractionalIndicies = (
+  elements: readonly ExcalidrawElement[],
+) => {
+  for (let i = 0; i < elements.length; i++) {
+    const element = elements[i];
+    const successor = elements[i + 1];
+
+    if (successor) {
+      if (element.fractionalIndex && successor.fractionalIndex) {
+        if (element.fractionalIndex >= successor.fractionalIndex) {
+          console.log(
+            "this is the case",
+            element.fractionalIndex,
+            successor.fractionalIndex,
+          );
+          return false;
+        }
+      } else {
+        console.log(
+          "this is the other case",
+          element.fractionalIndex,
+          successor.fractionalIndex,
+        );
+        return false;
+      }
+    }
+  }
+
+  return true;
+};

+ 10 - 1
src/scene/Scene.ts

@@ -11,7 +11,10 @@ import { getSelectedElements } from "./selection";
 import { AppState } from "../types";
 import { Assert, SameType } from "../utility-types";
 import { randomInteger } from "../random";
-import { fixFractionalIndices } from "../fractionalIndex";
+import {
+  fixFractionalIndices,
+  validateFractionalIndicies,
+} from "../fractionalIndex";
 import { arrayToMap } from "../utils";
 
 type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
@@ -240,6 +243,12 @@ class Scene {
       _nextElements = nextElements;
     }
 
+    if (import.meta.env.DEV) {
+      if (!validateFractionalIndicies(_nextElements)) {
+        console.error("fractional indices consistency has been compromised");
+      }
+    }
+
     this.elements = _nextElements;
     const nextFrameLikes: ExcalidrawFrameLikeElement[] = [];
     this.elementsMap.clear();

+ 4 - 4
yarn.lock

@@ -4891,10 +4891,10 @@ form-data@^4.0.0:
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
-fractional-indexing@3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
-  integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
+fractional-indexing[email protected].0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/fractional-indexing-jittered/-/fractional-indexing-jittered-0.9.0.tgz#53a5f05acc4a8f8ceb5cb5a326122deb2cf8105c"
+  integrity sha512-3XIGQbmuEIg1j/qdHLDVyCH1vNMUyeBhM+5d6Su03fTiHzmQLE5nOqvDXLDgg240lLBIsLyJa3xZIUqO57yrAQ==
 
 fs-extra@^11.1.0:
   version "11.1.1"