tldraw/state/sessions/brush-session.ts

102 lines
2.7 KiB
TypeScript
Raw Normal View History

2021-06-04 16:08:43 +00:00
import { Bounds, Data, ShapeType } from 'types'
import BaseSession from './base-session'
2021-06-21 21:35:28 +00:00
import { getShapeUtils } from 'state/shape-utils'
import { deepClone, getBoundsFromPoints } from 'utils'
import vec from 'utils/vec'
2021-06-29 12:00:59 +00:00
import tld from 'utils/tld'
2021-05-10 12:16:57 +00:00
export default class BrushSession extends BaseSession {
origin: number[]
snapshot: BrushSnapshot
constructor(data: Data, point: number[]) {
super(data)
this.origin = vec.round(point)
this.snapshot = getBrushSnapshot(data)
2021-05-10 12:16:57 +00:00
}
2021-06-21 21:35:28 +00:00
update = (data: Data, point: number[]): void => {
2021-05-10 12:16:57 +00:00
const { origin, snapshot } = this
2021-05-18 08:32:20 +00:00
const brushBounds = getBoundsFromPoints([origin, point])
2021-05-10 12:16:57 +00:00
2021-06-04 17:56:46 +00:00
const hits = new Set<string>([])
const selectedIds = [...snapshot.selectedIds]
2021-06-21 21:35:28 +00:00
for (const id in snapshot.shapeHitTests) {
if (selectedIds.includes(id)) continue
2021-06-04 16:08:43 +00:00
const { test, selectId } = snapshot.shapeHitTests[id]
2021-06-04 17:56:46 +00:00
if (!hits.has(selectId)) {
if (test(brushBounds)) {
hits.add(selectId)
// When brushing a shape, select its top group parent.
if (!selectedIds.includes(selectId)) {
selectedIds.push(selectId)
2021-06-04 17:56:46 +00:00
}
} else if (selectedIds.includes(selectId)) {
selectedIds.splice(selectedIds.indexOf(selectId), 1)
}
2021-05-12 11:27:33 +00:00
}
}
2021-05-10 12:16:57 +00:00
2021-06-29 12:00:59 +00:00
tld.getPageState(data).selectedIds = selectedIds
data.brush = brushBounds
2021-05-10 12:16:57 +00:00
}
2021-06-21 21:35:28 +00:00
cancel = (data: Data): void => {
2021-05-10 12:16:57 +00:00
data.brush = undefined
2021-06-29 12:00:59 +00:00
tld.setSelectedIds(data, this.snapshot.selectedIds)
2021-05-10 12:16:57 +00:00
}
2021-06-21 21:35:28 +00:00
complete = (data: Data): void => {
2021-05-10 12:16:57 +00:00
data.brush = undefined
}
}
2021-05-10 12:16:57 +00:00
/**
* Get a snapshot of the current selected ids, for each shape that is
* not already selected, the shape's id and a test to see whether the
* brush will intersect that shape. For tests, start broad -> fine.
*/
2021-06-21 21:35:28 +00:00
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getBrushSnapshot(data: Data) {
2021-06-29 14:54:46 +00:00
const cData = data
2021-06-29 12:00:59 +00:00
const { selectedIds } = tld.getPageState(cData)
2021-06-29 12:00:59 +00:00
const shapesToTest = tld
.getShapes(cData)
2021-06-10 09:49:16 +00:00
.filter((shape) => shape.type !== ShapeType.Group && !shape.isHidden)
.filter(
(shape) =>
!(
selectedIds.includes(shape.id) || selectedIds.includes(shape.parentId)
)
)
2021-06-29 14:54:46 +00:00
.map(deepClone)
return {
selectedIds: [...selectedIds],
shapeHitTests: Object.fromEntries(
shapesToTest.map((shape) => {
return [
shape.id,
{
2021-06-29 14:54:46 +00:00
selectId: tld.getTopParentId(data, shape.id),
test: (bounds: Bounds) => {
return getShapeUtils(shape).hitTestBounds(shape, bounds)
},
},
]
})
),
2021-05-10 12:16:57 +00:00
}
}
export type BrushSnapshot = ReturnType<typeof getBrushSnapshot>