index.tsx 4.5 KB

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