Browse Source

generate real fractional index after z-index actions

Ryan Di 1 year ago
parent
commit
093e684d9e
4 changed files with 98 additions and 22 deletions
  1. 2 1
      src/data/restore.ts
  2. 75 1
      src/fractionalIndex.ts
  3. 3 1
      src/scene/Scene.ts
  4. 18 19
      src/zindex.ts

+ 2 - 1
src/data/restore.ts

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

+ 75 - 1
src/fractionalIndex.ts

@@ -1,6 +1,6 @@
 import { mutateElement } from "./element/mutateElement";
 import { ExcalidrawElement } from "./element/types";
-import { generateKeyBetween } from "fractional-indexing";
+import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
 
 type FractionalIndex = ExcalidrawElement["fractionalIndex"];
 
@@ -28,6 +28,80 @@ const isValidFractionalIndex = (
   return false;
 };
 
+const getContiguousMovedIndices = (
+  elements: readonly ExcalidrawElement[],
+  movedElementsMap: Record<string, ExcalidrawElement>,
+) => {
+  const result: number[][] = [];
+  const contiguous: number[] = [];
+
+  for (let i = 0; i < elements.length; i++) {
+    const element = elements[i];
+    if (movedElementsMap[element.id]) {
+      if (contiguous.length) {
+        if (contiguous[contiguous.length - 1] + 1 === i) {
+          contiguous.push(i);
+        } else {
+          result.push(contiguous.slice());
+          contiguous.length = 0;
+          contiguous.push(i);
+        }
+      } else {
+        contiguous.push(i);
+      }
+    }
+  }
+
+  if (contiguous.length > 0) {
+    result.push(contiguous.slice());
+  }
+
+  return result;
+};
+
+export const fixFractionalIndices = (
+  elements: readonly ExcalidrawElement[],
+  movedElementsMap: Record<string, ExcalidrawElement>,
+) => {
+  const fixedElements = elements.slice();
+  const contiguousMovedIndices = getContiguousMovedIndices(
+    fixedElements,
+    movedElementsMap,
+  );
+
+  for (const movedIndices of contiguousMovedIndices) {
+    try {
+      const predecessor =
+        fixedElements[movedIndices[0] - 1]?.fractionalIndex || null;
+      const successor =
+        fixedElements[movedIndices[movedIndices.length - 1] + 1]
+          ?.fractionalIndex || null;
+
+      const newKeys = generateNKeysBetween(
+        predecessor,
+        successor,
+        movedIndices.length,
+      );
+
+      for (let i = 0; i < movedIndices.length; i++) {
+        const element = fixedElements[movedIndices[i]];
+
+        mutateElement(
+          element,
+          {
+            fractionalIndex: newKeys[i],
+          },
+          false,
+        );
+      }
+    } catch (e) {
+      console.error("error generating fractional indices", e);
+    }
+  }
+
+  return fixedElements;
+};
+
 const generateFractionalIndex = (
   index: FractionalIndex,
   predecessor: FractionalIndex,

+ 3 - 1
src/scene/Scene.ts

@@ -328,7 +328,9 @@ class Scene {
     if (element.frameId) {
       this.insertElementAtIndex(element, this.getElementIndex(element.frameId));
     } else {
-      this.replaceAllElements([...this.elements, element]);
+      this.replaceAllElements(
+        normalizeFractionalIndicies([...this.elements, element]),
+      );
     }
   };
 

+ 18 - 19
src/zindex.ts

@@ -1,6 +1,7 @@
 import { bumpVersion } from "./element/mutateElement";
 import { isFrameLikeElement } from "./element/typeChecks";
 import { ExcalidrawElement, ExcalidrawFrameLikeElement } from "./element/types";
+import { fixFractionalIndices } from "./fractionalIndex";
 import { getElementsInGroup } from "./groups";
 import { getSelectedElements } from "./scene";
 import Scene from "./scene/Scene";
@@ -312,12 +313,7 @@ const shiftElementsByOne = (
           ];
   });
 
-  return elements.map((element) => {
-    if (targetElementsMap[element.id]) {
-      return bumpVersion(element);
-    }
-    return element;
-  });
+  return fixFractionalIndices(elements, targetElementsMap);
 };
 
 const shiftElementsToEnd = (
@@ -390,19 +386,22 @@ const shiftElementsToEnd = (
   const leadingElements = elements.slice(0, leadingIndex);
   const trailingElements = elements.slice(trailingIndex + 1);
 
-  return direction === "left"
-    ? [
-        ...leadingElements,
-        ...targetElements,
-        ...displacedElements,
-        ...trailingElements,
-      ]
-    : [
-        ...leadingElements,
-        ...displacedElements,
-        ...targetElements,
-        ...trailingElements,
-      ];
+  return fixFractionalIndices(
+    direction === "left"
+      ? [
+          ...leadingElements,
+          ...targetElements,
+          ...displacedElements,
+          ...trailingElements,
+        ]
+      : [
+          ...leadingElements,
+          ...displacedElements,
+          ...targetElements,
+          ...trailingElements,
+        ],
+    targetElementsMap,
+  );
 };
 
 function shiftElementsAccountingForFrames(