diff --git a/bun.lockb b/bun.lockb index fc35a79..13d1157 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index a802d6c..bb905cc 100644 --- a/package.json +++ b/package.json @@ -16,55 +16,56 @@ "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-checkbox": "^1.1.3", - "@radix-ui/react-dialog": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-dialog": "^1.1.5", + "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-label": "^2.1.1", - "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-popover": "^1.1.5", "@radix-ui/react-progress": "^1.1.1", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", - "@tanstack/react-query": "^5.63.0", - "@tanstack/react-query-devtools": "^5.63.0", + "@tanstack/react-query": "^5.64.2", + "@tanstack/react-query-devtools": "^5.64.2", "@tanstack/react-table": "^8.20.6", "@trivago/prettier-plugin-sort-imports": "^5.2.1", "@types/d3-geo": "^3.1.0", "@types/luxon": "^3.4.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "country-flag-icons": "^1.5.13", + "cmdk": "1.0.0", + "country-flag-icons": "^1.5.14", "d3-geo": "^3.1.1", "dayjs": "^1.11.13", - "framer-motion": "^12.0.0-alpha.2", + "framer-motion": "^12.0.3", "i18n-iso-countries": "^7.13.0", "i18next": "^24.2.1", "lucide-react": "^0.460.0", "luxon": "^3.5.0", - "prettier-plugin-tailwindcss": "^0.6.9", + "prettier-plugin-tailwindcss": "^0.6.10", "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^15.4.0", - "react-router-dom": "^7.1.1", + "react-router-dom": "^7.1.3", "recharts": "^2.15.0", - "sonner": "^1.7.1", + "sonner": "^1.7.2", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@eslint/js": "^9.17.0", - "@types/node": "^22.10.5", - "@types/react": "^19.0.4", - "@types/react-dom": "^19.0.2", + "@eslint/js": "^9.18.0", + "@types/node": "^22.10.9", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", "@vitejs/plugin-react-swc": "^3.7.2", "autoprefixer": "^10.4.20", - "eslint": "^9.17.0", + "eslint": "^9.18.0", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.16", + "eslint-plugin-react-refresh": "^0.4.18", "globals": "^15.14.0", - "postcss": "^8.4.49", + "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "~5.6.3", - "typescript-eslint": "^8.19.1", - "vite": "^6.0.7" + "typescript-eslint": "^8.21.0", + "vite": "^6.0.11" } } diff --git a/src/App.tsx b/src/App.tsx index 2bdc76c..1b01be3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { Route, BrowserRouter as Router, Routes } from "react-router-dom" import ErrorBoundary from "./components/ErrorBoundary" import Footer from "./components/Footer" import Header, { RefreshToast } from "./components/Header" +import { useBackground } from "./hooks/use-background" import { useTheme } from "./hooks/use-theme" import { InjectContext } from "./lib/inject" import { fetchSetting } from "./lib/nezha-api" @@ -14,6 +15,7 @@ import ErrorPage from "./pages/ErrorPage" import NotFound from "./pages/NotFound" import Server from "./pages/Server" import ServerDetail from "./pages/ServerDetail" +import { DashCommand } from "./components/DashCommand" const App: React.FC = () => { const { data: settingData, error } = useQuery({ @@ -25,11 +27,7 @@ const App: React.FC = () => { const { i18n } = useTranslation() const { setTheme } = useTheme() const [isCustomCodeInjected, setIsCustomCodeInjected] = useState(false) - - // 检测是否强制指定了主题颜色 - const forceTheme = - // @ts-expect-error ForceTheme is a global variable - (window.ForceTheme as string) !== "" ? window.ForceTheme : undefined + const { backgroundImage: customBackgroundImage } = useBackground() useEffect(() => { if (settingData?.data?.config?.custom_code) { @@ -38,6 +36,11 @@ const App: React.FC = () => { } }, [settingData?.data?.config?.custom_code]) + // 检测是否强制指定了主题颜色 + const forceTheme = + // @ts-expect-error ForceTheme is a global variable + (window.ForceTheme as string) !== "" ? window.ForceTheme : undefined + useEffect(() => { if (forceTheme === "dark" || forceTheme === "light") { setTheme(forceTheme) @@ -60,13 +63,7 @@ const App: React.FC = () => { i18n.changeLanguage(settingData?.data?.config?.language) } - const customBackgroundImage = - // @ts-expect-error CustomBackgroundImage is a global variable - (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined - - const customMobileBackgroundImage = - // @ts-expect-error CustomMobileBackgroundImage is a global variable - (window.CustomMobileBackgroundImage as string) !== "" ? window.CustomMobileBackgroundImage : undefined + const customMobileBackgroundImage = window.CustomMobileBackgroundImage !== "" ? window.CustomMobileBackgroundImage : undefined return ( @@ -94,6 +91,7 @@ const App: React.FC = () => {
+ } /> } /> diff --git a/src/components/CycleTransferStatsClient.tsx b/src/components/CycleTransferStatsClient.tsx index ebae6bb..85419ca 100644 --- a/src/components/CycleTransferStatsClient.tsx +++ b/src/components/CycleTransferStatsClient.tsx @@ -22,9 +22,7 @@ interface CycleTransferStatsClientProps { export const CycleTransferStatsClient: React.FC = ({ name, from, to, max, serverStats, className }) => { const { t } = useTranslation() - const customBackgroundImage = - // @ts-expect-error CustomBackgroundImage is a global variable - (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined + const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined return (
{ + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + if (!connected || !nezhaWsData) return null + + const shortcuts = [ + { + keywords: ["home", "homepage"], + icon: , + label: t("Home"), + action: () => navigate("/"), + }, + { + keywords: ["light", "theme", "lightmode"], + icon: , + label: t("ToggleLightMode"), + action: () => setTheme("light"), + }, + { + keywords: ["dark", "theme", "darkmode"], + icon: , + label: t("ToggleDarkMode"), + action: () => setTheme("dark"), + }, + { + keywords: ["system", "theme", "systemmode"], + icon: , + label: t("ToggleSystemMode"), + action: () => setTheme("system"), + }, + ].map((item) => ({ + ...item, + value: `${item.keywords.join(" ")} ${item.label}`, + })) + + return ( + <> + + + + {t("NoResults")} + + {nezhaWsData.servers.map((server) => ( + { + navigate(`/server/${server.id}`) + setOpen(false) + }} + > + {formatNezhaInfo(nezhaWsData.now, server).online ? ( + + ) : ( + + )} + {server.name} + + ))} + + + + + {shortcuts.map((item) => ( + { + item.action() + setOpen(false) + }} + > + {item.icon} + {item.label} + + ))} + + + + + ) +} diff --git a/src/components/GlobalMap.tsx b/src/components/GlobalMap.tsx index c1d12c3..ae9f2c1 100644 --- a/src/components/GlobalMap.tsx +++ b/src/components/GlobalMap.tsx @@ -13,9 +13,7 @@ export default function GlobalMap({ serverList, now }: { serverList: NezhaServer const countryList: string[] = [] const serverCounts: { [key: string]: number } = {} - const customBackgroundImage = - // @ts-expect-error CustomBackgroundImage is a global variable - (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined + const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined serverList.forEach((server) => { if (server.country_code) { diff --git a/src/components/GroupSwitch.tsx b/src/components/GroupSwitch.tsx index 0298634..960c95f 100644 --- a/src/components/GroupSwitch.tsx +++ b/src/components/GroupSwitch.tsx @@ -11,9 +11,7 @@ export default function GroupSwitch({ currentTab: string setCurrentTab: (tab: string) => void }) { - const customBackgroundImage = - // @ts-expect-error CustomBackgroundImage is a global variable - (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined + const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined const scrollRef = useRef(null) const tagRefs = useRef(tabs.map(() => createRef())) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a657556..5718b71 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,11 +1,13 @@ import { ModeToggle } from "@/components/ThemeSwitcher" import { Separator } from "@/components/ui/separator" import { Skeleton } from "@/components/ui/skeleton" +import { useBackground } from "@/hooks/use-background" import { useWebSocketContext } from "@/hooks/use-websocket-context" import { fetchLoginUser, fetchSetting } from "@/lib/nezha-api" import { cn } from "@/lib/utils" import { useQuery } from "@tanstack/react-query" import { AnimatePresence, m } from "framer-motion" +import { ImageMinus } from "lucide-react" import { DateTime } from "luxon" import { useEffect, useRef, useState } from "react" import { useTranslation } from "react-i18next" @@ -18,6 +20,7 @@ import { Button } from "./ui/button" function Header() { const { t } = useTranslation() const navigate = useNavigate() + const { backgroundImage, updateBackground } = useBackground() const { data: settingData, isLoading } = useQuery({ queryKey: ["setting"], @@ -38,9 +41,7 @@ function Header() { // @ts-expect-error CustomDesc is a global variable const customDesc = window.CustomDesc || t("nezha") - const customBackgroundImage = - // @ts-expect-error CustomBackgroundImage is a global variable - (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined + const customMobileBackgroundImage = window.CustomMobileBackgroundImage !== "" ? window.CustomMobileBackgroundImage : undefined useEffect(() => { const link = document.querySelector("link[rel*='icon']") || document.createElement("link") @@ -57,6 +58,22 @@ function Header() { document.title = siteName || "CZL SVR" }, [siteName]) + const handleBackgroundToggle = () => { + if (window.CustomBackgroundImage) { + // Store the current background image before removing it + sessionStorage.setItem("savedBackgroundImage", window.CustomBackgroundImage) + updateBackground(undefined) + } else { + // Restore the saved background image + const savedImage = sessionStorage.getItem("savedBackgroundImage") + if (savedImage) { + updateBackground(savedImage) + } + } + } + + const customBackgroundImage = backgroundImage + return (
@@ -87,6 +104,19 @@ function Header() {
+ {(customBackgroundImage || sessionStorage.getItem("savedBackgroundImage")) && ( + + )}