diff --git a/components/canvas/coop/coop.tsx b/components/canvas/coop/coop.tsx index f8614dbab..9cec229bc 100644 --- a/components/canvas/coop/coop.tsx +++ b/components/canvas/coop/coop.tsx @@ -1,15 +1,16 @@ import Cursor from './cursor' import { useCoopSelector } from 'state/coop/coop-state' +import { useSelector } from 'state' export default function Presence(): JSX.Element { const others = useCoopSelector((s) => s.data.others) + const currentPageId = useSelector((s) => s.data.currentPageId) return ( <> {Object.values(others).map(({ connectionId, presence }) => { - if (presence == null) { - return null - } + if (presence === null) return null + if (presence.pageId !== currentPageId) return null return ( @@ -27,8 +28,14 @@ export default function useCanvasEvents( const handlePointerMove = useCallback((e: React.PointerEvent) => { if (!inputs.canAccept(e.pointerId)) return + const prev = inputs.pointer?.point const info = inputs.pointerMove(e) + if (prev && state.isIn('selecting') && inputs.keys[' ']) { + state.send('KEYBOARD_PANNED_CAMERA', { delta: Vec.sub(prev, info.point) }) + return + } + if (state.isIn('draw.editing')) { fastDrawUpdate(info) } else if (state.isIn('brushSelecting')) { diff --git a/hooks/useKeyboardEvents.ts b/hooks/useKeyboardEvents.ts index 1d3ecbceb..e294baf33 100644 --- a/hooks/useKeyboardEvents.ts +++ b/hooks/useKeyboardEvents.ts @@ -1,8 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { useEffect } from 'react' import state from 'state' +import inputs from 'state/inputs' import { MoveType } from 'types' -import { getKeyboardEventInfo, metaKey } from 'utils' +import { metaKey } from 'utils' export default function useKeyboardEvents() { useEffect(() => { @@ -24,7 +25,7 @@ export default function useKeyboardEvents() { e.preventDefault() } - const info = getKeyboardEventInfo(e) + const info = inputs.keydown(e) switch (e.key) { case 'ArrowUp': { @@ -269,7 +270,7 @@ export default function useKeyboardEvents() { } function handleKeyUp(e: KeyboardEvent) { - const info = getKeyboardEventInfo(e) + const info = inputs.keyup(e) if (e.key === 'Shift') { state.send('RELEASED_SHIFT_KEY', info) diff --git a/hooks/useShapeEvents.ts b/hooks/useShapeEvents.ts index 2a4ffa515..0e3c26cbd 100644 --- a/hooks/useShapeEvents.ts +++ b/hooks/useShapeEvents.ts @@ -2,6 +2,7 @@ import React, { MutableRefObject, useCallback } from 'react' import state from 'state' import inputs from 'state/inputs' +import Vec from 'utils/vec' export default function useShapeEvents( id: string, @@ -57,6 +58,20 @@ export default function useShapeEvents( (e: React.PointerEvent) => { if (!inputs.canAccept(e.pointerId)) return + const prev = inputs.pointer?.point + const info = inputs.pointerMove(e) + + if (prev && state.isIn('selecting') && inputs.keys[' ']) { + if (!e.currentTarget.hasPointerCapture(e.pointerId)) { + e.currentTarget.setPointerCapture(e.pointerId) + } + + state.send('KEYBOARD_PANNED_CAMERA', { + delta: Vec.sub(prev, info.point), + }) + return + } + if (isParent) { state.send('MOVED_OVER_GROUP', inputs.pointerEnter(e, id)) } else { diff --git a/state/coop/client-liveblocks.ts b/state/coop/client-liveblocks.ts index 38626cff3..ef5d76381 100644 --- a/state/coop/client-liveblocks.ts +++ b/state/coop/client-liveblocks.ts @@ -1,5 +1,3 @@ -/* eslint-disable prefer-const */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Client, Room, createClient } from '@liveblocks/client' import coopState from './coop-state' import { CoopPresence } from 'types' @@ -108,6 +106,7 @@ class CoopClient { bufferedYs: this.bufferedYs, times, duration, + pageId, }) // Reset data for next update @@ -118,6 +117,7 @@ class CoopClient { elapsed = 0 } + // Add the new point and time this.bufferedXs.push(point[0]) this.bufferedYs.push(point[1]) this.bufferedTs.push(elapsed / 1000) diff --git a/state/inputs.tsx b/state/inputs.tsx index 6cde4377a..bd0ddb8b2 100644 --- a/state/inputs.tsx +++ b/state/inputs.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { PointerInfo } from 'types' +import { KeyboardInfo, PointerInfo } from 'types' import vec from 'utils/vec' import { isDarwin, getPoint } from 'utils' @@ -8,8 +8,12 @@ const DOUBLE_CLICK_DURATION = 250 class Inputs { activePointerId?: number pointerUpTime = 0 - points: Record = {} + pointer: PointerInfo + points: Record = {} + + keyboard: KeyboardInfo + keys: Record = {} touchStart(e: TouchEvent | React.TouchEvent, target: string) { const { shiftKey, ctrlKey, metaKey, altKey } = e @@ -190,6 +194,36 @@ class Inputs { resetDoubleClick() { this.pointerUpTime = 0 } + + keydown = (e: KeyboardEvent | React.KeyboardEvent): KeyboardInfo => { + const { shiftKey, ctrlKey, metaKey, altKey } = e + + this.keys[e.key] = true + + return { + key: e.key, + keys: Object.keys(this.keys), + shiftKey, + ctrlKey, + metaKey: isDarwin() ? metaKey : ctrlKey, + altKey, + } + } + + keyup = (e: KeyboardEvent | React.KeyboardEvent): KeyboardInfo => { + const { shiftKey, ctrlKey, metaKey, altKey } = e + + delete this.keys[e.key] + + return { + key: e.key, + keys: Object.keys(this.keys), + shiftKey, + ctrlKey, + metaKey: isDarwin() ? metaKey : ctrlKey, + altKey, + } + } } export default new Inputs() diff --git a/state/state.ts b/state/state.ts index 35fdfcbb9..28791f9f1 100644 --- a/state/state.ts +++ b/state/state.ts @@ -173,8 +173,6 @@ const state = createState({ // RT_CHANGED_STATUS: 'setRtStatus', // RT_DELETED_SHAPE: 'deleteRtShape', // RT_EDITED_SHAPE: 'editRtShape', - // RT_MOVED_CURSOR: 'moveRtCursor', - // MOVED_POINTER: { secretlyDo: 'sendRtCursorMove' }, // Client RESIZED_WINDOW: 'resetPageState', RESET_DOCUMENT_STATE: 'resetDocumentState', @@ -334,6 +332,7 @@ const state = createState({ selecting: { onEnter: ['setActiveToolSelect', 'clearInputs'], on: { + KEYBOARD_PANNED_CAMERA: 'panCamera', STARTED_PINCHING: { unless: 'isInSession', to: 'pinching.selectPinching', diff --git a/types.ts b/types.ts index 01197577b..5c2ad14fe 100644 --- a/types.ts +++ b/types.ts @@ -44,6 +44,7 @@ export type CoopPresence = { bufferedYs: number[] times: number[] duration: number + pageId: string } export interface TLDocument { @@ -286,6 +287,15 @@ export interface PointerInfo { altKey: boolean } +export interface KeyboardInfo { + key: string + keys: string[] + shiftKey: boolean + ctrlKey: boolean + metaKey: boolean + altKey: boolean +} + export enum Edge { Top = 'top_edge', Right = 'right_edge', diff --git a/utils/utils.ts b/utils/utils.ts index a8336db49..c31766989 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1564,28 +1564,6 @@ export function metaKey(e: KeyboardEvent | React.KeyboardEvent): boolean { return isDarwin() ? e.metaKey : e.ctrlKey } -/** - * Extract info about a keyboard event. - * @param e - */ -export function getKeyboardEventInfo(e: KeyboardEvent | React.KeyboardEvent): { - key: string - shiftKey: boolean - ctrlKey: boolean - metaKey: boolean - altKey: boolean -} { - const { shiftKey, ctrlKey, metaKey, altKey } = e - - return { - key: e.key, - shiftKey, - ctrlKey, - metaKey: isDarwin() ? metaKey : ctrlKey, - altKey, - } -} - /** * Find the closest point on a SVG path to an off-path point. * @param pathNode