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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import useShapeEvents from 'hooks/useShapeEvents'
  2. import { Shape as _Shape, ShapeType, TextShape } from 'types'
  3. import { getShapeUtils } from 'state/shape-utils'
  4. import { shallowEqual } from 'utils'
  5. import { memo, useRef } from 'react'
  6. import styled from 'styles'
  7. interface ShapeProps {
  8. shape: _Shape
  9. isEditing: boolean
  10. isHovered: boolean
  11. isSelected: boolean
  12. isDarkMode: boolean
  13. isCurrentParent: boolean
  14. }
  15. const Shape = memo(
  16. ({
  17. shape,
  18. isEditing,
  19. isHovered,
  20. isSelected,
  21. isDarkMode,
  22. isCurrentParent,
  23. }: ShapeProps) => {
  24. const rGroup = useRef<SVGGElement>(null)
  25. const events = useShapeEvents(shape.id, isCurrentParent, rGroup)
  26. const utils = getShapeUtils(shape)
  27. const center = utils.getCenter(shape)
  28. const rotation = shape.rotation * (180 / Math.PI)
  29. const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
  30. return (
  31. <ShapeGroup
  32. ref={rGroup}
  33. id={shape.id}
  34. transform={transform}
  35. isCurrentParent={isCurrentParent}
  36. filter={isHovered ? 'url(#expand)' : 'none'}
  37. {...events}
  38. >
  39. {isEditing && shape.type === ShapeType.Text ? (
  40. <EditingTextShape shape={shape} isDarkMode={isDarkMode} />
  41. ) : (
  42. <RenderedShape
  43. shape={shape}
  44. isEditing={isEditing}
  45. isHovered={isHovered}
  46. isSelected={isSelected}
  47. isDarkMode={isDarkMode}
  48. isCurrentParent={isCurrentParent}
  49. />
  50. )}
  51. </ShapeGroup>
  52. )
  53. },
  54. shallowEqual
  55. )
  56. export default Shape
  57. interface RenderedShapeProps {
  58. shape: _Shape
  59. isEditing: boolean
  60. isHovered: boolean
  61. isSelected: boolean
  62. isDarkMode: boolean
  63. isCurrentParent: boolean
  64. }
  65. const RenderedShape = memo(
  66. function RenderedShape({
  67. shape,
  68. isEditing,
  69. isHovered,
  70. isSelected,
  71. isDarkMode,
  72. isCurrentParent,
  73. }: RenderedShapeProps) {
  74. return getShapeUtils(shape).render(shape, {
  75. isEditing,
  76. isHovered,
  77. isSelected,
  78. isDarkMode,
  79. isCurrentParent,
  80. })
  81. },
  82. (prev, next) => {
  83. if (
  84. prev.isEditing !== next.isEditing ||
  85. prev.isHovered !== next.isHovered ||
  86. prev.isSelected !== next.isSelected ||
  87. prev.isDarkMode !== next.isDarkMode ||
  88. prev.isCurrentParent !== next.isCurrentParent
  89. ) {
  90. return false
  91. }
  92. if (next.shape !== prev.shape) {
  93. return !getShapeUtils(next.shape).shouldRender(next.shape, prev.shape)
  94. }
  95. return true
  96. }
  97. )
  98. function EditingTextShape({
  99. shape,
  100. isDarkMode,
  101. }: {
  102. shape: TextShape
  103. isDarkMode: boolean
  104. }) {
  105. const ref = useRef<HTMLTextAreaElement>(null)
  106. return getShapeUtils(shape).render(shape, {
  107. ref,
  108. isEditing: true,
  109. isHovered: false,
  110. isSelected: false,
  111. isDarkMode,
  112. isCurrentParent: false,
  113. })
  114. }
  115. const ShapeGroup = styled('g', {
  116. outline: 'none',
  117. '& > *[data-shy=true]': {
  118. opacity: 0,
  119. },
  120. '&:hover': {
  121. '& > *[data-shy=true]': {
  122. opacity: 1,
  123. },
  124. },
  125. variants: {
  126. isCurrentParent: {
  127. true: {
  128. '& > *[data-shy=true]': {
  129. opacity: 1,
  130. },
  131. },
  132. },
  133. },
  134. })