index.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import React, { useEffect, useState } from 'react';
  2. import theme from '../../helpers/theme';
  3. import useCursorPosition from '../../hooks/useCursorPosition';
  4. import { getAbsoluteCoordinate } from '../../helpers/position';
  5. import { calcDragAndDropScale } from '../../helpers/utility';
  6. import generateCirclesData from './data';
  7. import { SVG, Rect, Circle } from './styled';
  8. type Props = {
  9. left: number;
  10. top: number;
  11. width: number;
  12. height: number;
  13. onMove?: (moveCoord: CoordType) => void;
  14. onScale?: (scaleCoord: CoordType) => void;
  15. onClick?: (event: React.MouseEvent) => void;
  16. onDoubleClick?: () => void;
  17. onMouseUp?: () => void;
  18. };
  19. type ObjPositionType = {
  20. top: number;
  21. left: number;
  22. width: number;
  23. height: number;
  24. operator: string;
  25. clickX: number;
  26. clickY: number;
  27. };
  28. const initState = {
  29. top: 0,
  30. left: 0,
  31. width: 0,
  32. height: 0,
  33. operator: '',
  34. clickX: 0,
  35. clickY: 0,
  36. };
  37. const OuterRect: React.FC<Props> = ({
  38. left,
  39. top,
  40. width,
  41. height,
  42. onMove,
  43. onScale,
  44. onClick,
  45. onDoubleClick,
  46. onMouseUp,
  47. }: Props) => {
  48. const data = generateCirclesData(width, height);
  49. const [state, setState] = useState(initState);
  50. const [cursorPosition, setRef] = useCursorPosition(25);
  51. const handleMouseDown = (e: React.MouseEvent | React.TouchEvent): void => {
  52. e.preventDefault();
  53. const operatorId = (e.target as HTMLElement).getAttribute(
  54. 'data-id',
  55. ) as string;
  56. const coord = getAbsoluteCoordinate(document.body, e);
  57. setRef(document.body);
  58. setState({
  59. top,
  60. left,
  61. width,
  62. height,
  63. operator: operatorId,
  64. clickX: coord.x,
  65. clickY: coord.y,
  66. });
  67. };
  68. const handleMouseUp = (): void => {
  69. setRef(null);
  70. setState(initState);
  71. if (onMouseUp) {
  72. onMouseUp();
  73. }
  74. };
  75. const calcMoveResult = (
  76. currentPosition: PointType,
  77. startPosition: PointType,
  78. objPosition: ObjPositionType,
  79. ): CoordType => ({
  80. left: currentPosition.x - (startPosition.x - objPosition.left),
  81. top: currentPosition.y - (startPosition.y - objPosition.top),
  82. width: objPosition.width,
  83. height: objPosition.height,
  84. });
  85. const calcScaleResult = (
  86. currentPosition: PointType,
  87. objPosition: ObjPositionType,
  88. ): CoordType => {
  89. const scaleData = calcDragAndDropScale({
  90. ...objPosition,
  91. moveX: currentPosition.x || 0,
  92. moveY: currentPosition.y || 0,
  93. });
  94. const scaleWidth = scaleData.width || 0;
  95. const scaleHeight = scaleData.height || 0;
  96. const maxTop = objPosition.top + objPosition.height;
  97. const maxLeft = objPosition.left + objPosition.width;
  98. scaleData.left = scaleData.left > maxLeft ? maxLeft : scaleData.left;
  99. scaleData.top = scaleData.top > maxTop ? maxTop : scaleData.top;
  100. scaleData.width = scaleWidth > 0 ? scaleWidth : 0;
  101. scaleData.height = scaleHeight > 0 ? scaleHeight : 0;
  102. return scaleData;
  103. };
  104. useEffect(() => {
  105. if (cursorPosition.x && cursorPosition.y && state.clickX) {
  106. if (state.operator === 'move' && onMove) {
  107. onMove(
  108. calcMoveResult(
  109. { x: cursorPosition.x, y: cursorPosition.y },
  110. { x: state.clickX, y: state.clickY },
  111. state,
  112. ),
  113. );
  114. } else if (onScale) {
  115. onScale(
  116. calcScaleResult(
  117. {
  118. x: cursorPosition.x,
  119. y: cursorPosition.y,
  120. },
  121. state,
  122. ),
  123. );
  124. }
  125. }
  126. }, [cursorPosition, state]);
  127. useEffect(() => {
  128. window.addEventListener('mouseup', handleMouseUp);
  129. window.addEventListener('touchend', handleMouseUp);
  130. return (): void => {
  131. window.removeEventListener('mouseup', handleMouseUp);
  132. window.removeEventListener('touchend', handleMouseUp);
  133. };
  134. }, []);
  135. return (
  136. <SVG
  137. style={{
  138. left: `${left - 12}px`,
  139. top: `${top - 12}px`,
  140. width: `${width + 24}px`,
  141. height: `${height + 24}px`,
  142. }}
  143. onClick={onClick}
  144. onDoubleClick={onDoubleClick}
  145. >
  146. <Rect
  147. x={6}
  148. y={6}
  149. width={width + 12}
  150. height={height + 12}
  151. stroke={onScale ? theme.colors.primary : 'transparency'}
  152. onMouseDown={handleMouseDown}
  153. onTouchStart={handleMouseDown}
  154. data-id="move"
  155. />
  156. {onScale &&
  157. data.map((attr: CircleType) => (
  158. <Circle
  159. key={attr.direction}
  160. data-id={attr.direction}
  161. onMouseDown={handleMouseDown}
  162. onTouchStart={handleMouseDown}
  163. fill={theme.colors.primary}
  164. {...attr}
  165. />
  166. ))}
  167. </SVG>
  168. );
  169. };
  170. export default OuterRect;