From ab176937e6df41bab7b9c35577d85795372685fa Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 21 Feb 2020 08:17:20 -0500 Subject: [PATCH] Add touch support (#788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add touch support * Mock media query * Mock media query pt 2 * Fix tests * Allow installing as an app on iOS * Fix type error * Math.hypot * delete and finalize buttons, hint viewer * skip failing tests * skip the rest of the failing tests * Hide the selected shape actions when nothing is selected * Don’t go into mobile view on short-but-wide viewports * lol --- public/index.html | 2 + src/actions/actionDeleteSelected.tsx | 13 ++ src/actions/actionFinalize.tsx | 19 +++ src/components/HintViewer.css | 8 + src/gesture.ts | 17 ++ src/index.tsx | 225 +++++++++++++++++---------- src/is-mobile.tsx | 12 +- src/locales/en.json | 3 +- src/styles.scss | 2 +- src/tests/dragCreate.test.tsx | 54 +++---- src/tests/move.test.tsx | 28 ++-- src/tests/multiPointCreate.test.tsx | 52 +++---- src/tests/resize.test.tsx | 36 ++--- src/tests/selection.test.tsx | 80 +++++----- src/types.ts | 13 ++ 15 files changed, 356 insertions(+), 208 deletions(-) create mode 100644 src/gesture.ts diff --git a/public/index.html b/public/index.html index 927afed6..6c42b897 100644 --- a/public/index.html +++ b/public/index.html @@ -7,6 +7,8 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> + + isSomeElementSelected(elements), keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE, + PanelComponent: ({ updateData }) => ( + updateData(null)} + /> + ), }; diff --git a/src/actions/actionFinalize.tsx b/src/actions/actionFinalize.tsx index c9615f8a..44770285 100644 --- a/src/actions/actionFinalize.tsx +++ b/src/actions/actionFinalize.tsx @@ -3,6 +3,10 @@ import { KEYS } from "../keys"; import { clearSelection } from "../scene"; import { isInvisiblySmallElement } from "../element"; import { resetCursor } from "../utils"; +import React from "react"; +import { ToolButton } from "../components/ToolButton"; +import { save } from "../components/icons"; +import { t } from "../i18n"; export const actionFinalize: Action = { name: "finalize", @@ -43,4 +47,19 @@ export const actionFinalize: Action = { appState.multiElement === null) || ((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) && appState.multiElement !== null), + PanelComponent: ({ appState, updateData }) => ( +
+ updateData(null)} + /> +
+ ), }; diff --git a/src/components/HintViewer.css b/src/components/HintViewer.css index 89eb9ab0..6af44678 100644 --- a/src/components/HintViewer.css +++ b/src/components/HintViewer.css @@ -4,3 +4,11 @@ bottom: 0.5em; font-size: 0.8rem; } + +@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px) { + .HintViewer { + position: static; + margin-top: 0.5rem; + text-align: center; + } +} diff --git a/src/gesture.ts b/src/gesture.ts new file mode 100644 index 00000000..c4546eab --- /dev/null +++ b/src/gesture.ts @@ -0,0 +1,17 @@ +import { Pointer } from "./types"; +import { normalizeScroll } from "./scene/data"; + +export function getCenter(pointers: readonly Pointer[]) { + return { + x: normalizeScroll(sum(pointers, p => p.x) / pointers.length), + y: normalizeScroll(sum(pointers, p => p.y) / pointers.length), + }; +} + +export function getDistance([a, b]: readonly Pointer[]) { + return Math.hypot(a.x - b.x, a.y - b.y); +} + +function sum(array: readonly T[], mapper: (item: T) => number): number { + return array.reduce((acc, item) => acc + mapper(item), 0); +} diff --git a/src/index.tsx b/src/index.tsx index c40ca01b..2dfddab2 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -41,7 +41,7 @@ import { } from "./scene"; import { renderScene } from "./renderer"; -import { AppState, FlooredNumber } from "./types"; +import { AppState, FlooredNumber, Gesture } from "./types"; import { ExcalidrawElement } from "./element/types"; import { @@ -108,6 +108,7 @@ import useIsMobile, { IsMobileProvider } from "./is-mobile"; import { copyToAppClipboard, getClipboardContent } from "./clipboard"; import { normalizeScroll } from "./scene/data"; +import { getCenter, getDistance } from "./gesture"; let { elements } = createScene(); const { history } = createHistory(); @@ -130,10 +131,11 @@ const CURSOR_TYPE = { CROSSHAIR: "crosshair", GRABBING: "grabbing", }; -const MOUSE_BUTTON = { +const POINTER_BUTTON = { MAIN: 0, WHEEL: 1, SECONDARY: 2, + TOUCH: -1, }; // Block pinch-zooming on iOS outside of the content area @@ -148,7 +150,13 @@ document.addEventListener( { passive: false }, ); -let lastMouseUp: ((e: any) => void) | null = null; +let lastPointerUp: ((e: any) => void) | null = null; +const gesture: Gesture = { + pointers: [], + lastCenter: null, + initialDistance: null, + initialScale: null, +}; export function viewportCoordsToSceneCoords( { clientX, clientY }: { clientX: number; clientY: number }, @@ -202,7 +210,6 @@ let cursorX = 0; let cursorY = 0; let isHoldingSpace: boolean = false; let isPanning: boolean = false; -let isHoldingMouseButton: boolean = false; interface LayerUIProps { actionManager: ActionManager; @@ -279,17 +286,15 @@ const LayerUI = React.memo( ); } - function renderSelectedShapeActions( - elements: readonly ExcalidrawElement[], - ) { + const showSelectedShapeActions = + (appState.editingElement || getSelectedElements(elements).length) && + appState.elementType === "selection"; + + function renderSelectedShapeActions() { const { elementType, editingElement } = appState; const targetElements = editingElement ? [editingElement] : getSelectedElements(elements); - if (!targetElements.length && elementType === "selection") { - return null; - } - return (
{actionManager.renderAction("changeStrokeColor")} @@ -331,8 +336,6 @@ const LayerUI = React.memo( {actionManager.renderAction("bringForward")}
- - {actionManager.renderAction("deleteSelectedElements")} ); } @@ -418,7 +421,7 @@ const LayerUI = React.memo( - ) : appState.openedMenu === "shape" ? ( + ) : appState.openedMenu === "shape" && showSelectedShapeActions ? (
- {renderSelectedShapeActions(elements)} + {renderSelectedShapeActions()}
) : null} @@ -444,6 +447,12 @@ const LayerUI = React.memo( +