diff --git a/public/index.html b/public/index.html index 927afed66a..6c42b897e9 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 c9615f8a98..4477028548 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 89eb9ab0de..6af44678fc 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 0000000000..c4546eabc0 --- /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 c40ca01bce..2dfddab214 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( +