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.6KB

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