From f3ef93e9ce8707851bca6d9c06f00bdb430c935b Mon Sep 17 00:00:00 2001 From: Tom Dohnal Date: Sat, 11 Apr 2020 13:37:43 +0200 Subject: [PATCH] Allow to drag THEN press alt to duplicate (#1373) * fix typo * duplicate elements when alt is pressed on pointer move * document use case Co-authored-by: dwelle --- src/components/App.tsx | 59 +++++++++++-------- src/index.tsx | 6 +- src/tests/__snapshots__/move.test.tsx.snap | 16 ++--- .../regressionTests.test.tsx.snap | 24 ++++---- src/tests/move.test.tsx | 13 ++-- 5 files changed, 68 insertions(+), 50 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 4c3be550..78e930ce 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1818,29 +1818,6 @@ export class App extends React.Component { ); hitElementWasAddedToSelection = true; } - - // We duplicate the selected element if alt is pressed on pointer down - if (event.altKey) { - // Move the currently selected elements to the top of the z index stack, and - // put the duplicates where the selected elements used to be. - const nextElements = []; - const elementsToAppend = []; - for (const element of globalSceneState.getElementsIncludingDeleted()) { - if ( - this.state.selectedElementIds[element.id] || - (element.id === hitElement.id && hitElementWasAddedToSelection) - ) { - nextElements.push(duplicateElement(element)); - elementsToAppend.push(element); - } else { - nextElements.push(element); - } - } - globalSceneState.replaceAllElements([ - ...nextElements, - ...elementsToAppend, - ]); - } } } } else { @@ -1990,6 +1967,8 @@ export class App extends React.Component { resizeArrowFn = fn; }; + let selectedElementWasDuplicated = false; + const onPointerMove = withBatchedUpdates((event: PointerEvent) => { const target = event.target; if (!(target instanceof HTMLElement)) { @@ -2082,6 +2061,40 @@ export class App extends React.Component { }); lastX = x; lastY = y; + + // We duplicate the selected element if alt is pressed on pointer move + if (event.altKey && !selectedElementWasDuplicated) { + // Move the currently selected elements to the top of the z index stack, and + // put the duplicates where the selected elements used to be. + // (the origin point where the dragging started) + + selectedElementWasDuplicated = true; + + const nextElements = []; + const elementsToAppend = []; + for (const element of globalSceneState.getElementsIncludingDeleted()) { + if ( + this.state.selectedElementIds[element.id] || + // case: the state.selectedElementIds might not have been + // updated yet by the time this mousemove event is fired + (element.id === hitElement.id && hitElementWasAddedToSelection) + ) { + const duplicatedElement = duplicateElement(element); + mutateElement(duplicatedElement, { + x: duplicatedElement.x + (originX - lastX), + y: duplicatedElement.y + (originY - lastY), + }); + nextElements.push(duplicatedElement); + elementsToAppend.push(element); + } else { + nextElements.push(element); + } + } + globalSceneState.replaceAllElements([ + ...nextElements, + ...elementsToAppend, + ]); + } return; } } diff --git a/src/index.tsx b/src/index.tsx index 85ce4120..c743432d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,12 +7,12 @@ import { IsMobileProvider } from "./is-mobile"; import { App } from "./components/App"; import "./styles.scss"; -const SentyEnvHostnameMap: { [key: string]: string } = { +const SentryEnvHostnameMap: { [key: string]: string } = { "excalidraw.com": "production", "now.sh": "staging", }; -const onlineEnv = Object.keys(SentyEnvHostnameMap).find( +const onlineEnv = Object.keys(SentryEnvHostnameMap).find( (item) => window.location.hostname.indexOf(item) >= 0, ); @@ -21,7 +21,7 @@ Sentry.init({ dsn: onlineEnv ? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260" : undefined, - environment: onlineEnv ? SentyEnvHostnameMap[onlineEnv] : undefined, + environment: onlineEnv ? SentryEnvHostnameMap[onlineEnv] : undefined, release: process.env.REACT_APP_GIT_SHA, integrations: [ new SentryIntegrations.CaptureConsole({ diff --git a/src/tests/__snapshots__/move.test.tsx.snap b/src/tests/__snapshots__/move.test.tsx.snap index d2b14fcf..1b2f319b 100644 --- a/src/tests/__snapshots__/move.test.tsx.snap +++ b/src/tests/__snapshots__/move.test.tsx.snap @@ -6,16 +6,16 @@ Object { "backgroundColor": "transparent", "fillStyle": "hachure", "height": 50, - "id": "id1", + "id": "id2", "isDeleted": false, "opacity": 100, "roughness": 1, - "seed": 453191, + "seed": 2019559783, "strokeColor": "#000000", "strokeWidth": 1, "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, + "version": 4, + "versionNonce": 1150084233, "width": 30, "x": 30, "y": 20, @@ -36,11 +36,11 @@ Object { "strokeColor": "#000000", "strokeWidth": 1, "type": "rectangle", - "version": 3, - "versionNonce": 2019559783, + "version": 5, + "versionNonce": 1014066025, "width": 30, - "x": 0, - "y": 40, + "x": -10, + "y": 60, } `; diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 79d1127c..5d85183a 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -34,7 +34,7 @@ Object { "scrolledOutside": false, "selectedElementIds": Object { "id0": true, - "id2": true, + "id1": true, }, "selectionElement": null, "shouldCacheIgnoreZoom": false, @@ -51,16 +51,16 @@ Object { "backgroundColor": "transparent", "fillStyle": "hachure", "height": 10, - "id": "id1", + "id": "id2", "isDeleted": false, "opacity": 100, "roughness": 1, - "seed": 453191, + "seed": 2019559783, "strokeColor": "#000000", "strokeWidth": 1, "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, + "version": 4, + "versionNonce": 1150084233, "width": 10, "x": 10, "y": 10, @@ -82,7 +82,7 @@ Object { "strokeWidth": 1, "type": "rectangle", "version": 3, - "versionNonce": 2019559783, + "versionNonce": 401146281, "width": 10, "x": 20, "y": 20, @@ -147,7 +147,7 @@ Object { "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, - "id2": true, + "id1": true, }, "viewBackgroundColor": "#ffffff", }, @@ -157,16 +157,16 @@ Object { "backgroundColor": "transparent", "fillStyle": "hachure", "height": 10, - "id": "id1", + "id": "id2", "isDeleted": false, "opacity": 100, "roughness": 1, - "seed": 453191, + "seed": 2019559783, "strokeColor": "#000000", "strokeWidth": 1, "type": "rectangle", - "version": 3, - "versionNonce": 1278240551, + "version": 5, + "versionNonce": 1150084233, "width": 10, "x": 10, "y": 10, @@ -185,7 +185,7 @@ Object { "strokeWidth": 1, "type": "rectangle", "version": 4, - "versionNonce": 2019559783, + "versionNonce": 401146281, "width": 10, "x": 20, "y": 20, diff --git a/src/tests/move.test.tsx b/src/tests/move.test.tsx index 9b36211d..586e8d8f 100644 --- a/src/tests/move.test.tsx +++ b/src/tests/move.test.tsx @@ -74,17 +74,22 @@ describe("duplicate element on move when ALT is clicked", () => { renderScene.mockClear(); } - fireEvent.pointerDown(canvas, { clientX: 50, clientY: 20, altKey: true }); - fireEvent.pointerMove(canvas, { clientX: 20, clientY: 40 }); + fireEvent.pointerDown(canvas, { clientX: 50, clientY: 20 }); + fireEvent.pointerMove(canvas, { clientX: 20, clientY: 40, altKey: true }); + + // firing another pointerMove event with alt key pressed should NOT trigger + // another duplication + fireEvent.pointerMove(canvas, { clientX: 20, clientY: 40, altKey: true }); + fireEvent.pointerMove(canvas, { clientX: 10, clientY: 60 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(3); + expect(renderScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(2); // previous element should stay intact expect([h.elements[0].x, h.elements[0].y]).toEqual([30, 20]); - expect([h.elements[1].x, h.elements[1].y]).toEqual([0, 40]); + expect([h.elements[1].x, h.elements[1].y]).toEqual([-10, 60]); h.elements.forEach((element) => expect(element).toMatchSnapshot()); });