File size: 4,744 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 | import { useCallback, type RefObject } from 'react';
import type {
PPTElement,
PPTLineElement,
PPTVideoElement,
PPTAudioElement,
PPTChartElement,
} from '@/lib/types/slides';
import { useHistorySnapshot } from '@/lib/hooks/use-history-snapshot';
import { useCanvasOperations } from '@/lib/hooks/use-canvas-operations';
/**
* Calculate the angle (in radians) of the line from the origin to the given coordinates
* @param x Coordinate x
* @param y Coordinate y
*/
const getAngleFromCoordinate = (x: number, y: number) => {
const radian = Math.atan2(x, y);
const angle = (180 / Math.PI) * radian;
return angle;
};
/**
* Rotate element Hook
*
* @param elementListRef - Element list ref (stores the latest value)
* @param setElementList - Element list setter (used to trigger re-render)
* @param viewportRef - Viewport reference
* @param canvasScale - Canvas scale ratio
*/
export function useRotateElement(
elementListRef: React.RefObject<PPTElement[]>,
setElementList: React.Dispatch<React.SetStateAction<PPTElement[]>>,
viewportRef: RefObject<HTMLElement | null>,
canvasScale: number,
) {
const updateSlide = useCanvasOperations().updateSlide;
const { addHistorySnapshot } = useHistorySnapshot();
// Rotate element
const rotateElement = useCallback(
(
e: React.MouseEvent | React.TouchEvent,
element: Exclude<
PPTElement,
PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement
>,
) => {
const native = e.nativeEvent;
const isTouchEvent = native instanceof TouchEvent;
if (isTouchEvent && !native.changedTouches?.length) return;
let isMouseDown = true;
let angle = 0;
const elOriginRotate = element.rotate || 0;
const elLeft = element.left;
const elTop = element.top;
const elWidth = element.width;
const elHeight = element.height;
// Element center point (rotation center)
const centerX = elLeft + elWidth / 2;
const centerY = elTop + elHeight / 2;
if (!viewportRef.current) return;
const viewportRect = viewportRef.current.getBoundingClientRect();
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;
// Calculate the angle of the line from the current mouse position to the element center
const mouseX = (currentPageX - viewportRect.left) / canvasScale;
const mouseY = (currentPageY - viewportRect.top) / canvasScale;
const x = mouseX - centerX;
const y = centerY - mouseY;
angle = getAngleFromCoordinate(x, y);
// Snap to multiples of 45 degrees when close
const sorptionRange = 5;
if (Math.abs(angle) <= sorptionRange) angle = 0;
else if (angle > 0 && Math.abs(angle - 45) <= sorptionRange) angle -= angle - 45;
else if (angle < 0 && Math.abs(angle + 45) <= sorptionRange) angle -= angle + 45;
else if (angle > 0 && Math.abs(angle - 90) <= sorptionRange) angle -= angle - 90;
else if (angle < 0 && Math.abs(angle + 90) <= sorptionRange) angle -= angle + 90;
else if (angle > 0 && Math.abs(angle - 135) <= sorptionRange) angle -= angle - 135;
else if (angle < 0 && Math.abs(angle + 135) <= sorptionRange) angle -= angle + 135;
else if (angle > 0 && Math.abs(angle - 180) <= sorptionRange) angle -= angle - 180;
else if (angle < 0 && Math.abs(angle + 180) <= sorptionRange) angle -= angle + 180;
const newElements = elementListRef.current.map((el) => {
if (el.id === element.id && 'rotate' in el) {
return { ...el, rotate: angle };
}
return el;
});
// Update both ref and state
elementListRef.current = newElements;
setElementList(newElements);
};
const handleMouseUp = () => {
isMouseDown = false;
document.onmousemove = null;
document.onmouseup = null;
document.ontouchmove = null;
document.ontouchend = null;
if (elOriginRotate === angle) return;
updateSlide({ elements: elementListRef.current });
addHistorySnapshot();
};
if (isTouchEvent) {
document.ontouchmove = handleMouseMove;
document.ontouchend = handleMouseUp;
} else {
document.onmousemove = handleMouseMove;
document.onmouseup = handleMouseUp;
}
},
[elementListRef, setElementList, viewportRef, canvasScale, updateSlide, addHistorySnapshot],
);
return {
rotateElement,
};
}
|