Adds provisional keyboard panning

This commit is contained in:
Steve Ruiz 2021-06-30 21:56:42 +01:00
parent e5408219de
commit 7da573ffdb
10 changed files with 79 additions and 40 deletions

View file

@ -1,15 +1,16 @@
import Cursor from './cursor' import Cursor from './cursor'
import { useCoopSelector } from 'state/coop/coop-state' import { useCoopSelector } from 'state/coop/coop-state'
import { useSelector } from 'state'
export default function Presence(): JSX.Element { export default function Presence(): JSX.Element {
const others = useCoopSelector((s) => s.data.others) const others = useCoopSelector((s) => s.data.others)
const currentPageId = useSelector((s) => s.data.currentPageId)
return ( return (
<> <>
{Object.values(others).map(({ connectionId, presence }) => { {Object.values(others).map(({ connectionId, presence }) => {
if (presence == null) { if (presence === null) return null
return null if (presence.pageId !== currentPageId) return null
}
return ( return (
<Cursor <Cursor

View file

@ -2,12 +2,6 @@ import React from 'react'
import styled from 'styles' import styled from 'styles'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
// const transition = {
// type: 'spring',
// mass: 2,
// damping: 20,
// }
export default function Cursor({ export default function Cursor({
color = 'dodgerblue', color = 'dodgerblue',
duration = 0, duration = 0,

View file

@ -8,6 +8,7 @@ import {
fastTranslate, fastTranslate,
} from 'state/hacks' } from 'state/hacks'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import Vec from 'utils/vec'
export default function useCanvasEvents( export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement> rCanvas: MutableRefObject<SVGGElement>
@ -27,8 +28,14 @@ export default function useCanvasEvents(
const handlePointerMove = useCallback((e: React.PointerEvent) => { const handlePointerMove = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return if (!inputs.canAccept(e.pointerId)) return
const prev = inputs.pointer?.point
const info = inputs.pointerMove(e) 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')) { if (state.isIn('draw.editing')) {
fastDrawUpdate(info) fastDrawUpdate(info)
} else if (state.isIn('brushSelecting')) { } else if (state.isIn('brushSelecting')) {

View file

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useEffect } from 'react' import { useEffect } from 'react'
import state from 'state' import state from 'state'
import inputs from 'state/inputs'
import { MoveType } from 'types' import { MoveType } from 'types'
import { getKeyboardEventInfo, metaKey } from 'utils' import { metaKey } from 'utils'
export default function useKeyboardEvents() { export default function useKeyboardEvents() {
useEffect(() => { useEffect(() => {
@ -24,7 +25,7 @@ export default function useKeyboardEvents() {
e.preventDefault() e.preventDefault()
} }
const info = getKeyboardEventInfo(e) const info = inputs.keydown(e)
switch (e.key) { switch (e.key) {
case 'ArrowUp': { case 'ArrowUp': {
@ -269,7 +270,7 @@ export default function useKeyboardEvents() {
} }
function handleKeyUp(e: KeyboardEvent) { function handleKeyUp(e: KeyboardEvent) {
const info = getKeyboardEventInfo(e) const info = inputs.keyup(e)
if (e.key === 'Shift') { if (e.key === 'Shift') {
state.send('RELEASED_SHIFT_KEY', info) state.send('RELEASED_SHIFT_KEY', info)

View file

@ -2,6 +2,7 @@
import React, { MutableRefObject, useCallback } from 'react' import React, { MutableRefObject, useCallback } from 'react'
import state from 'state' import state from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import Vec from 'utils/vec'
export default function useShapeEvents( export default function useShapeEvents(
id: string, id: string,
@ -57,6 +58,20 @@ export default function useShapeEvents(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return 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) { if (isParent) {
state.send('MOVED_OVER_GROUP', inputs.pointerEnter(e, id)) state.send('MOVED_OVER_GROUP', inputs.pointerEnter(e, id))
} else { } else {

View file

@ -1,5 +1,3 @@
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Client, Room, createClient } from '@liveblocks/client' import { Client, Room, createClient } from '@liveblocks/client'
import coopState from './coop-state' import coopState from './coop-state'
import { CoopPresence } from 'types' import { CoopPresence } from 'types'
@ -108,6 +106,7 @@ class CoopClient {
bufferedYs: this.bufferedYs, bufferedYs: this.bufferedYs,
times, times,
duration, duration,
pageId,
}) })
// Reset data for next update // Reset data for next update
@ -118,6 +117,7 @@ class CoopClient {
elapsed = 0 elapsed = 0
} }
// Add the new point and time
this.bufferedXs.push(point[0]) this.bufferedXs.push(point[0])
this.bufferedYs.push(point[1]) this.bufferedYs.push(point[1])
this.bufferedTs.push(elapsed / 1000) this.bufferedTs.push(elapsed / 1000)

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { PointerInfo } from 'types' import { KeyboardInfo, PointerInfo } from 'types'
import vec from 'utils/vec' import vec from 'utils/vec'
import { isDarwin, getPoint } from 'utils' import { isDarwin, getPoint } from 'utils'
@ -8,8 +8,12 @@ const DOUBLE_CLICK_DURATION = 250
class Inputs { class Inputs {
activePointerId?: number activePointerId?: number
pointerUpTime = 0 pointerUpTime = 0
points: Record<string, PointerInfo> = {}
pointer: PointerInfo pointer: PointerInfo
points: Record<string, PointerInfo> = {}
keyboard: KeyboardInfo
keys: Record<string, boolean> = {}
touchStart(e: TouchEvent | React.TouchEvent, target: string) { touchStart(e: TouchEvent | React.TouchEvent, target: string) {
const { shiftKey, ctrlKey, metaKey, altKey } = e const { shiftKey, ctrlKey, metaKey, altKey } = e
@ -190,6 +194,36 @@ class Inputs {
resetDoubleClick() { resetDoubleClick() {
this.pointerUpTime = 0 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() export default new Inputs()

View file

@ -173,8 +173,6 @@ const state = createState({
// RT_CHANGED_STATUS: 'setRtStatus', // RT_CHANGED_STATUS: 'setRtStatus',
// RT_DELETED_SHAPE: 'deleteRtShape', // RT_DELETED_SHAPE: 'deleteRtShape',
// RT_EDITED_SHAPE: 'editRtShape', // RT_EDITED_SHAPE: 'editRtShape',
// RT_MOVED_CURSOR: 'moveRtCursor',
// MOVED_POINTER: { secretlyDo: 'sendRtCursorMove' },
// Client // Client
RESIZED_WINDOW: 'resetPageState', RESIZED_WINDOW: 'resetPageState',
RESET_DOCUMENT_STATE: 'resetDocumentState', RESET_DOCUMENT_STATE: 'resetDocumentState',
@ -334,6 +332,7 @@ const state = createState({
selecting: { selecting: {
onEnter: ['setActiveToolSelect', 'clearInputs'], onEnter: ['setActiveToolSelect', 'clearInputs'],
on: { on: {
KEYBOARD_PANNED_CAMERA: 'panCamera',
STARTED_PINCHING: { STARTED_PINCHING: {
unless: 'isInSession', unless: 'isInSession',
to: 'pinching.selectPinching', to: 'pinching.selectPinching',

View file

@ -44,6 +44,7 @@ export type CoopPresence = {
bufferedYs: number[] bufferedYs: number[]
times: number[] times: number[]
duration: number duration: number
pageId: string
} }
export interface TLDocument { export interface TLDocument {
@ -286,6 +287,15 @@ export interface PointerInfo {
altKey: boolean altKey: boolean
} }
export interface KeyboardInfo {
key: string
keys: string[]
shiftKey: boolean
ctrlKey: boolean
metaKey: boolean
altKey: boolean
}
export enum Edge { export enum Edge {
Top = 'top_edge', Top = 'top_edge',
Right = 'right_edge', Right = 'right_edge',

View file

@ -1564,28 +1564,6 @@ export function metaKey(e: KeyboardEvent | React.KeyboardEvent): boolean {
return isDarwin() ? e.metaKey : e.ctrlKey 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. * Find the closest point on a SVG path to an off-path point.
* @param pathNode * @param pathNode