瀏覽代碼

Fix keypress rebinding (#2102)

Co-authored-by: Anton <anton.matrenin@introduct.tech>
Anton 4 年之前
父節點
當前提交
1828a93ba7
共有 3 個文件被更改,包括 189 次插入20 次删除
  1. 32 20
      src/components/App.tsx
  2. 103 0
      src/tests/__snapshots__/move.test.tsx.snap
  3. 54 0
      src/tests/move.test.tsx

+ 32 - 20
src/components/App.tsx

@@ -135,7 +135,7 @@ import {
 import LayerUI from "./LayerUI";
 import { ScrollBars, SceneState } from "../scene/types";
 import { generateCollaborationLink, getCollaborationLinkData } from "../data";
-import { mutateElement, newElementWith } from "../element/mutateElement";
+import { mutateElement } from "../element/mutateElement";
 import { invalidateShapeForElement } from "../renderer/renderElement";
 import { unstable_batchedUpdates } from "react-dom";
 import {
@@ -173,6 +173,7 @@ import {
   fixBindingsAfterDeletion,
   isLinearElementSimpleAndAlreadyBound,
   isBindingEnabled,
+  updateBoundElements,
 } from "../element/binding";
 import { MaybeTransformHandleType } from "../element/transformHandles";
 
@@ -608,7 +609,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     this.setState({});
   });
 
-  private onHashChange = (event: HashChangeEvent) => {
+  private onHashChange = (_: HashChangeEvent) => {
     if (window.location.hash.length > 1) {
       this.initializeScene();
     }
@@ -1486,24 +1487,35 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         (event.shiftKey
           ? ELEMENT_SHIFT_TRANSLATE_AMOUNT
           : ELEMENT_TRANSLATE_AMOUNT);
-      this.scene.replaceAllElements(
-        this.scene.getElementsIncludingDeleted().map((el) => {
-          if (this.state.selectedElementIds[el.id]) {
-            const update: { x?: number; y?: number } = {};
-            if (event.key === KEYS.ARROW_LEFT) {
-              update.x = el.x - step;
-            } else if (event.key === KEYS.ARROW_RIGHT) {
-              update.x = el.x + step;
-            } else if (event.key === KEYS.ARROW_UP) {
-              update.y = el.y - step;
-            } else if (event.key === KEYS.ARROW_DOWN) {
-              update.y = el.y + step;
-            }
-            return newElementWith(el, update);
-          }
-          return el;
-        }),
-      );
+
+      const selectedElements = this.scene
+        .getElements()
+        .filter((element) => this.state.selectedElementIds[element.id]);
+
+      let offsetX = 0;
+      let offsetY = 0;
+
+      if (event.key === KEYS.ARROW_LEFT) {
+        offsetX = -step;
+      } else if (event.key === KEYS.ARROW_RIGHT) {
+        offsetX = step;
+      } else if (event.key === KEYS.ARROW_UP) {
+        offsetY = -step;
+      } else if (event.key === KEYS.ARROW_DOWN) {
+        offsetY = step;
+      }
+
+      selectedElements.forEach((element) => {
+        mutateElement(element, {
+          x: element.x + offsetX,
+          y: element.y + offsetY,
+        });
+
+        updateBoundElements(element, {
+          simultaneouslyUpdated: selectedElements,
+        });
+      });
+
       event.preventDefault();
     } else if (event.key === KEYS.ENTER) {
       const selectedElements = getSelectedElements(

+ 103 - 0
src/tests/__snapshots__/move.test.tsx.snap

@@ -77,3 +77,106 @@ Object {
   "y": 40,
 }
 `;
+
+exports[`move element rectangles with binding arrow 1`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": Array [
+    "id2",
+  ],
+  "fillStyle": "hachure",
+  "groupIds": Array [],
+  "height": 100,
+  "id": "id0",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 337897,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 3,
+  "versionNonce": 1014066025,
+  "width": 100,
+  "x": 0,
+  "y": 0,
+}
+`;
+
+exports[`move element rectangles with binding arrow 2`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": Array [
+    "id2",
+  ],
+  "fillStyle": "hachure",
+  "groupIds": Array [],
+  "height": 300,
+  "id": "id1",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 449462985,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 6,
+  "versionNonce": 1723083209,
+  "width": 300,
+  "x": 201,
+  "y": 2,
+}
+`;
+
+exports[`move element rectangles with binding arrow 3`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": null,
+  "endBinding": Object {
+    "elementId": "id1",
+    "focus": -0.46666666666666673,
+    "gap": 10,
+  },
+  "fillStyle": "hachure",
+  "groupIds": Array [],
+  "height": 81.48231043525051,
+  "id": "id2",
+  "isDeleted": false,
+  "lastCommittedPoint": null,
+  "opacity": 100,
+  "points": Array [
+    Array [
+      0,
+      0,
+    ],
+    Array [
+      81,
+      81.48231043525051,
+    ],
+  ],
+  "roughness": 1,
+  "seed": 401146281,
+  "startBinding": Object {
+    "elementId": "id0",
+    "focus": -0.6000000000000001,
+    "gap": 10,
+  },
+  "strokeColor": "#000000",
+  "strokeSharpness": "round",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "line",
+  "version": 11,
+  "versionNonce": 1006504105,
+  "width": 81,
+  "x": 110,
+  "y": 49.981789081137734,
+}
+`;

+ 54 - 0
src/tests/move.test.tsx

@@ -4,6 +4,13 @@ import { render, fireEvent } from "./test-utils";
 import App from "../components/App";
 import * as Renderer from "../renderer/renderScene";
 import { reseed } from "../random";
+import { bindOrUnbindLinearElement } from "../element/binding";
+import {
+  ExcalidrawLinearElement,
+  NonDeleted,
+  ExcalidrawRectangleElement,
+} from "../element/types";
+import { UI, Pointer, Keyboard } from "./helpers/ui";
 
 // Unmount ReactDOM from root
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -50,6 +57,53 @@ describe("move element", () => {
 
     h.elements.forEach((element) => expect(element).toMatchSnapshot());
   });
+
+  it("rectangles with binding arrow", () => {
+    render(<App />);
+
+    // create elements
+    const rectA = UI.createElement("rectangle", { size: 100 });
+    const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
+    const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
+
+    // bind line to two rectangles
+    bindOrUnbindLinearElement(
+      line as NonDeleted<ExcalidrawLinearElement>,
+      rectA as ExcalidrawRectangleElement,
+      rectB as ExcalidrawRectangleElement,
+    );
+
+    // select the second rectangles
+    new Pointer("mouse").clickOn(rectB);
+
+    expect(renderScene).toHaveBeenCalledTimes(19);
+    expect(h.state.selectionElement).toBeNull();
+    expect(h.elements.length).toEqual(3);
+    expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
+    expect([rectA.x, rectA.y]).toEqual([0, 0]);
+    expect([rectB.x, rectB.y]).toEqual([200, 0]);
+    expect([line.x, line.y]).toEqual([110, 50]);
+    expect([line.width, line.height]).toEqual([80, 80]);
+
+    renderScene.mockClear();
+
+    // Move selected rectangle
+    Keyboard.keyDown("ArrowRight");
+    Keyboard.keyDown("ArrowDown");
+    Keyboard.keyDown("ArrowDown");
+
+    // Check that the arrow size has been changed according to moving the rectangle
+    expect(renderScene).toHaveBeenCalledTimes(3);
+    expect(h.state.selectionElement).toBeNull();
+    expect(h.elements.length).toEqual(3);
+    expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
+    expect([rectA.x, rectA.y]).toEqual([0, 0]);
+    expect([rectB.x, rectB.y]).toEqual([201, 2]);
+    expect([Math.round(line.x), Math.round(line.y)]).toEqual([110, 50]);
+    expect([Math.round(line.width), Math.round(line.height)]).toEqual([81, 81]);
+
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
+  });
 });
 
 describe("duplicate element on move when ALT is clicked", () => {