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.

page.tsx 1.5KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import { getShapeUtils } from 'state/shape-utils'
  2. import { useSelector } from 'state'
  3. import { Bounds, PageState } from 'types'
  4. import { boundsCollide, boundsContain } from 'utils/bounds'
  5. import { deepCompareArrays, getPage, getViewport } from 'utils'
  6. import Shape from './shape'
  7. /*
  8. On each state change, compare node ids of all shapes
  9. on the current page. Kind of expensive but only happens
  10. here; and still cheaper than any other pattern I've found.
  11. */
  12. const noOffset = [0, 0]
  13. const viewportCache = new WeakMap<PageState, Bounds>()
  14. export default function Page(): JSX.Element {
  15. const currentPageShapeIds = useSelector((s) => {
  16. const page = getPage(s.data)
  17. const pageState = s.data.pageStates[page.id]
  18. if (!viewportCache.has(pageState)) {
  19. const viewport = getViewport(s.data)
  20. viewportCache.set(pageState, viewport)
  21. }
  22. const viewport = viewportCache.get(pageState)
  23. return s.values.currentShapes
  24. .filter((shape) => {
  25. const shapeBounds = getShapeUtils(shape).getBounds(shape)
  26. return (
  27. boundsContain(viewport, shapeBounds) ||
  28. boundsCollide(viewport, shapeBounds)
  29. )
  30. })
  31. .map((shape) => shape.id)
  32. }, deepCompareArrays)
  33. const isSelecting = useSelector((s) => s.isIn('selecting'))
  34. return (
  35. <g pointerEvents={isSelecting ? 'all' : 'none'}>
  36. {currentPageShapeIds.map((shapeId) => (
  37. <Shape
  38. key={shapeId}
  39. id={shapeId}
  40. isSelecting={isSelecting}
  41. parentPoint={noOffset}
  42. />
  43. ))}
  44. </g>
  45. )
  46. }