Improves performance for certain actions

This commit is contained in:
Steve Ruiz 2021-06-06 11:50:01 +01:00
parent 8623958e74
commit 704e92faa4
15 changed files with 846 additions and 530 deletions

View file

@ -11,52 +11,26 @@ import Bounds from './bounds/bounding-box'
import BoundsBg from './bounds/bounds-bg' import BoundsBg from './bounds/bounds-bg'
import Selected from './selected' import Selected from './selected'
import Handles from './bounds/handles' import Handles from './bounds/handles'
import { isMobile, throttle } from 'utils/utils' import { isMobile, screenToWorld, throttle } from 'utils/utils'
import session from 'state/session'
import { PointerInfo } from 'types'
import { fastDrawUpdate } from 'state/hacks'
import useCanvasEvents from 'hooks/useCanvasEvents'
export default function Canvas() { export default function Canvas() {
const rCanvas = useRef<SVGSVGElement>(null) const rCanvas = useRef<SVGSVGElement>(null)
const rGroup = useRef<SVGGElement>(null) const rGroup = useRef<SVGGElement>(null)
useCamera(rGroup) useCamera(rGroup)
useZoomEvents() useZoomEvents()
const events = useCanvasEvents(rCanvas)
const isReady = useSelector((s) => s.isIn('ready')) const isReady = useSelector((s) => s.isIn('ready'))
const handlePointerDown = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.setPointerCapture(e.pointerId)
state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas'))
}, [])
const handleTouchStart = useCallback((e: React.TouchEvent) => {
if (e.touches.length === 2) {
state.send('TOUCH_UNDO')
} else {
if (isMobile()) {
state.send('TOUCHED_CANVAS')
}
}
}, [])
const handlePointerMove = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
throttledPointerMove(inputs.pointerMove(e))
}, [])
const handlePointerUp = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.releasePointerCapture(e.pointerId)
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
}, [])
return ( return (
<MainSVG <MainSVG ref={rCanvas} {...events}>
ref={rCanvas}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onTouchStart={handleTouchStart}
>
<Defs /> <Defs />
{isReady && ( {isReady && (
<g ref={rGroup}> <g ref={rGroup}>

View file

@ -1,6 +1,8 @@
import { useSelector } from 'state' import { getShapeUtils } from 'lib/shape-utils'
import { GroupShape } from 'types' import state, { useSelector } from 'state'
import { deepCompareArrays, getPage } from 'utils/utils' import { Bounds, GroupShape, PageState } from 'types'
import { boundsContain } from 'utils/bounds'
import { deepCompareArrays, getPage, screenToWorld } from 'utils/utils'
import Shape from './shape' import Shape from './shape'
/* /*
@ -11,12 +13,42 @@ here; and still cheaper than any other pattern I've found.
const noOffset = [0, 0] const noOffset = [0, 0]
const viewportCache = new WeakMap<PageState, Bounds>()
export default function Page() { export default function Page() {
const currentPageShapeIds = useSelector(({ data }) => { const currentPageShapeIds = useSelector((s) => {
return Object.values(getPage(data).shapes) const page = getPage(s.data)
.filter((shape) => shape.parentId === data.currentPageId) const pageState = s.data.pageStates[page.id]
.sort((a, b) => a.childIndex - b.childIndex)
.map((shape) => shape.id) // if (!viewportCache.has(pageState)) {
// const [minX, minY] = screenToWorld([0, 0], s.data)
// const [maxX, maxY] = screenToWorld(
// [window.innerWidth, window.innerHeight],
// s.data
// )
// viewportCache.set(pageState, {
// minX,
// minY,
// maxX,
// maxY,
// height: maxX - minX,
// width: maxY - minY,
// })
// }
// const viewport = viewportCache.get(pageState)
return (
Object.values(page.shapes)
.filter((shape) => shape.parentId === page.id)
// .filter((shape) => {
// const shapeBounds = getShapeUtils(shape).getBounds(shape)
// console.log(shapeBounds, viewport)
// return boundsContain(viewport, shapeBounds)
// })
.sort((a, b) => a.childIndex - b.childIndex)
.map((shape) => shape.id)
)
}, deepCompareArrays) }, deepCompareArrays)
const isSelecting = useSelector((s) => s.isIn('selecting')) const isSelecting = useSelector((s) => s.isIn('selecting'))

View file

@ -12,7 +12,7 @@ export default function Selected() {
return Array.from(data.selectedIds.values()) return Array.from(data.selectedIds.values())
}, deepCompareArrays) }, deepCompareArrays)
const isSelecting = useSelector((s) => s.isInAny('notPointing', 'pinching')) const isSelecting = useSelector((s) => s.isIn('selecting'))
if (!isSelecting) return null if (!isSelecting) return null

View file

@ -49,7 +49,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
{...events} {...events}
/> />
)} )}
{!shape.isHidden && <ReadShape isGroup={isGroup} id={id} style={style} />} {!shape.isHidden && <RealShape isGroup={isGroup} id={id} style={style} />}
{isGroup && {isGroup &&
shape.children.map((shapeId) => ( shape.children.map((shapeId) => (
<Shape <Shape
@ -69,7 +69,7 @@ interface RealShapeProps {
style: Partial<React.SVGProps<SVGUseElement>> style: Partial<React.SVGProps<SVGUseElement>>
} }
const ReadShape = memo(function RealShape({ const RealShape = memo(function RealShape({
isGroup, isGroup,
id, id,
style, style,
@ -157,10 +157,10 @@ function Label({ children }: { children: React.ReactNode }) {
) )
} }
export { HoverIndicator }
export default memo(Shape)
function pp(n: number[]) { function pp(n: number[]) {
return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']' return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
} }
export { HoverIndicator }
export default memo(Shape)

53
hooks/useCanvasEvents.ts Normal file
View file

@ -0,0 +1,53 @@
import { MutableRefObject, useCallback } from 'react'
import state from 'state'
import { fastBrushSelect, fastDrawUpdate } from 'state/hacks'
import inputs from 'state/inputs'
import { isMobile } from 'utils/utils'
export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement>
) {
const handlePointerDown = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.setPointerCapture(e.pointerId)
state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas'))
}, [])
const handleTouchStart = useCallback((e: React.TouchEvent) => {
if (isMobile()) {
if (e.touches.length === 2) {
state.send('TOUCH_UNDO')
} else state.send('TOUCHED_CANVAS')
}
}, [])
const handlePointerMove = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
if (state.isIn('draw.editing')) {
fastDrawUpdate(inputs.pointerMove(e))
return
}
if (state.isIn('brushSelecting')) {
const info = inputs.pointerMove(e)
fastBrushSelect(info.point)
return
}
state.send('MOVED_POINTER', inputs.pointerMove(e))
}, [])
const handlePointerUp = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.releasePointerCapture(e.pointerId)
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
}, [])
return {
onPointerDown: handlePointerDown,
onTouchStart: handleTouchStart,
onPointerMove: handlePointerMove,
onPointerUp: handlePointerUp,
}
}

View file

@ -47,6 +47,7 @@ export default function useShapeEvents(
const handlePointerMove = useCallback( const handlePointerMove = useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return if (!inputs.canAccept(e.pointerId)) return
if (isGroup) { if (isGroup) {
state.send('MOVED_OVER_GROUP', inputs.pointerEnter(e, id)) state.send('MOVED_OVER_GROUP', inputs.pointerEnter(e, id))
} else { } else {

View file

@ -3,6 +3,12 @@ import state from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import * as vec from 'utils/vec' import * as vec from 'utils/vec'
import { useGesture } from 'react-use-gesture' import { useGesture } from 'react-use-gesture'
import {
fastBrushSelect,
fastPanUpdate,
fastPinchCamera,
fastZoomUpdate,
} from 'state/hacks'
/** /**
* Capture zoom gestures (pinches, wheels and pans) and send to the state. * Capture zoom gestures (pinches, wheels and pans) and send to the state.
@ -17,10 +23,17 @@ export default function useZoomEvents() {
{ {
onWheel: ({ event, delta }) => { onWheel: ({ event, delta }) => {
if (event.ctrlKey) { if (event.ctrlKey) {
state.send('ZOOMED_CAMERA', { const { point } = inputs.wheel(event as WheelEvent)
delta: delta[1], fastZoomUpdate(point, delta[1])
...inputs.wheel(event as WheelEvent), // state.send('ZOOMED_CAMERA', {
}) // delta: delta[1],
// ...inputs.wheel(event as WheelEvent),
// })
return
}
if (state.isIn('pointing')) {
fastPanUpdate(delta)
return return
} }
@ -45,12 +58,19 @@ export default function useZoomEvents() {
const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da) const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
state.send('PINCHED', { fastPinchCamera(
delta: vec.sub(rPinchPoint.current, origin), origin,
point: origin, vec.sub(rPinchPoint.current, origin),
distanceDelta, distanceDelta,
angleDelta, angleDelta
}) )
// state.send('PINCHED', {
// delta: vec.sub(rPinchPoint.current, origin),
// point: origin,
// distanceDelta,
// angleDelta,
// })
rPinchDa.current = da rPinchDa.current = da
rPinchPoint.current = origin rPinchPoint.current = origin

View file

@ -16,7 +16,7 @@
"@radix-ui/react-icons": "^1.0.3", "@radix-ui/react-icons": "^1.0.3",
"@radix-ui/react-radio-group": "^0.0.16", "@radix-ui/react-radio-group": "^0.0.16",
"@radix-ui/react-tooltip": "^0.0.18", "@radix-ui/react-tooltip": "^0.0.18",
"@state-designer/react": "^1.7.1", "@state-designer/react": "^1.7.3",
"@stitches/react": "^0.1.9", "@stitches/react": "^0.1.9",
"framer-motion": "^4.1.16", "framer-motion": "^4.1.16",
"ismobilejs": "^1.1.1", "ismobilejs": "^1.1.1",

94
state/hacks.ts Normal file
View file

@ -0,0 +1,94 @@
import { PointerInfo } from 'types'
import {
getCameraZoom,
getCurrentCamera,
screenToWorld,
setZoomCSS,
} from 'utils/utils'
import session from './session'
import state from './state'
import * as vec from 'utils/vec'
/**
* While a user is drawing with the draw tool, we want to update the shape without
* going through the trouble of updating the entire state machine. Speciifcally, we
* do not want to push the change through immer. Instead, we'll push the change
* directly to the state using `forceData`.
* @param info
*/
export function fastDrawUpdate(info: PointerInfo) {
const data = { ...state.data }
session.current.update(
data,
screenToWorld(info.point, data),
info.pressure,
info.shiftKey
)
const selectedId = Array.from(data.selectedIds.values())[0]
const shape = data.document.pages[data.currentPageId].shapes[selectedId]
data.document.pages[data.currentPageId].shapes[selectedId] = { ...shape }
state.forceData(Object.freeze(data))
}
export function fastPanUpdate(delta: number[]) {
const data = { ...state.data }
const camera = getCurrentCamera(data)
camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom))
data.pageStates[data.currentPageId].camera = { ...camera }
state.forceData(Object.freeze(data))
}
export function fastZoomUpdate(point: number[], delta: number) {
const data = { ...state.data }
const camera = getCurrentCamera(data)
const next = camera.zoom - (delta / 100) * camera.zoom
const p0 = screenToWorld(point, data)
camera.zoom = getCameraZoom(next)
const p1 = screenToWorld(point, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
data.pageStates[data.currentPageId].camera = { ...camera }
state.forceData(Object.freeze(data))
}
export function fastPinchCamera(
point: number[],
delta: number[],
distanceDelta: number,
angleDelta: number
) {
const data = { ...state.data }
const camera = getCurrentCamera(data)
camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom))
const next = camera.zoom - (distanceDelta / 300) * camera.zoom
const p0 = screenToWorld(point, data)
camera.zoom = getCameraZoom(next)
const p1 = screenToWorld(point, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
data.pageStates[data.currentPageId].camera = { ...camera }
state.forceData(Object.freeze(data))
}
export function fastBrushSelect(point: number[]) {
const data = { ...state.data }
session.current.update(data, screenToWorld(point, data))
data.selectedIds = new Set(data.selectedIds)
state.forceData(Object.freeze(data))
}

27
state/session.ts Normal file
View file

@ -0,0 +1,27 @@
import { BaseSession } from './sessions'
class SessionManager {
private _current?: BaseSession
clear() {
this._current = undefined
return this
}
setCurrent(session: BaseSession) {
this._current = session
return this
}
get current() {
return this._current
}
set current(session: BaseSession) {
this._current = session
}
}
const session = new SessionManager()
export default session

View file

@ -4,6 +4,7 @@ import BaseSession from './base-session'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'lib/shape-utils'
import { getBoundsFromPoints, getPage, getShapes } from 'utils/utils' import { getBoundsFromPoints, getPage, getShapes } from 'utils/utils'
import * as vec from 'utils/vec' import * as vec from 'utils/vec'
import state from 'state/state'
export default class BrushSession extends BaseSession { export default class BrushSession extends BaseSession {
origin: number[] origin: number[]
@ -62,7 +63,7 @@ export function getBrushSnapshot(data: Data) {
return { return {
selectedIds: new Set(data.selectedIds), selectedIds: new Set(data.selectedIds),
shapeHitTests: Object.fromEntries( shapeHitTests: Object.fromEntries(
getShapes(current(data)) getShapes(state.data)
.filter((shape) => shape.type !== ShapeType.Group) .filter((shape) => shape.type !== ShapeType.Group)
.map((shape) => { .map((shape) => {
return [ return [

View file

@ -25,6 +25,7 @@ import {
rotateBounds, rotateBounds,
getBoundsCenter, getBoundsCenter,
getDocumentBranch, getDocumentBranch,
getCameraZoom,
} from 'utils/utils' } from 'utils/utils'
import { import {
Data, Data,
@ -43,6 +44,7 @@ import {
SizeStyle, SizeStyle,
ColorStyle, ColorStyle,
} from 'types' } from 'types'
import session from './session'
const initialData: Data = { const initialData: Data = {
isReadOnly: false, isReadOnly: false,
@ -324,28 +326,31 @@ const state = createState({
], ],
on: { on: {
STARTED_PINCHING: { do: 'completeSession', to: 'pinching' }, STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
MOVED_POINTER: 'updateBrushSession', // Currently using hacks.fastBrushSelect
// MOVED_POINTER: 'updateBrushSession',
PANNED_CAMERA: 'updateBrushSession', PANNED_CAMERA: 'updateBrushSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
pinching: { },
},
pinching: {
on: {
// Pinching uses hacks.fastPinchCamera
// PINCHED: { do: 'pinchCamera' },
},
initial: 'selectPinching',
onExit: { secretlyDo: 'updateZoomCSS' },
states: {
selectPinching: {
on: { on: {
PINCHED: { do: 'pinchCamera' }, STOPPED_PINCHING: { to: 'selecting' },
}, },
initial: 'selectPinching', },
states: { toolPinching: {
selectPinching: { on: {
on: { STOPPED_PINCHING: { to: 'usingTool.previous' },
STOPPED_PINCHING: { to: 'selecting' },
},
},
toolPinching: {
on: {
STOPPED_PINCHING: { to: 'usingTool.previous' },
},
},
}, },
}, },
}, },
@ -385,17 +390,17 @@ const state = createState({
editing: { editing: {
onEnter: 'startDrawSession', onEnter: 'startDrawSession',
on: { on: {
STOPPED_POINTING: {
do: 'completeSession',
to: 'draw.creating',
},
CANCELLED: { CANCELLED: {
do: 'breakSession', do: 'breakSession',
to: 'selecting', to: 'selecting',
}, },
STOPPED_POINTING: {
do: 'completeSession',
to: 'draw.creating',
},
PRESSED_SHIFT: 'keyUpdateDrawSession', PRESSED_SHIFT: 'keyUpdateDrawSession',
RELEASED_SHIFT: 'keyUpdateDrawSession', RELEASED_SHIFT: 'keyUpdateDrawSession',
MOVED_POINTER: 'updateDrawSession', // MOVED_POINTER: 'updateDrawSession',
PANNED_CAMERA: 'updateDrawSession', PANNED_CAMERA: 'updateDrawSession',
}, },
}, },
@ -816,53 +821,57 @@ const state = createState({
// Shared // Shared
breakSession(data) { breakSession(data) {
session?.cancel(data) session.current?.cancel(data)
session = undefined session.clear()
history.disable() history.disable()
commands.deleteSelected(data) commands.deleteSelected(data)
history.enable() history.enable()
}, },
cancelSession(data) { cancelSession(data) {
session?.cancel(data) session.current?.cancel(data)
session = undefined session.clear()
}, },
completeSession(data) { completeSession(data) {
session?.complete(data) session.current?.complete(data)
session = undefined session.clear()
}, },
// Brushing // Brushing
startBrushSession(data, payload: PointerInfo) { startBrushSession(data, payload: PointerInfo) {
session = new Sessions.BrushSession( session.current = new Sessions.BrushSession(
data, data,
screenToWorld(payload.point, data) screenToWorld(payload.point, data)
) )
}, },
updateBrushSession(data, payload: PointerInfo) { updateBrushSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data)) session.current.update(data, screenToWorld(payload.point, data))
}, },
// Rotating // Rotating
startRotateSession(data, payload: PointerInfo) { startRotateSession(data, payload: PointerInfo) {
session = new Sessions.RotateSession( session.current = new Sessions.RotateSession(
data, data,
screenToWorld(payload.point, data) screenToWorld(payload.point, data)
) )
}, },
keyUpdateRotateSession(data, payload: PointerInfo) { keyUpdateRotateSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.shiftKey payload.shiftKey
) )
}, },
updateRotateSession(data, payload: PointerInfo) { updateRotateSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data), payload.shiftKey) session.current.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey
)
}, },
// Dragging / Translating // Dragging / Translating
startTranslateSession(data) { startTranslateSession(data) {
session = new Sessions.TranslateSession( session.current = new Sessions.TranslateSession(
data, data,
screenToWorld(inputs.pointer.origin, data) screenToWorld(inputs.pointer.origin, data)
) )
@ -871,7 +880,7 @@ const state = createState({
data, data,
payload: { shiftKey: boolean; altKey: boolean } payload: { shiftKey: boolean; altKey: boolean }
) { ) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.shiftKey, payload.shiftKey,
@ -879,7 +888,7 @@ const state = createState({
) )
}, },
updateTranslateSession(data, payload: PointerInfo) { updateTranslateSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(payload.point, data), screenToWorld(payload.point, data),
payload.shiftKey, payload.shiftKey,
@ -892,7 +901,7 @@ const state = createState({
const shapeId = Array.from(data.selectedIds.values())[0] const shapeId = Array.from(data.selectedIds.values())[0]
const handleId = payload.target const handleId = payload.target
session = new Sessions.HandleSession( session.current = new Sessions.HandleSession(
data, data,
shapeId, shapeId,
handleId, handleId,
@ -903,7 +912,7 @@ const state = createState({
data, data,
payload: { shiftKey: boolean; altKey: boolean } payload: { shiftKey: boolean; altKey: boolean }
) { ) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.shiftKey, payload.shiftKey,
@ -911,7 +920,7 @@ const state = createState({
) )
}, },
updateHandleSession(data, payload: PointerInfo) { updateHandleSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(payload.point, data), screenToWorld(payload.point, data),
payload.shiftKey, payload.shiftKey,
@ -925,14 +934,13 @@ const state = createState({
payload: PointerInfo & { target: Corner | Edge } payload: PointerInfo & { target: Corner | Edge }
) { ) {
const point = screenToWorld(inputs.pointer.origin, data) const point = screenToWorld(inputs.pointer.origin, data)
session = new Sessions.TransformSession(data, payload.target, point) session.current =
session =
data.selectedIds.size === 1 data.selectedIds.size === 1
? new Sessions.TransformSingleSession(data, payload.target, point) ? new Sessions.TransformSingleSession(data, payload.target, point)
: new Sessions.TransformSession(data, payload.target, point) : new Sessions.TransformSession(data, payload.target, point)
}, },
startDrawTransformSession(data, payload: PointerInfo) { startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSingleSession( session.current = new Sessions.TransformSingleSession(
data, data,
Corner.BottomRight, Corner.BottomRight,
screenToWorld(payload.point, data), screenToWorld(payload.point, data),
@ -940,7 +948,7 @@ const state = createState({
) )
}, },
keyUpdateTransformSession(data, payload: PointerInfo) { keyUpdateTransformSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.shiftKey, payload.shiftKey,
@ -948,7 +956,7 @@ const state = createState({
) )
}, },
updateTransformSession(data, payload: PointerInfo) { updateTransformSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(payload.point, data), screenToWorld(payload.point, data),
payload.shiftKey, payload.shiftKey,
@ -958,19 +966,19 @@ const state = createState({
// Direction // Direction
startDirectionSession(data, payload: PointerInfo) { startDirectionSession(data, payload: PointerInfo) {
session = new Sessions.DirectionSession( session.current = new Sessions.DirectionSession(
data, data,
screenToWorld(inputs.pointer.origin, data) screenToWorld(inputs.pointer.origin, data)
) )
}, },
updateDirectionSession(data, payload: PointerInfo) { updateDirectionSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data)) session.current.update(data, screenToWorld(payload.point, data))
}, },
// Drawing // Drawing
startDrawSession(data, payload: PointerInfo) { startDrawSession(data, payload: PointerInfo) {
const id = Array.from(data.selectedIds.values())[0] const id = Array.from(data.selectedIds.values())[0]
session = new Sessions.DrawSession( session.current = new Sessions.DrawSession(
data, data,
id, id,
screenToWorld(inputs.pointer.origin, data), screenToWorld(inputs.pointer.origin, data),
@ -978,7 +986,7 @@ const state = createState({
) )
}, },
keyUpdateDrawSession(data, payload: PointerInfo) { keyUpdateDrawSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.pressure, payload.pressure,
@ -986,7 +994,7 @@ const state = createState({
) )
}, },
updateDrawSession(data, payload: PointerInfo) { updateDrawSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(payload.point, data), screenToWorld(payload.point, data),
payload.pressure, payload.pressure,
@ -997,7 +1005,7 @@ const state = createState({
// Arrow // Arrow
startArrowSession(data, payload: PointerInfo) { startArrowSession(data, payload: PointerInfo) {
const id = Array.from(data.selectedIds.values())[0] const id = Array.from(data.selectedIds.values())[0]
session = new Sessions.ArrowSession( session.current = new Sessions.ArrowSession(
data, data,
id, id,
screenToWorld(inputs.pointer.origin, data), screenToWorld(inputs.pointer.origin, data),
@ -1005,14 +1013,18 @@ const state = createState({
) )
}, },
keyUpdateArrowSession(data, payload: PointerInfo) { keyUpdateArrowSession(data, payload: PointerInfo) {
session.update( session.current.update(
data, data,
screenToWorld(inputs.pointer.point, data), screenToWorld(inputs.pointer.point, data),
payload.shiftKey payload.shiftKey
) )
}, },
updateArrowSession(data, payload: PointerInfo) { updateArrowSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data), payload.shiftKey) session.current.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey
)
}, },
// Nudges // Nudges
@ -1257,6 +1269,10 @@ const state = createState({
const camera = getCurrentCamera(data) const camera = getCurrentCamera(data)
camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
}, },
updateZoomCSS(data) {
const camera = getCurrentCamera(data)
setZoomCSS(camera.zoom)
},
pinchCamera( pinchCamera(
data, data,
payload: { payload: {
@ -1509,16 +1525,10 @@ const state = createState({
}, },
}) })
let session: Sessions.BaseSession
export default state export default state
export const useSelector = createSelectorHook(state) export const useSelector = createSelectorHook(state)
function getCameraZoom(zoom: number) {
return clamp(zoom, 0.1, 5)
}
function getParentId(data: Data, id: string) { function getParentId(data: Data, id: string) {
const shape = getPage(data).shapes[id] const shape = getPage(data).shapes[id]
return shape.parentId return shape.parentId

View file

@ -33,15 +33,7 @@ export interface Data {
pages: Record<string, Page> pages: Record<string, Page>
code: Record<string, CodeFile> code: Record<string, CodeFile>
} }
pageStates: Record< pageStates: Record<string, PageState>
string,
{
camera: {
point: number[]
zoom: number
}
}
>
} }
/* -------------------------------------------------- */ /* -------------------------------------------------- */
@ -56,6 +48,13 @@ export interface Page {
shapes: Record<string, Shape> shapes: Record<string, Shape>
} }
export interface PageState {
camera: {
point: number[]
zoom: number
}
}
export enum ShapeType { export enum ShapeType {
Dot = 'dot', Dot = 'dot',
Circle = 'circle', Circle = 'circle',

View file

@ -834,6 +834,10 @@ export function throttle<P extends any[], T extends (...args: P) => any>(
} }
} }
export function getCameraZoom(zoom: number) {
return clamp(zoom, 0.1, 5)
}
export function pointInRect( export function pointInRect(
point: number[], point: number[],
minX: number, minX: number,

909
yarn.lock

File diff suppressed because it is too large Load diff