Fix stale inputs reference (#92)

* Add `onMount` to Renderer to share inputs

* Removes brush updater

* Update brush.test.tsx
This commit is contained in:
Steve Ruiz 2021-09-17 22:29:45 +01:00 committed by GitHub
parent cdb7c74f8e
commit 8ae625baef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 77 additions and 81 deletions

View file

@ -9,7 +9,6 @@ This package contains the core of the [tldraw](https://tldraw.com) library. It i
- [`Utils`](#utils)
- [`Vec`](#vec)
- [`Svg`](#svg)
- [`brushUpdater`](#brushupdater)
- [`Intersect`](#intersect)
## Installation
@ -160,17 +159,6 @@ An object that describes a relationship between two shapes on the page.
The `TLShapeUtil` is an abstract class that you can extend to create utilities for your custom shapes. See [Guide: Create a Custom Shape](#create-a-custom-shape).
### `brushUpdater`
The `brushUpdater` is a special class instance that allows you to quickly update the selection brush rectangle.
| Method | Description |
| ------- | --------------------------------------------------------------- |
| `set` | a method that accepts either a `TLBounds` object or `undefined` |
| `clear` | a method to hide the brush |
Normally, the renderer's brush will update in response to changes to `pageState.brush`; however, calling `brushUpdater.set` will produce a faster change in the brush rectangle. Calling `brushUpdater.set` will prevent the brush from any future updates from `pageState.brush`.
## `inputs`
A class instance that stores the current pointer position and pressed keys.

View file

@ -1,33 +0,0 @@
import * as React from 'react'
import type { TLBounds } from '+types'
export class BrushUpdater {
ref = React.createRef<SVGSVGElement>()
isControlled = false
set(bounds?: TLBounds) {
if (!this.isControlled) this.isControlled = true
if (!bounds) {
this.clear()
return
}
const elm = this.ref?.current
if (!elm) return
elm.setAttribute('opacity', '1')
elm.setAttribute('transform', `translate(${bounds.minX.toString()}, ${bounds.minY.toString()})`)
elm.setAttribute('width', bounds.width.toString())
elm.setAttribute('height', bounds.height.toString())
}
clear() {
const elm = this.ref?.current
if (!elm) return
elm.setAttribute('opacity', '0')
elm.setAttribute('width', '0')
elm.setAttribute('height', '0')
}
}

View file

@ -4,6 +4,17 @@ import { Brush } from './brush'
describe('brush', () => {
test('mounts component without crashing', () => {
renderWithSvg(<Brush />)
renderWithSvg(
<Brush
brush={{
minX: 0,
maxX: 100,
minY: 0,
maxY: 100,
width: 100,
height: 100,
}}
/>
)
})
})

View file

@ -1,12 +1,21 @@
import { SVGContainer } from '+components'
import { Container } from '+components/container'
import type { TLBounds } from '+types'
import * as React from 'react'
import { BrushUpdater } from './BrushUpdater'
export const brushUpdater = new BrushUpdater()
export const Brush = React.memo((): JSX.Element | null => {
export const Brush = React.memo(({ brush }: { brush: TLBounds }): JSX.Element | null => {
return (
<svg ref={brushUpdater.ref} opacity={0} width={0} height={0}>
<rect className="tl-brush" x={0} y={0} width="100%" height="100%" />
</svg>
<Container bounds={brush} rotation={0}>
<SVGContainer>
<rect
className="tl-brush"
opacity={1}
x={0}
y={0}
width={brush.width}
height={brush.height}
/>
</SVGContainer>
</Container>
)
})

View file

@ -1,2 +1 @@
export * from './brush'
export * from './BrushUpdater'

View file

@ -73,7 +73,7 @@ export function Canvas<T extends TLShape, M extends Record<string, unknown>>({
hideHandles={hideHandles}
meta={meta}
/>
<Brush />
{pageState.brush && <Brush brush={pageState.brush} />}
</div>
</ErrorBoundary>
</div>

View file

@ -1,4 +1,3 @@
export * from './renderer'
export { brushUpdater } from './brush'
export * from './svg-container'
export * from './html-container'

View file

@ -55,6 +55,10 @@ export interface RendererProps<T extends TLShape, E extends Element = any, M = a
* An object of custom options that should be passed to rendered shapes.
*/
meta?: M
/**
* A callback that receives the renderer's inputs manager.
*/
onMount?: (inputs: Inputs) => void
}
/**
@ -74,6 +78,7 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
hideHandles = false,
hideIndicators = false,
hideBounds = false,
onMount,
...rest
}: RendererProps<T, E, M>): JSX.Element {
useTLTheme(theme)
@ -96,6 +101,10 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
inputs: new Inputs(),
}))
React.useEffect(() => {
onMount?.(context.inputs)
}, [context])
return (
<TLContext.Provider value={context as unknown as TLContextType<TLShape, Element>}>
<Canvas

View file

@ -22,7 +22,7 @@ export interface TLPageState {
point: number[]
zoom: number
}
brush?: TLBounds
brush?: TLBounds | null
pointedId?: string | null
hoveredId?: string | null
editingId?: string | null

View file

@ -202,6 +202,7 @@ function InnerTldraw({
onRenderCountChange={tlstate.onRenderCountChange}
onShapeChange={tlstate.onShapeChange}
onShapeBlur={tlstate.onShapeBlur}
onMount={tlstate.handleMount}
/>
</ContextMenu>
<MenuButtons>

View file

@ -1,5 +1,4 @@
import * as React from 'react'
import { inputs } from '@tldraw/core'
import { useHotkeys } from 'react-hotkeys-hook'
import { TLDrawShapeType } from '~types'
import { useTLDrawContext } from '~hooks'
@ -9,13 +8,11 @@ export function useKeyboardShortcuts() {
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const info = inputs.keydown(e)
tlstate.onKeyDown(e.key, info)
tlstate.onKeyDown(e.key)
}
const handleKeyUp = (e: KeyboardEvent) => {
const info = inputs.keyup(e)
tlstate.onKeyUp(e.key, info)
tlstate.onKeyUp(e.key)
}
window.addEventListener('keydown', handleKeyDown)

View file

@ -1,4 +1,4 @@
import { brushUpdater, Utils } from '@tldraw/core'
import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { Data, Session, TLDrawPatch, TLDrawStatus } from '~types'
import { TLDR } from '~state/tldr'
@ -23,14 +23,11 @@ export class BrushSession implements Session {
// Create a bounding box between the origin and the new point
const brush = Utils.getBoundsFromPoints([origin, point])
brushUpdater.set(brush)
// Find ids of brushed shapes
const hits = new Set<string>()
const selectedIds = new Set(snapshot.selectedIds)
const page = TLDR.getPage(data, currentPageId)
const pageState = TLDR.getPageState(data, currentPageId)
snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
if (selectedIds.has(id)) return
@ -55,17 +52,18 @@ export class BrushSession implements Session {
}
})
if (
selectedIds.size === pageState.selectedIds.length &&
pageState.selectedIds.every((id) => selectedIds.has(id))
) {
return {}
}
// if (
// selectedIds.size === pageState.selectedIds.length &&
// pageState.selectedIds.every((id) => selectedIds.has(id))
// ) {
// return {}
// }
return {
document: {
pageStates: {
[currentPageId]: {
brush,
selectedIds: Array.from(selectedIds.values()),
},
},
@ -79,6 +77,7 @@ export class BrushSession implements Session {
document: {
pageStates: {
[currentPageId]: {
brush: null,
selectedIds: this.snapshot.selectedIds,
},
},
@ -89,10 +88,12 @@ export class BrushSession implements Session {
complete(data: Data) {
const { currentPageId } = data.appState
const pageState = TLDR.getPageState(data, currentPageId)
return {
document: {
pageStates: {
[currentPageId]: {
brush: null,
selectedIds: [...pageState.selectedIds],
},
},

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { StateManager } from 'rko'
import {
TLBoundsCorner,
@ -5,15 +6,14 @@ import {
TLBoundsEventHandler,
TLBoundsHandleEventHandler,
TLCanvasEventHandler,
TLKeyboardInfo,
TLPageState,
TLPinchEventHandler,
TLPointerEventHandler,
TLWheelEventHandler,
Utils,
brushUpdater,
TLPointerInfo,
TLBounds,
Inputs,
} from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import {
@ -98,6 +98,8 @@ export class TLDrawState extends StateManager<Data> {
private _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
private _onMount?: (tlstate: TLDrawState) => void
inputs?: Inputs
selectHistory: SelectHistory = {
stack: [[]],
pointer: 0,
@ -271,6 +273,10 @@ export class TLDrawState extends StateManager<Data> {
...data.document.pageStates[pageId],
}
if (!nextPageState.brush) {
delete nextPageState.brush
}
if (nextPageState.hoveredId && !page.shapes[nextPageState.hoveredId]) {
delete nextPageState.hoveredId
}
@ -339,6 +345,10 @@ export class TLDrawState extends StateManager<Data> {
)
}
handleMount = (inputs: Inputs): void => {
this.inputs = inputs
}
/* -------------------------------------------------- */
/* Settings & UI */
/* -------------------------------------------------- */
@ -1357,10 +1367,10 @@ export class TLDrawState extends StateManager<Data> {
if (!session) return this
const result = session.complete(this.state, ...args)
this.session = undefined
const result = session.complete(this.state, ...args)
if (result === undefined) {
this.isCreating = false
@ -1450,6 +1460,7 @@ export class TLDrawState extends StateManager<Data> {
document: {
pageStates: {
[this.currentPageId]: {
...result.document?.pageStates?.[this.currentPageId],
editingId: null,
},
},
@ -1939,7 +1950,6 @@ export class TLDrawState extends StateManager<Data> {
}
case TLDrawStatus.Brushing: {
this.cancelSession()
brushUpdater.clear()
break
}
case TLDrawStatus.Translating:
@ -2132,9 +2142,13 @@ export class TLDrawState extends StateManager<Data> {
/* ----------------- Keyboard Events ---------------- */
onKeyDown = (key: string, info: TLKeyboardInfo) => {
onKeyDown = (key: string) => {
const info = this.inputs?.pointer
if (!info) return
if (key === 'Escape') {
this.cancel()
return
}
@ -2188,7 +2202,10 @@ export class TLDrawState extends StateManager<Data> {
}
}
onKeyUp = (key: string, info: TLKeyboardInfo) => {
onKeyUp = (key: string) => {
const info = this.inputs?.pointer
if (!info) return
switch (this.appState.status.current) {
case TLDrawStatus.Brushing: {
if (key === 'Meta' || key === 'Control') {
@ -2360,7 +2377,6 @@ export class TLDrawState extends StateManager<Data> {
}
case TLDrawStatus.Brushing: {
this.completeSession<Sessions.BrushSession>()
brushUpdater.clear()
break
}
case TLDrawStatus.Translating: {
@ -2642,7 +2658,6 @@ export class TLDrawState extends StateManager<Data> {
}
case TLDrawStatus.Brushing: {
this.completeSession<Sessions.BrushSession>()
brushUpdater.clear()
break
}
}