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.

shape.tsx 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import React, { useCallback, useRef, memo } from 'react'
  2. import state, { useSelector } from 'state'
  3. import inputs from 'state/inputs'
  4. import styled from 'styles'
  5. import { getShapeUtils } from 'lib/shape-utils'
  6. import { getPage } from 'utils/utils'
  7. import { ShapeStyles } from 'types'
  8. function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
  9. const isHovered = useSelector((state) => state.data.hoveredId === id)
  10. const isSelected = useSelector((state) => state.values.selectedIds.has(id))
  11. const shape = useSelector(({ data }) => getPage(data).shapes[id])
  12. const rGroup = useRef<SVGGElement>(null)
  13. const handlePointerDown = useCallback(
  14. (e: React.PointerEvent) => {
  15. e.stopPropagation()
  16. rGroup.current.setPointerCapture(e.pointerId)
  17. state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
  18. },
  19. [id]
  20. )
  21. const handlePointerUp = useCallback(
  22. (e: React.PointerEvent) => {
  23. e.stopPropagation()
  24. rGroup.current.releasePointerCapture(e.pointerId)
  25. state.send('STOPPED_POINTING', inputs.pointerUp(e))
  26. },
  27. [id]
  28. )
  29. const handlePointerEnter = useCallback(
  30. (e: React.PointerEvent) => {
  31. state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
  32. },
  33. [id, shape]
  34. )
  35. const handlePointerMove = useCallback(
  36. (e: React.PointerEvent) => {
  37. state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
  38. },
  39. [id, shape]
  40. )
  41. const handlePointerLeave = useCallback(
  42. () => state.send('UNHOVERED_SHAPE', { target: id }),
  43. [id]
  44. )
  45. // This is a problem with deleted shapes. The hooks in this component
  46. // may sometimes run before the hook in the Page component, which means
  47. // a deleted shape will still be pulled here before the page component
  48. // detects the change and pulls this component.
  49. if (!shape) return null
  50. return (
  51. <StyledGroup
  52. ref={rGroup}
  53. isHovered={isHovered}
  54. isSelected={isSelected}
  55. transform={`rotate(${shape.rotation * (180 / Math.PI)},${getShapeUtils(
  56. shape
  57. ).getCenter(shape)}) translate(${shape.point})`}
  58. onPointerDown={handlePointerDown}
  59. onPointerUp={handlePointerUp}
  60. onPointerEnter={handlePointerEnter}
  61. onPointerLeave={handlePointerLeave}
  62. onPointerMove={handlePointerMove}
  63. >
  64. {isSelecting && <HoverIndicator as="use" href={'#' + id} />}
  65. <StyledShape id={id} style={shape.style} />
  66. </StyledGroup>
  67. )
  68. }
  69. const StyledShape = memo(
  70. ({ id, style }: { id: string; style: ShapeStyles }) => {
  71. return <MainShape as="use" href={'#' + id} {...style} />
  72. }
  73. )
  74. const MainShape = styled('use', {
  75. zStrokeWidth: 1,
  76. })
  77. const HoverIndicator = styled('path', {
  78. fill: 'none',
  79. stroke: 'transparent',
  80. pointerEvents: 'all',
  81. strokeLinecap: 'round',
  82. strokeLinejoin: 'round',
  83. transform: 'all .2s',
  84. })
  85. const StyledGroup = styled('g', {
  86. [`& ${HoverIndicator}`]: {
  87. opacity: '0',
  88. },
  89. variants: {
  90. isSelected: {
  91. true: {},
  92. false: {},
  93. },
  94. isHovered: {
  95. true: {},
  96. false: {},
  97. },
  98. },
  99. compoundVariants: [
  100. {
  101. isSelected: true,
  102. isHovered: true,
  103. css: {
  104. [`& ${HoverIndicator}`]: {
  105. opacity: '1',
  106. stroke: '$hint',
  107. zStrokeWidth: [8, 4],
  108. },
  109. },
  110. },
  111. {
  112. isSelected: true,
  113. isHovered: false,
  114. css: {
  115. [`& ${HoverIndicator}`]: {
  116. opacity: '1',
  117. stroke: '$hint',
  118. zStrokeWidth: [6, 3],
  119. },
  120. },
  121. },
  122. {
  123. isSelected: false,
  124. isHovered: true,
  125. css: {
  126. [`& ${HoverIndicator}`]: {
  127. opacity: '1',
  128. stroke: '$hint',
  129. zStrokeWidth: [8, 4],
  130. },
  131. },
  132. },
  133. ],
  134. })
  135. export { HoverIndicator }
  136. export default memo(Shape)