Przeglądaj źródła

fix: paste styles shortcut (#4886)

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
David Luzar 3 lat temu
rodzic
commit
20de06ef50

+ 5 - 3
src/actions/actionDistribute.tsx

@@ -7,7 +7,7 @@ import { distributeElements, Distribution } from "../disitrubte";
 import { getNonDeletedElements } from "../element";
 import { ExcalidrawElement } from "../element/types";
 import { t } from "../i18n";
-import { CODES } from "../keys";
+import { CODES, KEYS } from "../keys";
 import { getSelectedElements, isSomeElementSelected } from "../scene";
 import { AppState } from "../types";
 import { arrayToMap, getShortcutKey } from "../utils";
@@ -49,7 +49,8 @@ export const distributeHorizontally = register({
       commitToHistory: true,
     };
   },
-  keyTest: (event) => event.altKey && event.code === CODES.H,
+  keyTest: (event) =>
+    !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.H,
   PanelComponent: ({ elements, appState, updateData }) => (
     <ToolButton
       hidden={!enableActionGroup(elements, appState)}
@@ -77,7 +78,8 @@ export const distributeVertically = register({
       commitToHistory: true,
     };
   },
-  keyTest: (event) => event.altKey && event.code === CODES.V,
+  keyTest: (event) =>
+    !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
   PanelComponent: ({ elements, appState, updateData }) => (
     <ToolButton
       hidden={!enableActionGroup(elements, appState)}

+ 71 - 0
src/actions/actionStyles.test.tsx

@@ -0,0 +1,71 @@
+import ExcalidrawApp from "../excalidraw-app";
+import { t } from "../i18n";
+import { CODES } from "../keys";
+import { API } from "../tests/helpers/api";
+import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
+import { fireEvent, render, screen } from "../tests/test-utils";
+import { copiedStyles } from "./actionStyles";
+
+const { h } = window;
+
+const mouse = new Pointer("mouse");
+
+describe("actionStyles", () => {
+  beforeEach(async () => {
+    await render(<ExcalidrawApp />);
+  });
+  it("should copy & paste styles via keyboard", () => {
+    UI.clickTool("rectangle");
+    mouse.down(10, 10);
+    mouse.up(20, 20);
+
+    UI.clickTool("rectangle");
+    mouse.down(10, 10);
+    mouse.up(20, 20);
+
+    // Change some styles of second rectangle
+    UI.clickLabeledElement("Stroke");
+    UI.clickLabeledElement(t("colors.c92a2a"));
+    UI.clickLabeledElement("Background");
+    UI.clickLabeledElement(t("colors.e64980"));
+    // Fill style
+    fireEvent.click(screen.getByTitle("Cross-hatch"));
+    // Stroke width
+    fireEvent.click(screen.getByTitle("Bold"));
+    // Stroke style
+    fireEvent.click(screen.getByTitle("Dotted"));
+    // Roughness
+    fireEvent.click(screen.getByTitle("Cartoonist"));
+    // Opacity
+    fireEvent.change(screen.getByLabelText("Opacity"), {
+      target: { value: "60" },
+    });
+
+    mouse.reset();
+
+    API.setSelectedElements([h.elements[1]]);
+
+    Keyboard.withModifierKeys({ ctrl: true, alt: true }, () => {
+      Keyboard.codeDown(CODES.C);
+    });
+    const secondRect = JSON.parse(copiedStyles);
+    expect(secondRect.id).toBe(h.elements[1].id);
+
+    mouse.reset();
+    // Paste styles to first rectangle
+    API.setSelectedElements([h.elements[0]]);
+    Keyboard.withModifierKeys({ ctrl: true, alt: true }, () => {
+      Keyboard.codeDown(CODES.V);
+    });
+
+    const firstRect = API.getSelectedElement();
+    expect(firstRect.id).toBe(h.elements[0].id);
+    expect(firstRect.strokeColor).toBe("#c92a2a");
+    expect(firstRect.backgroundColor).toBe("#e64980");
+    expect(firstRect.fillStyle).toBe("cross-hatch");
+    expect(firstRect.strokeWidth).toBe(2); // Bold: 2
+    expect(firstRect.strokeStyle).toBe("dotted");
+    expect(firstRect.roughness).toBe(2); // Cartoonist: 2
+    expect(firstRect.opacity).toBe(60);
+  });
+});

+ 3 - 0
src/actions/manager.tsx

@@ -91,6 +91,9 @@ export class ActionManager implements ActionsManagerInterface {
       );
 
     if (data.length !== 1) {
+      if (data.length > 1) {
+        console.warn("Canceling as multiple actions match this shortcut", data);
+      }
       return false;
     }
 

+ 4 - 12
src/tests/contextmenu.test.tsx

@@ -40,14 +40,6 @@ const queryContextMenu = () => {
   return GlobalTestState.renderResult.container.querySelector(".context-menu");
 };
 
-const clickLabeledElement = (label: string) => {
-  const element = document.querySelector(`[aria-label='${label}']`);
-  if (!element) {
-    throw new Error(`No labeled element found: ${label}`);
-  }
-  fireEvent.click(element);
-};
-
 // Unmount ReactDOM from root
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
 
@@ -312,10 +304,10 @@ describe("contextMenu element", () => {
     mouse.up(20, 20);
 
     // Change some styles of second rectangle
-    clickLabeledElement("Stroke");
-    clickLabeledElement(t("colors.c92a2a"));
-    clickLabeledElement("Background");
-    clickLabeledElement(t("colors.e64980"));
+    UI.clickLabeledElement("Stroke");
+    UI.clickLabeledElement(t("colors.c92a2a"));
+    UI.clickLabeledElement("Background");
+    UI.clickLabeledElement(t("colors.e64980"));
     // Fill style
     fireEvent.click(screen.getByTitle("Cross-hatch"));
     // Stroke width

+ 8 - 0
src/tests/helpers/ui.ts

@@ -221,6 +221,14 @@ export class UI {
     fireEvent.click(GlobalTestState.renderResult.getByToolName(toolName));
   };
 
+  static clickLabeledElement = (label: string) => {
+    const element = document.querySelector(`[aria-label='${label}']`);
+    if (!element) {
+      throw new Error(`No labeled element found: ${label}`);
+    }
+    fireEvent.click(element);
+  };
+
   /**
    * Creates an Excalidraw element, and returns a proxy that wraps it so that
    * accessing props will return the latest ones from the object existing in

+ 8 - 16
src/tests/regressionTests.test.tsx

@@ -26,14 +26,6 @@ const mouse = new Pointer("mouse");
 const finger1 = new Pointer("touch", 1);
 const finger2 = new Pointer("touch", 2);
 
-const clickLabeledElement = (label: string) => {
-  const element = document.querySelector(`[aria-label='${label}']`);
-  if (!element) {
-    throw new Error(`No labeled element found: ${label}`);
-  }
-  fireEvent.click(element);
-};
-
 /**
  * This is always called at the end of your test, so usually you don't need to call it.
  * However, if you have a long test, you might want to call it during the test so it's easier
@@ -168,10 +160,10 @@ describe("regression tests", () => {
     mouse.down(10, 10);
     mouse.up(10, 10);
 
-    clickLabeledElement("Background");
-    clickLabeledElement(t("colors.fa5252"));
-    clickLabeledElement("Stroke");
-    clickLabeledElement(t("colors.5f3dc4"));
+    UI.clickLabeledElement("Background");
+    UI.clickLabeledElement(t("colors.fa5252"));
+    UI.clickLabeledElement("Stroke");
+    UI.clickLabeledElement(t("colors.5f3dc4"));
     expect(API.getSelectedElement().backgroundColor).toBe("#fa5252");
     expect(API.getSelectedElement().strokeColor).toBe("#5f3dc4");
   });
@@ -952,8 +944,8 @@ describe("regression tests", () => {
       UI.clickTool("rectangle");
       // change background color since default is transparent
       // and transparent elements can't be selected by clicking inside of them
-      clickLabeledElement("Background");
-      clickLabeledElement(t("colors.fa5252"));
+      UI.clickLabeledElement("Background");
+      UI.clickLabeledElement(t("colors.fa5252"));
       mouse.down();
       mouse.up(1000, 1000);
 
@@ -1059,8 +1051,8 @@ describe("regression tests", () => {
     mouse.up(10, 10);
     expect(screen.queryByText(/fill/i)).toBeNull();
 
-    clickLabeledElement("Background");
-    clickLabeledElement(t("colors.fa5252"));
+    UI.clickLabeledElement("Background");
+    UI.clickLabeledElement(t("colors.fa5252"));
     // select rectangle
     mouse.reset();
     mouse.click();