From c5f81d70d43bf75f227a0ecfde3780bf5b94c06f Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Fri, 24 Jan 2025 01:08:25 +0800 Subject: [PATCH] feat: dash command --- bun.lockb | Bin 197341 -> 197687 bytes package.json | 6 +- src/App.tsx | 2 + src/components/DashCommand.tsx | 114 ++++++++++++++++++ src/components/Footer.tsx | 27 +++-- src/components/ui/command.tsx | 178 +++++++++++------------------ src/locales/en/translation.json | 10 +- src/locales/zh-CN/translation.json | 10 +- src/locales/zh-TW/translation.json | 10 +- 9 files changed, 230 insertions(+), 127 deletions(-) create mode 100644 src/components/DashCommand.tsx diff --git a/bun.lockb b/bun.lockb index ea6c132183613c3954e8c6c3f451f50a8c3bf63b..13d1157780573391d3c8aace557e32a28cdc31b1 100755 GIT binary patch delta 916 zcmccH%Co(LXM&z)yyWSx7pi_3PCmRSCz$8?ue)uZ7Oh*%tfukt?)@j*Rd+3CW@Z3^ z2NT2PnI7EQSn+{RNE68CVPI&G1Jc|;+GzIXBK~)LnJk11(Xq< z+^A;G`*RipgC&qB22{^AnXyxQ@&-8$M!U^Fr)u-FG6DqyH!Cjv%?D*lZgH`3c&@W` z16N; zqkB2^6}csrw|VY|3F!`B{=c}=IJYjvaNUbzInB%`+h%cVD(WmZF-cgPc#z4| z>u&&Cb9INo-uP(u)%oQUvac#`>3@81>88~;-6THfXn9Z1jbN1UQQEig5I5I}i$c?X za9bWUzUOuEfr498MDq#}vzhA*lmrbg^KYEjK6!O@?3z2K&s@#Dr4;y#7wo^SRd(sx zrzG}lpbr@t{_+0<()YJlMlilso!lTNGMPh7V|zm$;{(P~5?=^(Fmh?!1|<>(h81_G zzrD$5zg_bdV*?ZCU5Ma;C(}lMg;|}8$)&Ld;2LJ6F?=lLoaXx{_n|uSwYq8z~ za_?^c`IymIP-q&^u=_xM#y5zXMQPKmzA~zTQu_AzuZ-*k)9>*yNvRmkhNN@Oe4Ash z0%z@JVyrXMGcwRKVEB;61ac6=hxF-PsZ1`*FQhUtaw%j$1m`nvzm>*hX3l85U2q=L th2-hW8JUHrKitWrINhO#iA^{!KP6RPS06;_8X4&s8t7S0|H8=p5&)W!W9$F` delta 469 zcmdnq!E?8jXM&#QbC(k4k9wgotY??;$A7B!d7jrfX-+|)_xTOR$2ET+I^fI1%m4!S zCx**2J-)fI;sYO-9FWh=z|inx*5)eycYK=*7Gm*ufJ@`pt#;aB5bj)Ud`@&*Te)FG2^gX+SRqBSD|9+T|?y$~(!X8Gp zja&bx{8)7IWmU_>vw`|CbJfmAhRxLbXeYGyt==Qd+46XzX> z;DN`}Pd#B&n||Xi;}uqa76t~t?Hlhg3b1iLhR7Ry1<7l%-UV{+Z2$R$(O8i09*~{z z6(Zk|I^FIYquTb2Z;WgODnDj1Fz^6FiZkEl*sH)9Vj(jlU*Oxu5@Gntt)8g4h7&vYSqx-~Pi5TnI(KW65a0KyBkRR910 diff --git a/package.json b/package.json index 0b2b24f..bb905cc 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "country-flag-icons": "^1.5.14", "d3-geo": "^3.1.1", "dayjs": "^1.11.13", - "framer-motion": "^12.0.1", + "framer-motion": "^12.0.3", "i18n-iso-countries": "^7.13.0", "i18next": "^24.2.1", "lucide-react": "^0.460.0", @@ -53,8 +53,8 @@ }, "devDependencies": { "@eslint/js": "^9.18.0", - "@types/node": "^22.10.8", - "@types/react": "^19.0.7", + "@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", diff --git a/src/App.tsx b/src/App.tsx index f606f58..1b01be3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,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({ @@ -90,6 +91,7 @@ const App: React.FC = () => {
+ } /> } /> diff --git a/src/components/DashCommand.tsx b/src/components/DashCommand.tsx new file mode 100644 index 0000000..d847eb2 --- /dev/null +++ b/src/components/DashCommand.tsx @@ -0,0 +1,114 @@ +"use client" + +import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "@/components/ui/command" +import { useTheme } from "@/hooks/use-theme" +import { useWebSocketContext } from "@/hooks/use-websocket-context" +import { formatNezhaInfo } from "@/lib/utils" +import { NezhaWebsocketResponse } from "@/types/nezha-api" +import { Home, Moon, Sun, SunMoon } from "lucide-react" +import { useEffect, useState } from "react" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router-dom" + +export function DashCommand() { + const [open, setOpen] = useState(false) + const [search, setSearch] = useState("") + const navigate = useNavigate() + const { t } = useTranslation() + const { setTheme } = useTheme() + + const { lastMessage, connected } = useWebSocketContext() + + const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null + + useEffect(() => { + 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/Footer.tsx b/src/components/Footer.tsx index eb11b2c..399b1c5 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -24,17 +24,24 @@ const Footer: React.FC = () => {

