import { Copy, FolderOpen, Globe, LogIn, LogOut, Menu, Search, Settings, Trash2, Upload, Users, type LucideIcon, } from "lucide-react"; import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { cn } from "@/lib/cn"; import { useAuth } from "@/store/auth"; import { useUi } from "@/store/ui"; interface Command { id: string; label: string; hint?: string; icon: LucideIcon; run: () => void; } export function CommandPalette({ open, onClose, }: { open: boolean; onClose: () => void; }) { if (!open) return null; return ; } function CommandPaletteDialog({ onClose }: { onClose: () => void }) { const navigate = useNavigate(); const user = useAuth((state) => state.user); const logout = useAuth((state) => state.logout); const toggleSidebar = useUi((state) => state.toggleSidebar); const [query, setQuery] = useState(""); const [activeIndex, setActiveIndex] = useState(0); const commands = useMemo(() => { const list: Command[] = [ { id: "library", label: "Go to Library", icon: FolderOpen, run: () => navigate("/"), }, ...(user?.can_upload ? [ { id: "upload", label: "Go to Upload", icon: Upload, run: () => navigate("/upload"), }, { id: "duplicates", label: "Find duplicates", icon: Copy, run: () => navigate("/duplicates"), }, { id: "delete", label: "Delete files & storage", icon: Trash2, run: () => navigate("/delete"), }, ] : []), { id: "online", label: "Go to Online browser", icon: Globe, run: () => navigate("/online"), }, { id: "filters", label: "Toggle filters", hint: "Ctrl+B", icon: Menu, run: toggleSidebar, }, { id: "search", label: "Focus library search", hint: "/", icon: Search, run: () => { navigate("/"); window.requestAnimationFrame(() => { document.getElementById("library-search")?.focus(); }); }, }, ]; if (user) { list.push({ id: "account", label: "Account settings", icon: Settings, run: () => navigate("/account"), }); if (user.is_staff || user.is_superuser || user.role === "staff") { list.push({ id: "users", label: "Manage users", icon: Users, run: () => navigate("/users"), }); } list.push({ id: "logout", label: `Log out (${user.username})`, icon: LogOut, run: () => void logout(), }); } else { list.push({ id: "login", label: "Log in", icon: LogIn, run: () => navigate("/login"), }); } return list; }, [navigate, toggleSidebar, user, logout]); const filtered = useMemo(() => { const needle = query.trim().toLowerCase(); return needle ? commands.filter((command) => command.label.toLowerCase().includes(needle), ) : commands; }, [commands, query]); function runCommand(command: Command | undefined) { if (!command) return; onClose(); command.run(); } return ( event.stopPropagation()} > { setQuery(event.target.value); setActiveIndex(0); }} onKeyDown={(event) => { if (event.key === "ArrowDown") { event.preventDefault(); setActiveIndex((index) => Math.min(index + 1, filtered.length - 1), ); } else if (event.key === "ArrowUp") { event.preventDefault(); setActiveIndex((index) => Math.max(index - 1, 0)); } else if (event.key === "Enter") { event.preventDefault(); runCommand(filtered[activeIndex]); } else if (event.key === "Escape") { onClose(); } }} placeholder="Type a command…" className="w-full border-b border-ctp-surface1 bg-transparent px-4 py-3 font-mono text-sm text-ctp-text placeholder:text-ctp-overlay0 focus:outline-none" /> {filtered.length === 0 ? ( No matching commands ) : ( filtered.map((command, index) => ( setActiveIndex(index)} onClick={() => runCommand(command)} className={cn( "flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm transition", index === activeIndex ? "bg-ctp-surface1 text-ctp-text" : "text-ctp-subtext0", )} > {command.label} {command.hint ? ( {command.hint} ) : null} )) )} ); }