"use client";
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
import { useHeatmapData, type HeatmapPoint } from "@/hooks/use-heatmap-data";
import { GlobeControls } from "./globe-controls";
function GlobeLoading() {
const t = useTranslations("globe");
return (
);
}
const Globe = dynamic(() => import("react-globe.gl"), {
ssr: false,
loading: () => ,
});
interface ArcData {
startLat: number;
startLng: number;
endLat: number;
endLng: number;
color: string;
}
export function GlobeView() {
const t = useTranslations("globe");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const globeRef = useRef(undefined);
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [hoveredPoint, setHoveredPoint] = useState(null);
const { points } = useHeatmapData(30000);
// Generate arcs from recent activity (connecting pairs of active points)
const arcs = useMemo((): ArcData[] => {
if (points.length < 2) return [];
const result: ArcData[] = [];
const maxArcs = Math.min(points.length - 1, 8);
for (let i = 0; i < maxArcs; i++) {
const from = points[i];
const to = points[(i + 1) % points.length];
result.push({
startLat: from.lat,
startLng: from.lng,
endLat: to.lat,
endLng: to.lng,
color: "rgba(0, 240, 255, 0.4)",
});
}
return result;
}, [points]);
useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
setDimensions({
width: containerRef.current.clientWidth,
height: containerRef.current.clientHeight,
});
}
};
updateDimensions();
const observer = new ResizeObserver(updateDimensions);
if (containerRef.current) observer.observe(containerRef.current);
return () => observer.disconnect();
}, []);
useEffect(() => {
if (globeRef.current) {
globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 2.5 });
}
}, [dimensions]);
const handleResetView = useCallback(() => {
if (globeRef.current) {
globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 2.5 });
}
}, []);
const handleZoomIn = useCallback(() => {
if (globeRef.current) {
globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 1.5 });
}
}, []);
const handleZoomOut = useCallback(() => {
if (globeRef.current) {
globeRef.current.pointOfView({ lat: 20, lng: 100, altitude: 3.5 });
}
}, []);
return (
{dimensions.width > 0 && (
(d as HeatmapPoint).lat}
pointLng={(d: object) => (d as HeatmapPoint).lng}
pointAltitude={(d: object) => Math.min((d as HeatmapPoint).weight * 0.02, 0.15)}
pointRadius={(d: object) => Math.max((d as HeatmapPoint).weight * 0.3, 0.4)}
pointColor={() => "#00f0ff"}
pointsMerge={false}
onPointHover={(point: object | null) => setHoveredPoint(point as HeatmapPoint | null)}
// Arcs
arcsData={arcs}
arcStartLat={(d: object) => (d as ArcData).startLat}
arcStartLng={(d: object) => (d as ArcData).startLng}
arcEndLat={(d: object) => (d as ArcData).endLat}
arcEndLng={(d: object) => (d as ArcData).endLng}
arcColor={(d: object) => (d as ArcData).color}
arcDashLength={0.5}
arcDashGap={0.2}
arcDashAnimateTime={2000}
arcStroke={0.3}
// Auto-rotate
animateIn={true}
enablePointerInteraction={true}
/>
)}
{/* Tooltip */}
{hoveredPoint && (
🦞
{hoveredPoint.city}
{hoveredPoint.country}
{t("activeClaws", { count: hoveredPoint.clawCount })}
)}
);
}