{settingData?.data?.version || ""}

-

- {t("footer.themeBy")} - - nezha-dash - - {import.meta.env.VITE_GIT_HASH && ( - - ({import.meta.env.VITE_GIT_HASH}) +

diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index f0e4e3f..2d76f1e 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -1,31 +1,29 @@ -import * as React from "react" -import { type DialogProps } from "@radix-ui/react-dialog" +"use client" + +import { Dialog, DialogContent } from "@/components/ui/dialog" +import { cn } from "@/lib/utils" +import { type DialogProps, DialogTitle } from "@radix-ui/react-dialog" import { Command as CommandPrimitive } from "cmdk" import { Search } from "lucide-react" +import * as React from "react" -import { cn } from "@/lib/utils" -import { Dialog, DialogContent } from "@/components/ui/dialog" - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) +const Command = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => ( + + ), +) Command.displayName = CommandPrimitive.displayName const CommandDialog = ({ children, ...props }: DialogProps) => { return ( + - + {children} @@ -33,119 +31,77 @@ const CommandDialog = ({ children, ...props }: DialogProps) => { ) } -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)) +const CommandInput = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => ( +
+ + +
+ ), +) CommandInput.displayName = CommandPrimitive.Input.displayName -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) +const CommandList = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => ( + + ), +) CommandList.displayName = CommandPrimitive.List.displayName -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)) +const CommandEmpty = React.forwardRef, React.ComponentPropsWithoutRef>( + (props, ref) => , +) CommandEmpty.displayName = CommandPrimitive.Empty.displayName -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) +const CommandGroup = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => ( + + ), +) CommandGroup.displayName = CommandPrimitive.Group.displayName const CommandSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) +>(({ className, ...props }, ref) => ) CommandSeparator.displayName = CommandPrimitive.Separator.displayName -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandItem.displayName = CommandPrimitive.Item.displayName - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - , React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => ( + - ) + ), +) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return } CommandShortcut.displayName = "CommandShortcut" -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -} +export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator } diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index a8c7075..f88943e 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -116,5 +116,13 @@ "price": "Price", "free": "Free", "usage-baseed": "Usage-based" - } + }, + "TypeCommand": "Type a command or search...", + "NoResults": "No results found.", + "Servers": "Servers", + "Shortcuts": "Shortcuts", + "ToggleLightMode": "Toggle Light Mode", + "ToggleDarkMode": "Toggle Dark Mode", + "ToggleSystemMode": "Toggle System Mode", + "Home": "Home" } diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 97131e2..9fd23d9 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -116,5 +116,13 @@ "price": "价格", "free": "免费", "usage-baseed": "按量计费" - } + }, + "TypeCommand": "输入命令或搜索", + "NoResults": "结果为空", + "Servers": "服务器", + "Shortcuts": "快捷键", + "ToggleLightMode": "切换亮色模式", + "ToggleDarkMode": "切换暗色模式", + "ToggleSystemMode": "切换系统模式", + "Home": "首页" } diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index 950909a..51f2ddb 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -112,5 +112,13 @@ "price": "價格", "free": "免費", "usage-baseed": "按量計費" - } + }, + "TypeCommand": "輸入命令或搜尋", + "NoResults": "沒有結果", + "Servers": "伺服器", + "Shortcuts": "快捷鍵", + "ToggleLightMode": "切換亮色模式", + "ToggleDarkMode": "切換暗色模式", + "ToggleSystemMode": "切換系統模式", + "Home": "首頁" }