瀏覽代碼

Send to back (#98)

Christopher Chedeau 5 年之前
父節點
當前提交
0d5272720f
共有 3 個文件被更改,包括 180 次插入1 次删除
  1. 32 1
      src/index.tsx
  2. 51 0
      src/zindex.test.ts
  3. 97 0
      src/zindex.ts

+ 32 - 1
src/index.tsx

@@ -11,6 +11,8 @@ import {
   faFont
 } from "@fortawesome/free-solid-svg-icons";
 
+import { moveOneLeft, moveAllLeft } from "./zindex";
+
 import "./styles.css";
 
 type ExcalidrawElement = ReturnType<typeof newElement>;
@@ -647,6 +649,16 @@ function isArrowKey(keyCode: string) {
   );
 }
 
+function getSelectedIndices() {
+  const selectedIndices: number[] = [];
+  elements.forEach((element, index) => {
+    if (element.isSelected) {
+      selectedIndices.push(index);
+    }
+  });
+  return selectedIndices;
+}
+
 const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
 const ELEMENT_TRANSLATE_AMOUNT = 1;
 
@@ -710,7 +722,26 @@ class App extends React.Component<{}, AppState> {
       });
       this.forceUpdate();
       event.preventDefault();
-    } else if (event.key === "a" && event.metaKey) {
+
+      // Send backwards: Cmd-Shift-Alt-B
+    } else if (
+      event.metaKey &&
+      event.shiftKey &&
+      event.altKey &&
+      event.code === "KeyB"
+    ) {
+      moveOneLeft(elements, getSelectedIndices());
+      this.forceUpdate();
+      event.preventDefault();
+
+      // Send to back: Cmd-Shift-B
+    } else if (event.metaKey && event.shiftKey && event.code === "KeyB") {
+      moveAllLeft(elements, getSelectedIndices());
+      this.forceUpdate();
+      event.preventDefault();
+
+      // Select all: Cmd-A
+    } else if (event.metaKey && event.code === "KeyA") {
       elements.forEach(element => {
         element.isSelected = true;
       });

+ 51 - 0
src/zindex.test.ts

@@ -0,0 +1,51 @@
+import { moveOneLeft, moveAllLeft } from "./zindex";
+
+function expectMove(fn, elems, indices, equal) {
+  fn(elems, indices);
+  expect(elems).toEqual(equal);
+}
+
+it("should moveOneLeft", () => {
+  expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 2], ["b", "c", "a", "d"]);
+  expectMove(moveOneLeft, ["a", "b", "c", "d"], [0], ["a", "b", "c", "d"]);
+  expectMove(
+    moveOneLeft,
+    ["a", "b", "c", "d"],
+    [0, 1, 2, 3],
+    ["a", "b", "c", "d"]
+  );
+  expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 3], ["b", "a", "d", "c"]);
+});
+
+it("should moveAllLeft", () => {
+  expectMove(
+    moveAllLeft,
+    ["a", "b", "c", "d", "e", "f", "g"],
+    [2, 5],
+    ["c", "f", "a", "b", "d", "e", "g"]
+  );
+  expectMove(
+    moveAllLeft,
+    ["a", "b", "c", "d", "e", "f", "g"],
+    [5],
+    ["f", "a", "b", "c", "d", "e", "g"]
+  );
+  expectMove(
+    moveAllLeft,
+    ["a", "b", "c", "d", "e", "f", "g"],
+    [0, 1, 2, 3, 4, 5, 6],
+    ["a", "b", "c", "d", "e", "f", "g"]
+  );
+  expectMove(
+    moveAllLeft,
+    ["a", "b", "c", "d", "e", "f", "g"],
+    [0, 1, 2],
+    ["a", "b", "c", "d", "e", "f", "g"]
+  );
+  expectMove(
+    moveAllLeft,
+    ["a", "b", "c", "d", "e", "f", "g"],
+    [4, 5, 6],
+    ["e", "f", "g", "a", "b", "c", "d"]
+  );
+});

+ 97 - 0
src/zindex.ts

@@ -0,0 +1,97 @@
+function swap<T>(elements: T[], indexA: number, indexB: number) {
+  const element = elements[indexA];
+  elements[indexA] = elements[indexB];
+  elements[indexB] = element;
+}
+
+export function moveOneLeft<T>(elements: T[], indicesToMove: number[]) {
+  indicesToMove.sort((a: number, b: number) => a - b);
+  let isSorted = true;
+  // We go from left to right to avoid overriding the wrong elements
+  indicesToMove.forEach((index, i) => {
+    // We don't want to bubble the first elements that are sorted as they are
+    // already in their correct position
+    isSorted = isSorted && index === i;
+    if (isSorted) {
+      return;
+    }
+    swap(elements, index - 1, index);
+  });
+}
+
+// Let's go through an example
+//        |        |
+// [a, b, c, d, e, f, g]
+// -->
+// [c, f, a, b, d, e, g]
+//
+// We are going to override all the elements we want to move, so we keep them in an array
+// that we will restore at the end.
+// [c, f]
+//
+// From now on, we'll never read those values from the array anymore
+//        |1       |0
+// [a, b, _, d, e, _, g]
+//
+// The idea is that we want to shift all the elements between the marker 0 and 1
+// by one slot to the right.
+//
+//        |1       |0
+// [a, b, _, d, e, _, g]
+//          -> ->
+//
+// which gives us
+//
+//        |1       |0
+// [a, b, _, _, d, e, g]
+//
+// Now, we need to move all the elements from marker 1 to the beginning by two (not one)
+// slots to the right, which gives us
+//
+//        |1       |0
+// [a, b, _, _, d, e, g]
+//  ---|--^  ^
+//     ------|
+//
+// which gives us
+//
+//        |1       |0
+// [_, _, a, b, d, e, g]
+//
+// At this point, we can fill back the leftmost elements with the array we saved at
+// the beggining
+//
+//        |1       |0
+// [c, f, a, b, d, e, g]
+//
+// And we are done!
+export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
+  indicesToMove.sort((a: number, b: number) => a - b);
+
+  // Copy the elements to move
+  const leftMostElements = indicesToMove.map(index => elements[index]);
+
+  const reversedIndicesToMove = indicesToMove
+    // We go from right to left to avoid overriding elements.
+    .reverse()
+    // We add 0 for the final marker
+    .concat([0]);
+
+  reversedIndicesToMove.forEach((index, i) => {
+    // We skip the first one as it is not paired with anything else
+    if (i === 0) {
+      return;
+    }
+
+    // We go from the next marker to the right (i - 1) to the current one (index)
+    for (let pos = reversedIndicesToMove[i - 1] - 1; pos >= index; --pos) {
+      // We move by 1 the first time, 2 the second... So we can use the index i in the array
+      elements[pos + i] = elements[pos];
+    }
+  });
+
+  // The final step
+  leftMostElements.forEach((element, i) => {
+    elements[i] = element;
+  });
+}