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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import React, { useCallback, useRef, memo } from "react"
  2. import state, { useSelector } from "state"
  3. import inputs from "state/inputs"
  4. import { getShapeUtils } from "lib/shapes"
  5. import styled from "styles"
  6. function Shape({ id }: { id: string }) {
  7. const rGroup = useRef<SVGGElement>(null)
  8. const isHovered = useSelector((state) => state.data.hoveredId === id)
  9. const isSelected = useSelector((state) => state.values.selectedIds.has(id))
  10. const shape = useSelector(
  11. ({ data }) => data.document.pages[data.currentPageId].shapes[id]
  12. )
  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. <defs>{getShapeUtils(shape).render(shape)}</defs>
  65. <HoverIndicator as="use" xlinkHref={"#" + id} />
  66. <MainShape as="use" xlinkHref={"#" + id} {...shape.style} />
  67. <Indicator as="use" xlinkHref={"#" + id} />
  68. </StyledGroup>
  69. )
  70. }
  71. const MainShape = styled("use", {
  72. zStrokeWidth: 1,
  73. })
  74. const Indicator = styled("path", {
  75. fill: "none",
  76. stroke: "transparent",
  77. zStrokeWidth: 1,
  78. pointerEvents: "none",
  79. strokeLineCap: "round",
  80. strokeLinejoin: "round",
  81. })
  82. const HoverIndicator = styled("path", {
  83. fill: "none",
  84. stroke: "transparent",
  85. zStrokeWidth: [8, 4],
  86. pointerEvents: "all",
  87. strokeLinecap: "round",
  88. strokeLinejoin: "round",
  89. transform: "all .2s",
  90. })
  91. const StyledGroup = styled("g", {
  92. [`& ${HoverIndicator}`]: {
  93. opacity: "0",
  94. },
  95. variants: {
  96. isSelected: {
  97. true: {
  98. [`& ${Indicator}`]: {
  99. stroke: "$selected",
  100. },
  101. },
  102. false: {},
  103. },
  104. isHovered: {
  105. true: {},
  106. false: {},
  107. },
  108. },
  109. compoundVariants: [
  110. {
  111. isSelected: true,
  112. isHovered: true,
  113. css: {
  114. [`& ${HoverIndicator}`]: {
  115. opacity: "1",
  116. stroke: "$hint",
  117. zStrokeWidth: [8, 4],
  118. },
  119. },
  120. },
  121. {
  122. isSelected: true,
  123. isHovered: false,
  124. css: {
  125. [`& ${HoverIndicator}`]: {
  126. opacity: "1",
  127. stroke: "$hint",
  128. zStrokeWidth: [6, 3],
  129. },
  130. },
  131. },
  132. {
  133. isSelected: false,
  134. isHovered: true,
  135. css: {
  136. [`& ${HoverIndicator}`]: {
  137. opacity: "1",
  138. stroke: "$hint",
  139. zStrokeWidth: [8, 4],
  140. },
  141. },
  142. },
  143. ],
  144. })
  145. export { Indicator, HoverIndicator }
  146. export default memo(Shape)