diff --git a/eslint.config.js b/eslint.config.js
index c1d602f..86d5d48 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -24,6 +24,7 @@ export default tseslint.config(
{ allowConstantExport: true },
],
"react-hooks/exhaustive-deps": "off",
+ "@typescript-eslint/no-explicit-any": "off",
},
},
);
diff --git a/src/components/GlobalMap.tsx b/src/components/GlobalMap.tsx
index aaf5ef1..28c7238 100644
--- a/src/components/GlobalMap.tsx
+++ b/src/components/GlobalMap.tsx
@@ -1,22 +1,23 @@
import { geoJsonString } from "@/lib/geo-json-string";
import { NezhaServer } from "@/types/nezha-api";
-import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { AnimatePresence, m } from "framer-motion";
import { geoEquirectangular, geoPath } from "d3-geo";
import { countryCoordinates } from "@/lib/geo-limit";
+import MapTooltip from "./MapTooltip";
+import useTooltip from "@/hooks/use-tooltip";
+import { formatNezhaInfo } from "@/lib/utils";
export default function GlobalMap({
serverList,
+ now,
}: {
serverList: NezhaServer[];
+ now: number;
}) {
const { t } = useTranslation();
const countryList: string[] = [];
const serverCounts: { [key: string]: number } = {};
- console.log(serverList);
-
serverList.forEach((server) => {
if (server.country_code) {
const countryCode = server.country_code.toUpperCase();
@@ -48,6 +49,8 @@ export default function GlobalMap({
width={width}
height={height}
filteredFeatures={filteredFeatures}
+ nezhaServerList={serverList}
+ now={now}
/>
@@ -67,21 +70,20 @@ interface InteractiveMapProps {
};
geometry: never;
}[];
+ nezhaServerList: NezhaServer[];
+ now: number;
}
-function InteractiveMap({
+export function InteractiveMap({
countries,
serverCounts,
width,
height,
filteredFeatures,
+ nezhaServerList,
+ now,
}: InteractiveMapProps) {
- const { t } = useTranslation();
- const [tooltipData, setTooltipData] = useState<{
- centroid: [number, number];
- country: string;
- count: number;
- } | null>(null);
+ const { setTooltipData } = useTooltip();
const projection = geoEquirectangular()
.scale(140)
@@ -91,7 +93,10 @@ function InteractiveMap({
const path = geoPath().projection(projection);
return (
-
+
setTooltipData(null)}
+ >
-
- {tooltipData && (
-
-
- {tooltipData.country === "China"
- ? "Mainland China"
- : tooltipData.country}
-
-
- {tooltipData.count} {t("map.Servers")}
-
-
- )}
-
+
);
}
diff --git a/src/components/MapTooltip.tsx b/src/components/MapTooltip.tsx
new file mode 100644
index 0000000..3d6fbe5
--- /dev/null
+++ b/src/components/MapTooltip.tsx
@@ -0,0 +1,62 @@
+import useTooltip from "@/hooks/use-tooltip";
+import { AnimatePresence, m } from "framer-motion";
+import { memo } from "react";
+import { useTranslation } from "react-i18next";
+
+const MapTooltip = memo(function MapTooltip() {
+ const { t } = useTranslation();
+ const { tooltipData } = useTooltip();
+
+ if (!tooltipData) return null;
+
+ return (
+
+ {
+ e.stopPropagation();
+ }}
+ >
+
+
+ {tooltipData.country === "China"
+ ? "Mainland China"
+ : tooltipData.country}
+
+
+ {tooltipData.count} {t("map.Servers")}
+
+
+
+ {tooltipData.servers.map((server, index: number) => (
+
+
+ {server.name}
+
+ ))}
+
+
+
+ );
+});
+
+export default MapTooltip;
diff --git a/src/context/status-provider.tsx b/src/context/status-provider.tsx
index 4b3d732..59e7e18 100644
--- a/src/context/status-provider.tsx
+++ b/src/context/status-provider.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { ReactNode, useState } from "react";
import { Status, StatusContext } from "./status-context";
diff --git a/src/context/tooltip-context.ts b/src/context/tooltip-context.ts
new file mode 100644
index 0000000..f942686
--- /dev/null
+++ b/src/context/tooltip-context.ts
@@ -0,0 +1,20 @@
+import { createContext } from "react";
+
+export interface TooltipData {
+ centroid: [number, number];
+ country: string;
+ count: number;
+ servers: Array<{
+ name: string;
+ status: boolean;
+ }>;
+}
+
+interface TooltipContextType {
+ tooltipData: TooltipData | null;
+ setTooltipData: (data: TooltipData | null) => void;
+}
+
+export const TooltipContext = createContext
(
+ undefined,
+);
diff --git a/src/context/tooltip-provider.tsx b/src/context/tooltip-provider.tsx
new file mode 100644
index 0000000..aa23bec
--- /dev/null
+++ b/src/context/tooltip-provider.tsx
@@ -0,0 +1,12 @@
+import { ReactNode, useState } from "react";
+import { TooltipContext, TooltipData } from "./tooltip-context";
+
+export function TooltipProvider({ children }: { children: ReactNode }) {
+ const [tooltipData, setTooltipData] = useState(null);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/hooks/use-tooltip.tsx b/src/hooks/use-tooltip.tsx
new file mode 100644
index 0000000..bf8754a
--- /dev/null
+++ b/src/hooks/use-tooltip.tsx
@@ -0,0 +1,12 @@
+import { useContext } from "react";
+import { TooltipContext } from "@/context/tooltip-context";
+
+export const useTooltip = () => {
+ const context = useContext(TooltipContext);
+ if (context === undefined) {
+ throw new Error("useTooltip must be used within a TooltipProvider");
+ }
+ return context;
+};
+
+export default useTooltip;
diff --git a/src/main.tsx b/src/main.tsx
index 77d7ed6..7acb462 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -11,6 +11,7 @@ import { MotionProvider } from "./components/motion/motion-provider";
import { WebSocketProvider } from "./context/websocket-provider";
import { StatusProvider } from "./context/status-provider";
import { FilterProvider } from "./context/network-filter-context";
+import { TooltipProvider } from "./context/tooltip-provider";
const queryClient = new QueryClient();
@@ -22,19 +23,21 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
-
+
+
+
+
+
diff --git a/src/pages/Server.tsx b/src/pages/Server.tsx
index 9633b2d..c394df0 100644
--- a/src/pages/Server.tsx
+++ b/src/pages/Server.tsx
@@ -243,7 +243,12 @@ export default function Servers() {
setCurrentTab={setCurrentGroup}
/>
- {showMap === "1" && }
+ {showMap === "1" && (
+
+ )}
{showServices === "1" && }
{inline === "1" && (
diff --git a/src/types/css.d.ts b/src/types/css.d.ts
index 18359a6..9d7b2e0 100644
--- a/src/types/css.d.ts
+++ b/src/types/css.d.ts
@@ -1,4 +1,4 @@
-declare module '*.css' {
- const css: { [key: string]: string }
- export default css
+declare module "*.css" {
+ const css: { [key: string]: string };
+ export default css;
}
diff --git a/src/types/nezha-api.ts b/src/types/nezha-api.ts
index c072013..4ca6531 100644
--- a/src/types/nezha-api.ts
+++ b/src/types/nezha-api.ts
@@ -23,7 +23,6 @@ export interface NezhaServerHost {
swap_total: number;
arch: string;
boot_time: number;
- country_code: string;
version: string;
}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 151aa68..11f02fe 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1 +1 @@
-///
\ No newline at end of file
+///