Update city radius values and add Miami; refine map styles and improve OpenSky code readability
This commit is contained in:
111
src/components/map/camera-controller.tsx
Normal file
111
src/components/map/camera-controller.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useMap } from "./map";
|
||||
import { useSettings } from "@/hooks/use-settings";
|
||||
import type { City } from "@/lib/cities";
|
||||
|
||||
const IDLE_TIMEOUT_MS = 5_000;
|
||||
|
||||
export function CameraController({ city }: { city: City }) {
|
||||
const { map, isLoaded } = useMap();
|
||||
const { settings } = useSettings();
|
||||
const prevCityRef = useRef<string | null>(null);
|
||||
const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const orbitFrameRef = useRef<number | null>(null);
|
||||
const isInteractingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!map || !isLoaded || !city) return;
|
||||
if (city.id === prevCityRef.current) return;
|
||||
|
||||
prevCityRef.current = city.id;
|
||||
map.flyTo({
|
||||
center: city.coordinates,
|
||||
zoom: 9.2,
|
||||
pitch: 49,
|
||||
bearing: 27.4,
|
||||
duration: 2800,
|
||||
essential: true,
|
||||
});
|
||||
}, [map, isLoaded, city]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!map || !isLoaded || !city || !settings.autoOrbit) {
|
||||
if (orbitFrameRef.current) cancelAnimationFrame(orbitFrameRef.current);
|
||||
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
|
||||
return;
|
||||
}
|
||||
|
||||
const prefersReducedMotion =
|
||||
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
||||
if (prefersReducedMotion) return;
|
||||
|
||||
const directionMultiplier =
|
||||
settings.orbitDirection === "clockwise" ? 1 : -1;
|
||||
const speed = settings.orbitSpeed * directionMultiplier;
|
||||
|
||||
function startOrbit() {
|
||||
if (!map || isInteractingRef.current) return;
|
||||
|
||||
function tick() {
|
||||
if (!map || isInteractingRef.current) return;
|
||||
const bearing = map.getBearing() + speed;
|
||||
map.setBearing(bearing % 360);
|
||||
orbitFrameRef.current = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
orbitFrameRef.current = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
function stopOrbit() {
|
||||
if (orbitFrameRef.current) {
|
||||
cancelAnimationFrame(orbitFrameRef.current);
|
||||
orbitFrameRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetIdleTimer() {
|
||||
isInteractingRef.current = true;
|
||||
stopOrbit();
|
||||
|
||||
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
|
||||
idleTimerRef.current = setTimeout(() => {
|
||||
isInteractingRef.current = false;
|
||||
startOrbit();
|
||||
}, IDLE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
const events = ["mousedown", "wheel", "touchstart"] as const;
|
||||
const container = map.getContainer();
|
||||
events.forEach((e) =>
|
||||
container.addEventListener(e, resetIdleTimer, { passive: true }),
|
||||
);
|
||||
|
||||
const onMoveStart = () => {
|
||||
if (isInteractingRef.current) stopOrbit();
|
||||
};
|
||||
map.on("movestart", onMoveStart);
|
||||
|
||||
idleTimerRef.current = setTimeout(() => {
|
||||
isInteractingRef.current = false;
|
||||
startOrbit();
|
||||
}, IDLE_TIMEOUT_MS);
|
||||
|
||||
return () => {
|
||||
stopOrbit();
|
||||
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
|
||||
events.forEach((e) => container.removeEventListener(e, resetIdleTimer));
|
||||
map.off("movestart", onMoveStart);
|
||||
};
|
||||
}, [
|
||||
map,
|
||||
isLoaded,
|
||||
city,
|
||||
settings.autoOrbit,
|
||||
settings.orbitSpeed,
|
||||
settings.orbitDirection,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user