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