useMoveShapeKeypoint.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { useCallback } from 'react';
  2. import type { PPTElement, PPTShapeElement } from '@/lib/types/slides';
  3. import { useHistorySnapshot } from '@/lib/hooks/use-history-snapshot';
  4. import { SHAPE_PATH_FORMULAS } from '@/configs/shapes';
  5. import { useCanvasOperations } from '@/lib/hooks/use-canvas-operations';
  6. interface ShapePathData {
  7. baseSize: number;
  8. originPos: number;
  9. min: number;
  10. max: number;
  11. relative: string;
  12. }
  13. /**
  14. * Move shape keypoint Hook
  15. *
  16. * @param elementListRef - Element list ref (used to read the latest value on mouseup)
  17. * @param setElementList - Element list setter (used to trigger re-render)
  18. * @param canvasScale - Canvas scale ratio
  19. */
  20. export function useMoveShapeKeypoint(
  21. elementListRef: React.RefObject<PPTElement[]>,
  22. setElementList: React.Dispatch<React.SetStateAction<PPTElement[]>>,
  23. canvasScale: number,
  24. ) {
  25. const updateSlide = useCanvasOperations().updateSlide;
  26. const { addHistorySnapshot } = useHistorySnapshot();
  27. const moveShapeKeypoint = useCallback(
  28. (e: React.MouseEvent | React.TouchEvent, element: PPTShapeElement, index = 0) => {
  29. const native = e.nativeEvent;
  30. const isTouchEvent = native instanceof TouchEvent;
  31. if (isTouchEvent && !native.changedTouches?.length) return;
  32. let isMouseDown = true;
  33. const startPageX = isTouchEvent ? native.changedTouches[0].pageX : native.pageX;
  34. const startPageY = isTouchEvent ? native.changedTouches[0].pageY : native.pageY;
  35. const originKeypoints = element.keypoints!;
  36. const pathFormula = SHAPE_PATH_FORMULAS[element.pathFormula!];
  37. let shapePathData: ShapePathData | null = null;
  38. if ('editable' in pathFormula && pathFormula.editable) {
  39. const getBaseSize = pathFormula.getBaseSize![index];
  40. const range = pathFormula.range![index];
  41. const relative = pathFormula.relative![index];
  42. const keypoint = originKeypoints[index];
  43. const baseSize = getBaseSize(element.width, element.height);
  44. const originPos = baseSize * keypoint;
  45. const [min, max] = range;
  46. shapePathData = { baseSize, originPos, min, max, relative };
  47. }
  48. const handleMouseMove = (e: MouseEvent | TouchEvent) => {
  49. if (!isMouseDown) return;
  50. const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX;
  51. const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY;
  52. const moveX = (currentPageX - startPageX) / canvasScale;
  53. const moveY = (currentPageY - startPageY) / canvasScale;
  54. // Update local element list during mousemove
  55. const newElements = elementListRef.current.map((el) => {
  56. if (el.id === element.id && shapePathData) {
  57. const { baseSize, originPos, min, max, relative } = shapePathData;
  58. const shapeElement = el as PPTShapeElement;
  59. let keypoint = 0;
  60. if (relative === 'center') keypoint = (originPos - moveX * 2) / baseSize;
  61. else if (relative === 'left') keypoint = (originPos + moveX) / baseSize;
  62. else if (relative === 'right') keypoint = (originPos - moveX) / baseSize;
  63. else if (relative === 'top') keypoint = (originPos + moveY) / baseSize;
  64. else if (relative === 'bottom') keypoint = (originPos - moveY) / baseSize;
  65. else if (relative === 'left_bottom') keypoint = (originPos + moveX) / baseSize;
  66. else if (relative === 'right_bottom') keypoint = (originPos - moveX) / baseSize;
  67. else if (relative === 'top_right') keypoint = (originPos + moveY) / baseSize;
  68. else if (relative === 'bottom_right') keypoint = (originPos - moveY) / baseSize;
  69. if (keypoint < min) keypoint = min;
  70. if (keypoint > max) keypoint = max;
  71. let keypoints: number[] = [];
  72. if (Array.isArray(originKeypoints)) {
  73. keypoints = [...originKeypoints];
  74. keypoints[index] = keypoint;
  75. } else keypoints = [keypoint];
  76. return {
  77. ...el,
  78. keypoints,
  79. path: pathFormula.formula(shapeElement.width, shapeElement.height, keypoints),
  80. };
  81. }
  82. return el;
  83. });
  84. // Update both ref and state
  85. elementListRef.current = newElements;
  86. setElementList(newElements);
  87. };
  88. const handleMouseUp = (e: MouseEvent | TouchEvent) => {
  89. isMouseDown = false;
  90. document.ontouchmove = null;
  91. document.ontouchend = null;
  92. document.onmousemove = null;
  93. document.onmouseup = null;
  94. const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX;
  95. const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY;
  96. if (startPageX === currentPageX && startPageY === currentPageY) return;
  97. updateSlide({ elements: elementListRef.current });
  98. addHistorySnapshot();
  99. };
  100. if (isTouchEvent) {
  101. document.ontouchmove = handleMouseMove;
  102. document.ontouchend = handleMouseUp;
  103. } else {
  104. document.onmousemove = handleMouseMove;
  105. document.onmouseup = handleMouseUp;
  106. }
  107. },
  108. [elementListRef, setElementList, canvasScale, updateSlide, addHistorySnapshot],
  109. );
  110. return {
  111. moveShapeKeypoint,
  112. };
  113. }