You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

brush-session.ts 2.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { current } from 'immer'
  2. import { Bounds, Data, ShapeType } from 'types'
  3. import BaseSession from './base-session'
  4. import { getShapeUtils } from 'lib/shape-utils'
  5. import { getBoundsFromPoints, getPage, getShapes } from 'utils/utils'
  6. import * as vec from 'utils/vec'
  7. import state from 'state/state'
  8. export default class BrushSession extends BaseSession {
  9. origin: number[]
  10. snapshot: BrushSnapshot
  11. constructor(data: Data, point: number[]) {
  12. super(data)
  13. this.origin = vec.round(point)
  14. this.snapshot = getBrushSnapshot(data)
  15. }
  16. update = (data: Data, point: number[]) => {
  17. const { origin, snapshot } = this
  18. const brushBounds = getBoundsFromPoints([origin, point])
  19. const hits = new Set<string>([])
  20. for (let id in snapshot.shapeHitTests) {
  21. const { test, selectId } = snapshot.shapeHitTests[id]
  22. if (!hits.has(selectId)) {
  23. if (test(brushBounds)) {
  24. hits.add(selectId)
  25. // When brushing a shape, select its top group parent.
  26. if (!data.selectedIds.has(selectId)) {
  27. data.selectedIds.add(selectId)
  28. }
  29. } else if (data.selectedIds.has(selectId)) {
  30. data.selectedIds.delete(selectId)
  31. }
  32. }
  33. }
  34. data.brush = brushBounds
  35. }
  36. cancel = (data: Data) => {
  37. data.brush = undefined
  38. data.selectedIds = new Set(this.snapshot.selectedIds)
  39. }
  40. complete = (data: Data) => {
  41. data.brush = undefined
  42. }
  43. }
  44. /**
  45. * Get a snapshot of the current selected ids, for each shape that is
  46. * not already selected, the shape's id and a test to see whether the
  47. * brush will intersect that shape. For tests, start broad -> fine.
  48. */
  49. export function getBrushSnapshot(data: Data) {
  50. return {
  51. selectedIds: new Set(data.selectedIds),
  52. shapeHitTests: Object.fromEntries(
  53. getShapes(state.data)
  54. .filter((shape) => shape.type !== ShapeType.Group)
  55. .map((shape) => {
  56. return [
  57. shape.id,
  58. {
  59. selectId: getTopParentId(data, shape.id),
  60. test: (bounds: Bounds) =>
  61. getShapeUtils(shape).hitTestBounds(shape, bounds),
  62. },
  63. ]
  64. })
  65. ),
  66. }
  67. }
  68. export type BrushSnapshot = ReturnType<typeof getBrushSnapshot>
  69. function getTopParentId(data: Data, id: string): string {
  70. const shape = getPage(data).shapes[id]
  71. return shape.parentId === data.currentPageId ||
  72. shape.parentId === data.currentParentId
  73. ? id
  74. : getTopParentId(data, shape.parentId)
  75. }