More mobile tweaks (#790)

* Disable text selection

* Set content-editable=plaintext-only to disable Touch Bar formatting buttons

* Enlarge resize handle tap targets for pen/touch

* Make the lock button a button in mobile mode

* Use icons instead of Unicode characters; add an alternate toolbar for creating multipoint lines

* Allow buttons to hide themselves

* Fix heuristic for showing shape actions

* Refactor icons

* Fix label for edit button

* Switch edit button icon

* Remove lock button on mobile

* Add language selector on mobile

* Fix showing edit button on mobile

* Fix showing edit button on mobile, part 2

* Fix handle touch regions

* Fix scroll-back button position

* Allow using the text tool on a text object to start editing it

* Fix deletion of last point in line
This commit is contained in:
Jed Fox 2020-02-21 14:34:18 -05:00 committed by GitHub
parent 949c3841ea
commit 0fd3fb4b5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 272 additions and 222 deletions

View file

@ -1,15 +1,26 @@
import { ExcalidrawElement } from "./types";
import { ExcalidrawElement, PointerType } from "./types";
import { getElementAbsoluteCoords } from "./bounds";
type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se";
export function handlerRectangles(element: ExcalidrawElement, zoom: number) {
const handlerWidth = 8 / zoom;
const handlerHeight = 8 / zoom;
const handleSizes: { [k in PointerType]: number } = {
mouse: 8,
pen: 16,
touch: 28,
};
const handlerMarginX = 8 / zoom;
const handlerMarginY = 8 / zoom;
export function handlerRectangles(
element: ExcalidrawElement,
zoom: number,
pointerType: PointerType = "mouse",
) {
const size = handleSizes[pointerType];
const handlerWidth = size / zoom;
const handlerHeight = size / zoom;
const handlerMarginX = size / zoom;
const handlerMarginY = size / zoom;
const [elementX1, elementY1, elementX2, elementY2] = getElementAbsoluteCoords(
element,
@ -20,59 +31,61 @@ export function handlerRectangles(element: ExcalidrawElement, zoom: number) {
const dashedLineMargin = 4 / zoom;
const centeringOffset = (size - 8) / (2 * zoom);
const handlers = {
nw: [
elementX1 - dashedLineMargin - handlerMarginX,
elementY1 - dashedLineMargin - handlerMarginY,
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
handlerWidth,
handlerHeight,
],
ne: [
elementX2 + dashedLineMargin,
elementY1 - dashedLineMargin - handlerMarginY,
elementX2 + dashedLineMargin - centeringOffset,
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
handlerWidth,
handlerHeight,
],
sw: [
elementX1 - dashedLineMargin - handlerMarginX,
elementY2 + dashedLineMargin,
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
elementY2 + dashedLineMargin - centeringOffset,
handlerWidth,
handlerHeight,
],
se: [
elementX2 + dashedLineMargin,
elementY2 + dashedLineMargin,
elementX2 + dashedLineMargin - centeringOffset,
elementY2 + dashedLineMargin - centeringOffset,
handlerWidth,
handlerHeight,
],
} as { [T in Sides]: number[] };
// We only want to show height handlers (all cardinal directions) above a certain size
const minimumSizeForEightHandlers = 40 / zoom;
const minimumSizeForEightHandlers = (5 * size) / zoom;
if (Math.abs(elementWidth) > minimumSizeForEightHandlers) {
handlers["n"] = [
elementX1 + elementWidth / 2,
elementY1 - dashedLineMargin - handlerMarginY,
elementX1 + elementWidth / 2 - handlerWidth / 2,
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
handlerWidth,
handlerHeight,
];
handlers["s"] = [
elementX1 + elementWidth / 2,
elementY2 + dashedLineMargin,
elementX1 + elementWidth / 2 - handlerWidth / 2,
elementY2 + dashedLineMargin - centeringOffset,
handlerWidth,
handlerHeight,
];
}
if (Math.abs(elementHeight) > minimumSizeForEightHandlers) {
handlers["w"] = [
elementX1 - dashedLineMargin - handlerMarginX,
elementY1 + elementHeight / 2,
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
elementY1 + elementHeight / 2 - handlerHeight / 2,
handlerWidth,
handlerHeight,
];
handlers["e"] = [
elementX2 + dashedLineMargin,
elementY1 + elementHeight / 2,
elementX2 + dashedLineMargin - centeringOffset,
elementY1 + elementHeight / 2 - handlerHeight / 2,
handlerWidth,
handlerHeight,
];

View file

@ -1,4 +1,4 @@
import { ExcalidrawElement } from "./types";
import { ExcalidrawElement, PointerType } from "./types";
import { handlerRectangles } from "./handlerRectangles";
@ -9,12 +9,13 @@ export function resizeTest(
x: number,
y: number,
zoom: number,
pointerType: PointerType,
): HandlerRectanglesRet | false {
if (!element.isSelected || element.type === "text") {
return false;
}
const handlers = handlerRectangles(element, zoom);
const handlers = handlerRectangles(element, zoom, pointerType);
const filter = Object.keys(handlers).filter(key => {
const handler = handlers[key as HandlerRectanglesRet]!;
@ -41,12 +42,13 @@ export function getElementWithResizeHandler(
elements: readonly ExcalidrawElement[],
{ x, y }: { x: number; y: number },
zoom: number,
pointerType: PointerType,
) {
return elements.reduce((result, element) => {
if (result) {
return result;
}
const resizeHandle = resizeTest(element, x, y, zoom);
const resizeHandle = resizeTest(element, x, y, zoom, pointerType);
return resizeHandle ? { element, resizeHandle } : null;
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
}

View file

@ -34,7 +34,7 @@ export function textWysiwyg({
// But this solution has an issue — it allows to paste
// multiline text, which is not currently supported
const editable = document.createElement("div");
editable.contentEditable = "true";
editable.contentEditable = "plaintext-only";
editable.tabIndex = 0;
editable.innerText = initText;
editable.dataset.type = "wysiwyg";

View file

@ -9,3 +9,5 @@ export type ExcalidrawTextElement = ExcalidrawElement & {
actualBoundingBoxAscent?: number;
baseline: number;
};
export type PointerType = "mouse" | "pen" | "touch";