feat: Move route-preview
This commit is contained in:
204
src/pages/Route/route-preview/TransformContext.tsx
Normal file
204
src/pages/Route/route-preview/TransformContext.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { SCALE_FACTOR, UP_SCALE } from "./Constants";
|
||||
|
||||
const TransformContext = createContext<{
|
||||
position: { x: number; y: number };
|
||||
scale: number;
|
||||
rotation: number;
|
||||
screenCenter?: { x: number; y: number };
|
||||
|
||||
setPosition: React.Dispatch<React.SetStateAction<{ x: number; y: number }>>;
|
||||
setScale: React.Dispatch<React.SetStateAction<number>>;
|
||||
setRotation: React.Dispatch<React.SetStateAction<number>>;
|
||||
screenToLocal: (x: number, y: number) => { x: number; y: number };
|
||||
localToScreen: (x: number, y: number) => { x: number; y: number };
|
||||
rotateToAngle: (to: number, fromPosition?: { x: number; y: number }) => void;
|
||||
setTransform: (
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
rotationDegrees?: number,
|
||||
scale?: number
|
||||
) => void;
|
||||
setScreenCenter: React.Dispatch<
|
||||
React.SetStateAction<{ x: number; y: number } | undefined>
|
||||
>;
|
||||
}>({
|
||||
position: { x: 0, y: 0 },
|
||||
scale: 1,
|
||||
rotation: 0,
|
||||
screenCenter: undefined,
|
||||
setPosition: () => {},
|
||||
setScale: () => {},
|
||||
setRotation: () => {},
|
||||
screenToLocal: () => ({ x: 0, y: 0 }),
|
||||
localToScreen: () => ({ x: 0, y: 0 }),
|
||||
rotateToAngle: () => {},
|
||||
setTransform: () => {},
|
||||
setScreenCenter: () => {},
|
||||
});
|
||||
|
||||
// Provider component
|
||||
export const TransformProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [rotation, setRotation] = useState(0);
|
||||
const [screenCenter, setScreenCenter] = useState<{ x: number; y: number }>();
|
||||
|
||||
const screenToLocal = useCallback(
|
||||
(screenX: number, screenY: number) => {
|
||||
// Translate point relative to current pan position
|
||||
const translatedX = (screenX - position.x) / scale;
|
||||
const translatedY = (screenY - position.y) / scale;
|
||||
|
||||
// Rotate point around center
|
||||
const cosRotation = Math.cos(-rotation); // Negative rotation to reverse transform
|
||||
const sinRotation = Math.sin(-rotation);
|
||||
const rotatedX = translatedX * cosRotation - translatedY * sinRotation;
|
||||
const rotatedY = translatedX * sinRotation + translatedY * cosRotation;
|
||||
|
||||
return {
|
||||
x: rotatedX / UP_SCALE,
|
||||
y: rotatedY / UP_SCALE,
|
||||
};
|
||||
},
|
||||
[position.x, position.y, scale, rotation]
|
||||
);
|
||||
|
||||
// Inverse of screenToLocal
|
||||
const localToScreen = useCallback(
|
||||
(localX: number, localY: number) => {
|
||||
const upscaledX = localX * UP_SCALE;
|
||||
const upscaledY = localY * UP_SCALE;
|
||||
|
||||
const cosRotation = Math.cos(rotation);
|
||||
const sinRotation = Math.sin(rotation);
|
||||
const rotatedX = upscaledX * cosRotation - upscaledY * sinRotation;
|
||||
const rotatedY = upscaledX * sinRotation + upscaledY * cosRotation;
|
||||
|
||||
const translatedX = rotatedX * scale + position.x;
|
||||
const translatedY = rotatedY * scale + position.y;
|
||||
|
||||
return {
|
||||
x: translatedX,
|
||||
y: translatedY,
|
||||
};
|
||||
},
|
||||
[position.x, position.y, scale, rotation]
|
||||
);
|
||||
|
||||
const rotateToAngle = useCallback(
|
||||
(to: number, fromPosition?: { x: number; y: number }) => {
|
||||
const rotationDiff = to - rotation;
|
||||
|
||||
const center = screenCenter ?? { x: 0, y: 0 };
|
||||
const cosDelta = Math.cos(rotationDiff);
|
||||
const sinDelta = Math.sin(rotationDiff);
|
||||
|
||||
const currentFromPosition = fromPosition ?? position;
|
||||
|
||||
const newPosition = {
|
||||
x:
|
||||
center.x * (1 - cosDelta) +
|
||||
currentFromPosition.x * cosDelta +
|
||||
(center.y - currentFromPosition.y) * sinDelta,
|
||||
y:
|
||||
center.y * (1 - cosDelta) +
|
||||
currentFromPosition.y * cosDelta +
|
||||
(currentFromPosition.x - center.x) * sinDelta,
|
||||
};
|
||||
|
||||
// Update both rotation and position in a single batch to avoid stale closure
|
||||
setRotation(to);
|
||||
setPosition(newPosition);
|
||||
},
|
||||
[rotation, position, screenCenter]
|
||||
);
|
||||
|
||||
const setTransform = useCallback(
|
||||
(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
rotationDegrees?: number,
|
||||
useScale?: number
|
||||
) => {
|
||||
const selectedRotation =
|
||||
rotationDegrees !== undefined
|
||||
? (rotationDegrees * Math.PI) / 180
|
||||
: rotation;
|
||||
const selectedScale =
|
||||
useScale !== undefined ? useScale / SCALE_FACTOR : scale;
|
||||
const center = screenCenter ?? { x: 0, y: 0 };
|
||||
|
||||
console.log("center", center.x, center.y);
|
||||
|
||||
const newPosition = {
|
||||
x: -latitude * UP_SCALE * selectedScale,
|
||||
y: -longitude * UP_SCALE * selectedScale,
|
||||
};
|
||||
|
||||
const cosRot = Math.cos(selectedRotation);
|
||||
const sinRot = Math.sin(selectedRotation);
|
||||
|
||||
// Translate point relative to center, rotate, then translate back
|
||||
const dx = newPosition.x;
|
||||
const dy = newPosition.y;
|
||||
newPosition.x = dx * cosRot - dy * sinRot + center.x;
|
||||
newPosition.y = dx * sinRot + dy * cosRot + center.y;
|
||||
|
||||
// Batch state updates to avoid intermediate renders
|
||||
setPosition(newPosition);
|
||||
setRotation(selectedRotation);
|
||||
setScale(selectedScale);
|
||||
},
|
||||
[rotation, scale, screenCenter]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
position,
|
||||
scale,
|
||||
rotation,
|
||||
screenCenter,
|
||||
setPosition,
|
||||
setScale,
|
||||
setRotation,
|
||||
rotateToAngle,
|
||||
screenToLocal,
|
||||
localToScreen,
|
||||
setTransform,
|
||||
setScreenCenter,
|
||||
}),
|
||||
[
|
||||
position,
|
||||
scale,
|
||||
rotation,
|
||||
screenCenter,
|
||||
rotateToAngle,
|
||||
screenToLocal,
|
||||
localToScreen,
|
||||
setTransform,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<TransformContext.Provider value={value}>
|
||||
{children}
|
||||
</TransformContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Custom hook for easy access to transform values
|
||||
export const useTransform = () => {
|
||||
const context = useContext(TransformContext);
|
||||
if (!context) {
|
||||
throw new Error("useTransform must be used within a TransformProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
Reference in New Issue
Block a user