import { geoEquirectangular, geoPath } from "d3-geo"; import { useTranslation } from "react-i18next"; import useTooltip from "@/hooks/use-tooltip"; import { geoJsonString } from "@/lib/geo-json-string"; import { countryCoordinates } from "@/lib/geo-limit"; import { cn, formatNezhaInfo } from "@/lib/utils"; import type { NezhaServer } from "@/types/nezha-api"; import MapTooltip from "./MapTooltip"; export default function GlobalMap({ serverList, now, }: { serverList: NezhaServer[]; now: number; }) { const { t } = useTranslation(); const countryList: string[] = []; const serverCounts: { [key: string]: number } = {}; const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined; serverList.forEach((server) => { if (server.country_code) { const countryCode = server.country_code.toUpperCase(); if (!countryList.includes(countryCode)) { countryList.push(countryCode); } serverCounts[countryCode] = (serverCounts[countryCode] || 0) + 1; } }); const width = 900; const height = 500; const geoJson = JSON.parse(geoJsonString); const filteredFeatures = geoJson.features.filter( (feature: { properties: { iso_a3_eh: string } }) => feature.properties.iso_a3_eh !== "", ); return (

{t("map.Distributions")} {countryList.length} {t("map.Regions")}

); } interface InteractiveMapProps { countries: string[]; serverCounts: { [key: string]: number }; width: number; height: number; filteredFeatures: { type: "Feature"; properties: { iso_a2_eh: string; [key: string]: string; }; geometry: never; }[]; nezhaServerList: NezhaServer[]; now: number; } export function InteractiveMap({ countries, serverCounts, width, height, filteredFeatures, nezhaServerList, now, }: InteractiveMapProps) { const { setTooltipData } = useTooltip(); const projection = geoEquirectangular() .scale(140) .translate([width / 2, height / 2]) .rotate([-12, 0, 0]); const path = geoPath().projection(projection); return (
setTooltipData(null)} > {/* Background rect to handle mouse events in empty areas */} setTooltipData(null)} /> {filteredFeatures.map((feature, index) => { const isHighlighted = countries.includes( feature.properties.iso_a2_eh, ); const serverCount = serverCounts[feature.properties.iso_a2_eh] || 0; return ( { if (!isHighlighted) { setTooltipData(null); return; } if (path.centroid(feature)) { const countryCode = feature.properties.iso_a2_eh; const countryServers = nezhaServerList .filter( (server: NezhaServer) => server.country_code?.toUpperCase() === countryCode, ) .map((server: NezhaServer) => ({ id: server.id, name: server.name, status: formatNezhaInfo(now, server).online, })); setTooltipData({ centroid: path.centroid(feature), country: feature.properties.name, count: serverCount, servers: countryServers, }); } }} /> ); })} {/* 渲染不在 filteredFeatures 中的国家标记点 */} {countries.map((countryCode) => { // 检查该国家是否已经在 filteredFeatures 中 const isInFilteredFeatures = filteredFeatures.some( (feature) => feature.properties.iso_a2_eh === countryCode, ); // 如果已经在 filteredFeatures 中,跳过 if (isInFilteredFeatures) return null; // 获取国家的经纬度 const coords = countryCoordinates[countryCode]; if (!coords) return null; // 使用投影函数将经纬度转换为 SVG 坐标 const [x, y] = projection([coords.lng, coords.lat]) || [0, 0]; const serverCount = serverCounts[countryCode] || 0; return ( { const countryServers = nezhaServerList .filter( (server: NezhaServer) => server.country_code?.toUpperCase() === countryCode.toUpperCase(), ) .map((server: NezhaServer) => ({ id: server.id, name: server.name, status: formatNezhaInfo(now, server).online, })); setTooltipData({ centroid: [x, y], country: coords.name, count: serverCount, servers: countryServers, }); }} className="cursor-pointer" > ); })}
); }