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.

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