diff --git a/src/components/App.tsx b/src/components/App.tsx index 75ccbb8b..0d3c9a7e 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -95,6 +95,7 @@ import { import { normalizeScroll } from "../scene"; import { getCenter, getDistance } from "../gesture"; import { createUndoAction, createRedoAction } from "../actions/actionHistory"; + import { CURSOR_TYPE, ELEMENT_SHIFT_TRANSLATE_AMOUNT, @@ -103,7 +104,16 @@ import { DRAGGING_THRESHOLD, TEXT_TO_CENTER_SNAP_THRESHOLD, LINE_CONFIRM_THRESHOLD, + SCENE, + EVENT, + ENV, } from "../constants"; +import { + FONT_LOAD_THRESHOLD, + INITAL_SCENE_UPDATE_TIMEOUT, + TAP_TWICE_TIMEOUT, +} from "../time_constants"; + import { LayerUI } from "./LayerUI"; import { ScrollBars, SceneState } from "../scene/types"; import { generateCollaborationLink, getCollaborationLinkData } from "../data"; @@ -363,7 +373,7 @@ export class App extends React.Component { }); }), // if fonts don't load in 1s for whatever reason, don't block the UI - new Promise((resolve) => setTimeout(resolve, 1000)), + new Promise((resolve) => setTimeout(resolve, FONT_LOAD_THRESHOLD)), ]); } catch (error) { console.error(error); @@ -411,32 +421,39 @@ export class App extends React.Component { this.onSceneUpdated, ); - document.addEventListener("copy", this.onCopy); - document.addEventListener("paste", this.pasteFromClipboard); - document.addEventListener("cut", this.onCut); + document.addEventListener(EVENT.COPY, this.onCopy); + document.addEventListener(EVENT.PASTE, this.pasteFromClipboard); + document.addEventListener(EVENT.CUT, this.onCut); - document.addEventListener("keydown", this.onKeyDown, false); - document.addEventListener("keyup", this.onKeyUp, { passive: true }); - document.addEventListener("mousemove", this.updateCurrentCursorPosition); - window.addEventListener("resize", this.onResize, false); - window.addEventListener("unload", this.onUnload, false); - window.addEventListener("blur", this.onBlur, false); - window.addEventListener("dragover", this.disableEvent, false); - window.addEventListener("drop", this.disableEvent, false); + document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false); + document.addEventListener(EVENT.KEYUP, this.onKeyUp, { passive: true }); + document.addEventListener( + EVENT.MOUSE_MOVE, + this.updateCurrentCursorPosition, + ); + window.addEventListener(EVENT.RESIZE, this.onResize, false); + window.addEventListener(EVENT.UNLOAD, this.onUnload, false); + window.addEventListener(EVENT.BLUR, this.onBlur, false); + window.addEventListener(EVENT.DRAG_OVER, this.disableEvent, false); + window.addEventListener(EVENT.DROP, this.disableEvent, false); // Safari-only desktop pinch zoom document.addEventListener( - "gesturestart", + EVENT.GESTURE_START, this.onGestureStart as any, false, ); document.addEventListener( - "gesturechange", + EVENT.GESTURE_CHANGE, this.onGestureChange as any, false, ); - document.addEventListener("gestureend", this.onGestureEnd as any, false); - window.addEventListener("beforeunload", this.beforeUnload); + document.addEventListener( + EVENT.GESTURE_END, + this.onGestureEnd as any, + false, + ); + window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload); this.initializeScene(); } @@ -445,35 +462,39 @@ export class App extends React.Component { this.unmounted = true; this.removeSceneCallback!(); - document.removeEventListener("copy", this.onCopy); - document.removeEventListener("paste", this.pasteFromClipboard); - document.removeEventListener("cut", this.onCut); + document.removeEventListener(EVENT.COPY, this.onCopy); + document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard); + document.removeEventListener(EVENT.CUT, this.onCut); - document.removeEventListener("keydown", this.onKeyDown, false); + document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false); document.removeEventListener( - "mousemove", + EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition, false, ); - document.removeEventListener("keyup", this.onKeyUp); - window.removeEventListener("resize", this.onResize, false); - window.removeEventListener("unload", this.onUnload, false); - window.removeEventListener("blur", this.onBlur, false); - window.removeEventListener("dragover", this.disableEvent, false); - window.removeEventListener("drop", this.disableEvent, false); + document.removeEventListener(EVENT.KEYUP, this.onKeyUp); + window.removeEventListener(EVENT.RESIZE, this.onResize, false); + window.removeEventListener(EVENT.UNLOAD, this.onUnload, false); + window.removeEventListener(EVENT.BLUR, this.onBlur, false); + window.removeEventListener(EVENT.DRAG_OVER, this.disableEvent, false); + window.removeEventListener(EVENT.DROP, this.disableEvent, false); document.removeEventListener( - "gesturestart", + EVENT.GESTURE_START, this.onGestureStart as any, false, ); document.removeEventListener( - "gesturechange", + EVENT.GESTURE_CHANGE, this.onGestureChange as any, false, ); - document.removeEventListener("gestureend", this.onGestureEnd as any, false); - window.removeEventListener("beforeunload", this.beforeUnload); + document.removeEventListener( + EVENT.GESTURE_END, + this.onGestureEnd as any, + false, + ); + window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload); } private onResize = withBatchedUpdates(() => { globalSceneState @@ -574,7 +595,7 @@ export class App extends React.Component { getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) > this.lastBroadcastedOrReceivedSceneVersion ) { - this.broadcastScene("SCENE_UPDATE"); + this.broadcastScene(SCENE.UPDATE); } history.record(this.state, globalSceneState.getElementsIncludingDeleted()); @@ -638,11 +659,18 @@ export class App extends React.Component { ); }; + private resetTapTwice() { + didTapTwice = false; + } + private onTapStart = (event: TouchEvent) => { if (!didTapTwice) { didTapTwice = true; clearTimeout(tappedTwiceTimer); - tappedTwiceTimer = window.setTimeout(() => (didTapTwice = false), 300); + tappedTwiceTimer = window.setTimeout( + this.resetTapTwice, + TAP_TWICE_TIMEOUT, + ); return; } // insert text only if we tapped twice with a single finger @@ -809,10 +837,13 @@ export class App extends React.Component { }; // fallback in case you're not alone in the room but still don't receive // initial SCENE_UPDATE message - const initializationTimer = setTimeout(initialize, 5000); + const initializationTimer = setTimeout( + initialize, + INITAL_SCENE_UPDATE_TIMEOUT, + ); const updateScene = ( - decryptedData: SocketUpdateDataSource["SCENE_INIT" | "SCENE_UPDATE"], + decryptedData: SocketUpdateDataSource[SCENE.INIT | SCENE.UPDATE], { scrollToContent = false }: { scrollToContent?: boolean } = {}, ) => { const { elements: remoteElements } = decryptedData.payload; @@ -821,7 +852,7 @@ export class App extends React.Component { this.setState({ ...this.state, ...calculateScrollCenter( - remoteElements.filter((element) => { + remoteElements.filter((element: { isDeleted: boolean }) => { return !element.isDeleted; }), ), @@ -946,13 +977,13 @@ export class App extends React.Component { switch (decryptedData.type) { case "INVALID_RESPONSE": return; - case "SCENE_INIT": { + case SCENE.INIT: { if (!this.portal.socketInitialized) { updateScene(decryptedData, { scrollToContent: true }); } break; } - case "SCENE_UPDATE": + case SCENE.UPDATE: updateScene(decryptedData); break; case "MOUSE_LOCATION": { @@ -1003,7 +1034,7 @@ export class App extends React.Component { }); }); this.portal.socket!.on("new-user", async (_socketID: string) => { - this.broadcastScene("SCENE_INIT"); + this.broadcastScene(SCENE.INIT); }); this.setState({ @@ -1035,7 +1066,7 @@ export class App extends React.Component { } }; - private broadcastScene = (sceneType: "SCENE_INIT" | "SCENE_UPDATE") => { + private broadcastScene = (sceneType: SCENE.INIT | SCENE.UPDATE) => { const data: SocketUpdateDataSource[typeof sceneType] = { type: sceneType, payload: { @@ -1651,16 +1682,16 @@ export class App extends React.Component { cursorButton: "up", }); this.savePointer(event.clientX, event.clientY, "up"); - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", teardown); - window.removeEventListener("blur", teardown); + window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove); + window.removeEventListener(EVENT.POINTER_UP, teardown); + window.removeEventListener(EVENT.BLUR, teardown); }), ); - window.addEventListener("blur", teardown); - window.addEventListener("pointermove", onPointerMove, { + window.addEventListener(EVENT.BLUR, teardown); + window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, { passive: true, }); - window.addEventListener("pointerup", teardown); + window.addEventListener(EVENT.POINTER_UP, teardown); return; } @@ -1755,14 +1786,14 @@ export class App extends React.Component { cursorButton: "up", }); this.savePointer(event.clientX, event.clientY, "up"); - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", onPointerUp); + window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove); + window.removeEventListener(EVENT.POINTER_UP, onPointerUp); }); lastPointerUp = onPointerUp; - window.addEventListener("pointermove", onPointerMove); - window.addEventListener("pointerup", onPointerUp); + window.addEventListener(EVENT.POINTER_MOVE, onPointerMove); + window.addEventListener(EVENT.POINTER_UP, onPointerUp); return; } @@ -2230,8 +2261,8 @@ export class App extends React.Component { resizeArrowFn = null; lastPointerUp = null; - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", onPointerUp); + window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove); + window.removeEventListener(EVENT.POINTER_UP, onPointerUp); if (isLinearElement(draggingElement)) { if (draggingElement!.points.length > 1) { @@ -2367,8 +2398,8 @@ export class App extends React.Component { lastPointerUp = onPointerUp; - window.addEventListener("pointermove", onPointerMove); - window.addEventListener("pointerup", onPointerUp); + window.addEventListener(EVENT.POINTER_MOVE, onPointerMove); + window.addEventListener(EVENT.POINTER_UP, onPointerUp); }; private handleCanvasRef = (canvas: HTMLCanvasElement) => { @@ -2377,13 +2408,13 @@ export class App extends React.Component { this.canvas = canvas; this.rc = rough.canvas(this.canvas); - this.canvas.addEventListener("wheel", this.handleWheel, { + this.canvas.addEventListener(EVENT.WHEEL, this.handleWheel, { passive: false, }); - this.canvas.addEventListener("touchstart", this.onTapStart); + this.canvas.addEventListener(EVENT.TOUCH_START, this.onTapStart); } else { - this.canvas?.removeEventListener("wheel", this.handleWheel); - this.canvas?.removeEventListener("touchstart", this.onTapStart); + this.canvas?.removeEventListener(EVENT.WHEEL, this.handleWheel); + this.canvas?.removeEventListener(EVENT.TOUCH_START, this.onTapStart); } }; @@ -2610,7 +2641,10 @@ declare global { } } -if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") { +if ( + process.env.NODE_ENV === ENV.TEST || + process.env.NODE_ENV === ENV.DEVELOPMENT +) { window.h = {} as Window["h"]; Object.defineProperties(window.h, { diff --git a/src/constants.ts b/src/constants.ts index 52e64768..ec248c12 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,3 +16,35 @@ export const POINTER_BUTTON = { SECONDARY: 2, TOUCH: -1, }; + +export enum SCENE { + INIT = "SCENE_INIT", + UPDATE = "SCENE_UPDATE", +} + +export enum EVENT { + COPY = "copy", + PASTE = "paste", + CUT = "cut", + KEYDOWN = "keydown", + KEYUP = "keyup", + MOUSE_MOVE = "mousemove", + RESIZE = "resize", + UNLOAD = "unload", + BLUR = "blur", + DRAG_OVER = "dragover", + DROP = "drop", + GESTURE_END = "gestureend", + BEFORE_UNLOAD = "beforeunload", + GESTURE_START = "gesturestart", + GESTURE_CHANGE = "gesturechange", + POINTER_MOVE = "pointermove", + POINTER_UP = "pointerup", + WHEEL = "wheel", + TOUCH_START = "touchstart", +} + +export const ENV = { + TEST: "test", + DEVELOPMENT: "development", +}; diff --git a/src/time_constants.ts b/src/time_constants.ts new file mode 100644 index 00000000..786f03ba --- /dev/null +++ b/src/time_constants.ts @@ -0,0 +1,4 @@ +// time in milliseconds +export const FONT_LOAD_THRESHOLD = 1000; +export const TAP_TWICE_TIMEOUT = 300; +export const INITAL_SCENE_UPDATE_TIMEOUT = 5000;