Loading packages/client/src/components/CursorPresenceLayer.tsx +14 −22 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import reactionWork from "../assets/reactions/work.png"; import reactionYay from "../assets/reactions/yay.png"; import { useAppContext } from "../contexts/AppContext"; import { CanvasCore } from "../lib/canvas"; import { KeybindManager, REACTION_KEYBINDS } from "../lib/keybinds"; import Network from "../lib/network"; import { colorToCss, Loading Loading @@ -698,14 +699,6 @@ export const CursorPresenceLayer = () => { remoteRenderer.clear(); }; const isTypingTarget = (target: EventTarget | null) => target instanceof HTMLElement && Boolean( target.closest( 'input, textarea, select, [contenteditable="true"], [role="textbox"]', ), ); const showReactionCooldown = (cooldown: CursorReactionCooldown) => { if (cooldown.until <= Date.now()) return; reactionBlockedUntil = Math.max(reactionBlockedUntil, cooldown.until); Loading @@ -728,17 +721,11 @@ export const CursorPresenceLayer = () => { }, REACTION_DURATION + 50); }; const handleKeyDown = (event: KeyboardEvent) => { const sendReaction = (reaction: number) => { if ( !reactionsEnabledRef.current || !localActive.current || !socket.connected || event.repeat || event.altKey || event.ctrlKey || event.metaKey || isTypingTarget(event.target) || !/^[0-9]$/.test(event.key) !socket.connected ) { return; } Loading @@ -749,18 +736,15 @@ export const CursorPresenceLayer = () => { showReactionCooldown(reactionBlockedCooldown); } toast.error(REACTION_COOLDOWN_MESSAGE); event.preventDefault(); return; } reactionBlockedCooldown = undefined; if (now - lastReactionAt < REACTION_COOLDOWN_DURATION) { toast.error(REACTION_COOLDOWN_MESSAGE); event.preventDefault(); return; } lastReactionAt = now; const reaction = event.key === "0" ? 9 : Number(event.key) - 1; socket.emit("cursorReaction", reaction, (result) => { if (!mounted) return; if (!result.success) { Loading @@ -776,8 +760,15 @@ export const CursorPresenceLayer = () => { ).matches; appendReaction(localElement.current, reaction, reduceMotion); }); event.preventDefault(); }; const reactionKeybindHandlers = REACTION_KEYBINDS.map( (keybind, reaction) => { const handle = () => sendReaction(reaction); KeybindManager.on(keybind, handle); return [keybind, handle] as const; }, ); const handleResize = () => { remoteRenderer.resize(); scheduleViewport(); Loading @@ -792,7 +783,6 @@ export const CursorPresenceLayer = () => { canvas.on("surfacePointer", handleSurfacePointer); PanZoom.on("viewportMove", handleViewportMove); PanZoom.on("initialize", handleInitialize); document.addEventListener("keydown", handleKeyDown); window.addEventListener("blur", hideLocalCursor); window.addEventListener("resize", handleResize); Loading Loading @@ -829,7 +819,9 @@ export const CursorPresenceLayer = () => { canvas.off("surfacePointer", handleSurfacePointer); PanZoom.off("viewportMove", handleViewportMove); PanZoom.off("initialize", handleInitialize); document.removeEventListener("keydown", handleKeyDown); for (const [keybind, handle] of reactionKeybindHandlers) { KeybindManager.off(keybind, handle); } window.removeEventListener("blur", hideLocalCursor); window.removeEventListener("resize", handleResize); localActive.current = false; Loading packages/client/src/lib/keybinds.ts +98 −226 Original line number Diff line number Diff line Loading @@ -23,219 +23,105 @@ interface EmittedKeybind { export const enforceObjectType: EnforceObjectType<IKeybind[]> = (v) => v; const KEYBINDS = enforceObjectType({ PAN_UP: [{ key: "KeyW" }, { key: "ArrowUp" }], PAN_DOWN: [{ key: "KeyS" }, { key: "ArrowDown" }], PAN_LEFT: [{ key: "KeyA" }, { key: "ArrowLeft" }], PAN_RIGHT: [{ key: "KeyD" }, { key: "ArrowRight" }], ZOOM_OUT: [ { key: "KeyQ", ctrl: false, alt: false, meta: false, shift: false, }, { key: "Minus", ctrl: false, export const REACTION_KEYBINDS = [ "REACTION_LOVE", "REACTION_JAM", "REACTION_CUTE", "REACTION_POG", "REACTION_YAY", "REACTION_BONGO", "REACTION_SHOCKED", "REACTION_WORK", "REACTION_CRY", "REACTION_AWE", ] as const; const unmodifiedKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: false, }, ], ZOOM_IN: [ { key: "KeyE", ctrl: false, alt: false, meta: false, shift: false, }, { key: "Equal", ctrl: false, alt: false, meta: false, shift: false, }, ], ZOOM_OUT_SMALL: [ { key: "KeyQ", ctrl: false, alt: false, meta: false, shift: true, }, { key: "Minus", ctrl: false, alt: false, meta: false, shift: true, }, ], ZOOM_IN_SMALL: [ { key: "KeyE", ctrl: false, })); const shiftKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: true, }, { key: "Equal", ctrl: false, alt: false, meta: false, shift: true, }, ], PIXEL_WHOIS: [ { key: "LCLICK", shift: true, }, { key: "LONG_PRESS" }, ], TEMPLATE_MOVE: [ { key: "LCLICK", alt: true, }, ], TOGGLE_TEMPLATE: [ { key: "KeyT", }, ], TOGGLE_BLANK: [ { key: "KeyV", // legacy pxls keybind }, { key: "KeyB", }, ], TOGGLE_HEATMAP: [ { key: "KeyH", }, ], TOGGLE_GRID: [ { key: "KeyG", }, ], TOGGLE_UI: [ { key: "KeyU", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_SETTINGS: [ { key: "KeyO", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_INFO: [ { key: "KeyI", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_MOD_MENU: [ { key: "KeyM", }, ], DESELECT_COLOR: [ { key: "Escape", }, ], SWAP_COLORS: [ { key: "KeyX", ctrl: false, alt: false, meta: false, shift: false, }, ], PICK_COLOR: [ { key: "KeyC", ctrl: false, })); const ctrlKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: false, }, ], PICK_CANVAS_COLOR: [ { key: "MCLICK", }, ], MOD_SELECT: [ { key: "LCLICK", ctrl: true, }, ], CAPTURE_CANVAS: [ { key: "KeyP", ctrl: false, alt: false, meta: false, shift: false, }, ], CAPTURE_VIEW: [ { key: "KeyL", })); const altKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: true, ctrl: false, alt: false, meta: false, shift: false, }, ], CAPTURE_REGION: [ { key: "KeyL", ctrl: false, alt: false, meta: false, shift: true, }, ], CAPTURE_TEMPLATE: [ { key: "KeyP", ctrl: false, alt: false, meta: false, shift: true, }, ], KEYBINDS: [ { key: "Slash", shift: true, }, })); const isTypingTarget = (target: EventTarget | null) => target instanceof HTMLElement && Boolean( target.closest( 'input, textarea, select, [contenteditable="true"], [role="textbox"]', ), ); const KEYBINDS = enforceObjectType({ PAN_UP: unmodifiedKeybind("KeyW", "ArrowUp"), PAN_DOWN: unmodifiedKeybind("KeyS", "ArrowDown"), PAN_LEFT: unmodifiedKeybind("KeyA", "ArrowLeft"), PAN_RIGHT: unmodifiedKeybind("KeyD", "ArrowRight"), ZOOM_OUT: unmodifiedKeybind("KeyQ", "Minus"), ZOOM_IN: unmodifiedKeybind("KeyE", "Equal"), ZOOM_OUT_SMALL: shiftKeybind("KeyQ", "Minus"), ZOOM_IN_SMALL: shiftKeybind("KeyE", "Equal"), PIXEL_WHOIS: [ ...shiftKeybind("LCLICK"), ...unmodifiedKeybind("LONG_PRESS"), ], TEMPLATE_MOVE: altKeybind("LCLICK"), TOGGLE_TEMPLATE: unmodifiedKeybind("KeyT"), TOGGLE_BLANK: unmodifiedKeybind("KeyV", "KeyB"), TOGGLE_HEATMAP: unmodifiedKeybind("KeyH"), TOGGLE_GRID: unmodifiedKeybind("KeyG"), TOGGLE_UI: unmodifiedKeybind("KeyU"), TOGGLE_SETTINGS: unmodifiedKeybind("KeyO"), TOGGLE_INFO: unmodifiedKeybind("KeyI"), TOGGLE_MOD_MENU: unmodifiedKeybind("KeyM"), DESELECT_COLOR: unmodifiedKeybind("Escape"), SWAP_COLORS: unmodifiedKeybind("KeyX"), PICK_COLOR: unmodifiedKeybind("KeyC"), PICK_CANVAS_COLOR: unmodifiedKeybind("MCLICK"), MOD_SELECT: ctrlKeybind("LCLICK"), CAPTURE_CANVAS: unmodifiedKeybind("KeyP"), CAPTURE_VIEW: unmodifiedKeybind("KeyL"), CAPTURE_REGION: shiftKeybind("KeyL"), CAPTURE_TEMPLATE: shiftKeybind("KeyP"), REACTION_LOVE: unmodifiedKeybind("Digit1", "Numpad1"), REACTION_JAM: unmodifiedKeybind("Digit2", "Numpad2"), REACTION_CUTE: unmodifiedKeybind("Digit3", "Numpad3"), REACTION_POG: unmodifiedKeybind("Digit4", "Numpad4"), REACTION_YAY: unmodifiedKeybind("Digit5", "Numpad5"), REACTION_BONGO: unmodifiedKeybind("Digit6", "Numpad6"), REACTION_SHOCKED: unmodifiedKeybind("Digit7", "Numpad7"), REACTION_WORK: unmodifiedKeybind("Digit8", "Numpad8"), REACTION_CRY: unmodifiedKeybind("Digit9", "Numpad9"), REACTION_AWE: unmodifiedKeybind("Digit0", "Numpad0"), KEYBINDS: shiftKeybind("Slash"), }); class KeybindManager_ extends EventEmitter<{ Loading Loading @@ -266,13 +152,7 @@ class KeybindManager_ extends EventEmitter<{ handleKeydown = (e: KeyboardEvent) => { if (!this.enabled) return; const blacklistedElements = ["INPUT"]; if (e.target instanceof HTMLElement) { if (blacklistedElements.indexOf(e.target.tagName) > -1) { return; } } if (isTypingTarget(e.target)) return; if (e.key === "Alt") e.preventDefault(); if (e.key === "Control") e.preventDefault(); Loading @@ -282,15 +162,7 @@ class KeybindManager_ extends EventEmitter<{ handleKeyup = (e: KeyboardEvent) => { if (!this.enabled) return; // discard if in an input element const blacklistedElements = ["INPUT"]; if (e.target instanceof HTMLElement) { if (blacklistedElements.indexOf(e.target.tagName) > -1) { return; } } if (isTypingTarget(e.target)) return; const isHandled = this.handleInteraction( { Loading Loading
packages/client/src/components/CursorPresenceLayer.tsx +14 −22 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import reactionWork from "../assets/reactions/work.png"; import reactionYay from "../assets/reactions/yay.png"; import { useAppContext } from "../contexts/AppContext"; import { CanvasCore } from "../lib/canvas"; import { KeybindManager, REACTION_KEYBINDS } from "../lib/keybinds"; import Network from "../lib/network"; import { colorToCss, Loading Loading @@ -698,14 +699,6 @@ export const CursorPresenceLayer = () => { remoteRenderer.clear(); }; const isTypingTarget = (target: EventTarget | null) => target instanceof HTMLElement && Boolean( target.closest( 'input, textarea, select, [contenteditable="true"], [role="textbox"]', ), ); const showReactionCooldown = (cooldown: CursorReactionCooldown) => { if (cooldown.until <= Date.now()) return; reactionBlockedUntil = Math.max(reactionBlockedUntil, cooldown.until); Loading @@ -728,17 +721,11 @@ export const CursorPresenceLayer = () => { }, REACTION_DURATION + 50); }; const handleKeyDown = (event: KeyboardEvent) => { const sendReaction = (reaction: number) => { if ( !reactionsEnabledRef.current || !localActive.current || !socket.connected || event.repeat || event.altKey || event.ctrlKey || event.metaKey || isTypingTarget(event.target) || !/^[0-9]$/.test(event.key) !socket.connected ) { return; } Loading @@ -749,18 +736,15 @@ export const CursorPresenceLayer = () => { showReactionCooldown(reactionBlockedCooldown); } toast.error(REACTION_COOLDOWN_MESSAGE); event.preventDefault(); return; } reactionBlockedCooldown = undefined; if (now - lastReactionAt < REACTION_COOLDOWN_DURATION) { toast.error(REACTION_COOLDOWN_MESSAGE); event.preventDefault(); return; } lastReactionAt = now; const reaction = event.key === "0" ? 9 : Number(event.key) - 1; socket.emit("cursorReaction", reaction, (result) => { if (!mounted) return; if (!result.success) { Loading @@ -776,8 +760,15 @@ export const CursorPresenceLayer = () => { ).matches; appendReaction(localElement.current, reaction, reduceMotion); }); event.preventDefault(); }; const reactionKeybindHandlers = REACTION_KEYBINDS.map( (keybind, reaction) => { const handle = () => sendReaction(reaction); KeybindManager.on(keybind, handle); return [keybind, handle] as const; }, ); const handleResize = () => { remoteRenderer.resize(); scheduleViewport(); Loading @@ -792,7 +783,6 @@ export const CursorPresenceLayer = () => { canvas.on("surfacePointer", handleSurfacePointer); PanZoom.on("viewportMove", handleViewportMove); PanZoom.on("initialize", handleInitialize); document.addEventListener("keydown", handleKeyDown); window.addEventListener("blur", hideLocalCursor); window.addEventListener("resize", handleResize); Loading Loading @@ -829,7 +819,9 @@ export const CursorPresenceLayer = () => { canvas.off("surfacePointer", handleSurfacePointer); PanZoom.off("viewportMove", handleViewportMove); PanZoom.off("initialize", handleInitialize); document.removeEventListener("keydown", handleKeyDown); for (const [keybind, handle] of reactionKeybindHandlers) { KeybindManager.off(keybind, handle); } window.removeEventListener("blur", hideLocalCursor); window.removeEventListener("resize", handleResize); localActive.current = false; Loading
packages/client/src/lib/keybinds.ts +98 −226 Original line number Diff line number Diff line Loading @@ -23,219 +23,105 @@ interface EmittedKeybind { export const enforceObjectType: EnforceObjectType<IKeybind[]> = (v) => v; const KEYBINDS = enforceObjectType({ PAN_UP: [{ key: "KeyW" }, { key: "ArrowUp" }], PAN_DOWN: [{ key: "KeyS" }, { key: "ArrowDown" }], PAN_LEFT: [{ key: "KeyA" }, { key: "ArrowLeft" }], PAN_RIGHT: [{ key: "KeyD" }, { key: "ArrowRight" }], ZOOM_OUT: [ { key: "KeyQ", ctrl: false, alt: false, meta: false, shift: false, }, { key: "Minus", ctrl: false, export const REACTION_KEYBINDS = [ "REACTION_LOVE", "REACTION_JAM", "REACTION_CUTE", "REACTION_POG", "REACTION_YAY", "REACTION_BONGO", "REACTION_SHOCKED", "REACTION_WORK", "REACTION_CRY", "REACTION_AWE", ] as const; const unmodifiedKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: false, }, ], ZOOM_IN: [ { key: "KeyE", ctrl: false, alt: false, meta: false, shift: false, }, { key: "Equal", ctrl: false, alt: false, meta: false, shift: false, }, ], ZOOM_OUT_SMALL: [ { key: "KeyQ", ctrl: false, alt: false, meta: false, shift: true, }, { key: "Minus", ctrl: false, alt: false, meta: false, shift: true, }, ], ZOOM_IN_SMALL: [ { key: "KeyE", ctrl: false, })); const shiftKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: true, }, { key: "Equal", ctrl: false, alt: false, meta: false, shift: true, }, ], PIXEL_WHOIS: [ { key: "LCLICK", shift: true, }, { key: "LONG_PRESS" }, ], TEMPLATE_MOVE: [ { key: "LCLICK", alt: true, }, ], TOGGLE_TEMPLATE: [ { key: "KeyT", }, ], TOGGLE_BLANK: [ { key: "KeyV", // legacy pxls keybind }, { key: "KeyB", }, ], TOGGLE_HEATMAP: [ { key: "KeyH", }, ], TOGGLE_GRID: [ { key: "KeyG", }, ], TOGGLE_UI: [ { key: "KeyU", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_SETTINGS: [ { key: "KeyO", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_INFO: [ { key: "KeyI", ctrl: false, alt: false, meta: false, shift: false, }, ], TOGGLE_MOD_MENU: [ { key: "KeyM", }, ], DESELECT_COLOR: [ { key: "Escape", }, ], SWAP_COLORS: [ { key: "KeyX", ctrl: false, alt: false, meta: false, shift: false, }, ], PICK_COLOR: [ { key: "KeyC", ctrl: false, })); const ctrlKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: false, meta: false, shift: false, }, ], PICK_CANVAS_COLOR: [ { key: "MCLICK", }, ], MOD_SELECT: [ { key: "LCLICK", ctrl: true, }, ], CAPTURE_CANVAS: [ { key: "KeyP", ctrl: false, alt: false, meta: false, shift: false, }, ], CAPTURE_VIEW: [ { key: "KeyL", })); const altKeybind = (...keys: IKeybind["key"][]): IKeybind[] => keys.map((key) => ({ key, alt: true, ctrl: false, alt: false, meta: false, shift: false, }, ], CAPTURE_REGION: [ { key: "KeyL", ctrl: false, alt: false, meta: false, shift: true, }, ], CAPTURE_TEMPLATE: [ { key: "KeyP", ctrl: false, alt: false, meta: false, shift: true, }, ], KEYBINDS: [ { key: "Slash", shift: true, }, })); const isTypingTarget = (target: EventTarget | null) => target instanceof HTMLElement && Boolean( target.closest( 'input, textarea, select, [contenteditable="true"], [role="textbox"]', ), ); const KEYBINDS = enforceObjectType({ PAN_UP: unmodifiedKeybind("KeyW", "ArrowUp"), PAN_DOWN: unmodifiedKeybind("KeyS", "ArrowDown"), PAN_LEFT: unmodifiedKeybind("KeyA", "ArrowLeft"), PAN_RIGHT: unmodifiedKeybind("KeyD", "ArrowRight"), ZOOM_OUT: unmodifiedKeybind("KeyQ", "Minus"), ZOOM_IN: unmodifiedKeybind("KeyE", "Equal"), ZOOM_OUT_SMALL: shiftKeybind("KeyQ", "Minus"), ZOOM_IN_SMALL: shiftKeybind("KeyE", "Equal"), PIXEL_WHOIS: [ ...shiftKeybind("LCLICK"), ...unmodifiedKeybind("LONG_PRESS"), ], TEMPLATE_MOVE: altKeybind("LCLICK"), TOGGLE_TEMPLATE: unmodifiedKeybind("KeyT"), TOGGLE_BLANK: unmodifiedKeybind("KeyV", "KeyB"), TOGGLE_HEATMAP: unmodifiedKeybind("KeyH"), TOGGLE_GRID: unmodifiedKeybind("KeyG"), TOGGLE_UI: unmodifiedKeybind("KeyU"), TOGGLE_SETTINGS: unmodifiedKeybind("KeyO"), TOGGLE_INFO: unmodifiedKeybind("KeyI"), TOGGLE_MOD_MENU: unmodifiedKeybind("KeyM"), DESELECT_COLOR: unmodifiedKeybind("Escape"), SWAP_COLORS: unmodifiedKeybind("KeyX"), PICK_COLOR: unmodifiedKeybind("KeyC"), PICK_CANVAS_COLOR: unmodifiedKeybind("MCLICK"), MOD_SELECT: ctrlKeybind("LCLICK"), CAPTURE_CANVAS: unmodifiedKeybind("KeyP"), CAPTURE_VIEW: unmodifiedKeybind("KeyL"), CAPTURE_REGION: shiftKeybind("KeyL"), CAPTURE_TEMPLATE: shiftKeybind("KeyP"), REACTION_LOVE: unmodifiedKeybind("Digit1", "Numpad1"), REACTION_JAM: unmodifiedKeybind("Digit2", "Numpad2"), REACTION_CUTE: unmodifiedKeybind("Digit3", "Numpad3"), REACTION_POG: unmodifiedKeybind("Digit4", "Numpad4"), REACTION_YAY: unmodifiedKeybind("Digit5", "Numpad5"), REACTION_BONGO: unmodifiedKeybind("Digit6", "Numpad6"), REACTION_SHOCKED: unmodifiedKeybind("Digit7", "Numpad7"), REACTION_WORK: unmodifiedKeybind("Digit8", "Numpad8"), REACTION_CRY: unmodifiedKeybind("Digit9", "Numpad9"), REACTION_AWE: unmodifiedKeybind("Digit0", "Numpad0"), KEYBINDS: shiftKeybind("Slash"), }); class KeybindManager_ extends EventEmitter<{ Loading Loading @@ -266,13 +152,7 @@ class KeybindManager_ extends EventEmitter<{ handleKeydown = (e: KeyboardEvent) => { if (!this.enabled) return; const blacklistedElements = ["INPUT"]; if (e.target instanceof HTMLElement) { if (blacklistedElements.indexOf(e.target.tagName) > -1) { return; } } if (isTypingTarget(e.target)) return; if (e.key === "Alt") e.preventDefault(); if (e.key === "Control") e.preventDefault(); Loading @@ -282,15 +162,7 @@ class KeybindManager_ extends EventEmitter<{ handleKeyup = (e: KeyboardEvent) => { if (!this.enabled) return; // discard if in an input element const blacklistedElements = ["INPUT"]; if (e.target instanceof HTMLElement) { if (blacklistedElements.indexOf(e.target.tagName) > -1) { return; } } if (isTypingTarget(e.target)) return; const isHandled = this.handleInteraction( { Loading