diff --git a/web/src/components/layout/app-top-nav.tsx b/web/src/components/layout/app-top-nav.tsx index c2660f47e..158f434e2 100644 --- a/web/src/components/layout/app-top-nav.tsx +++ b/web/src/components/layout/app-top-nav.tsx @@ -1,47 +1,32 @@ -import { Home, Infinity as InfinityIcon } from "lucide-react"; -import { Fragment, useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react"; -import { Link, useLocation, useNavigate } from "react-router"; +import { useEffect, useState, type CSSProperties, type ReactNode } from "react"; +import { useLocation, useNavigate } from "react-router"; import { ModelSetupGuide } from "@/components/layout/model-setup-guide"; -import { WorkspaceSidebarFooter } from "@/components/layout/workspace-sidebar-footer"; +import { WorkspaceCommandPalette } from "@/components/layout/workspace-command-palette"; +import { WorkspaceSidebarNav } from "@/components/layout/workspace-sidebar-nav"; import { WorkspaceTopBar } from "@/components/layout/workspace-top-bar"; -import { navigationTools, type NavigationToolSlug } from "@/constant/navigation-tools"; import { cn } from "@/lib/utils"; import { refreshFeatureAvailability } from "@/lib/user-session"; import { isSpatialWorkbenchPath } from "@/lib/workspace-routes"; -import { preloadWorkspaceRoute } from "@/lib/workspace-route-modules"; import { useUserStore } from "@/stores/use-user-store"; export function AppWorkspaceShell({ children }: { children: ReactNode }) { const { pathname } = useLocation(); const navigate = useNavigate(); const user = useUserStore((state) => state.user); - const features = useUserStore((state) => state.features); const [mobileSidebarExpanded, setMobileSidebarExpanded] = useState(false); const [desktopSidebarCollapsed, setDesktopSidebarCollapsed] = useState(false); - const scrollRef = useRef(null); - const [scrollState, setScrollState] = useState({ - hasTopFade: false, - hasBottomFade: false, - }); + const [paletteOpen, setPaletteOpen] = useState(false); + const hideChrome = pathname.startsWith("/admin") || /^\/canvas\/[^/]+/.test(pathname); const showGlobalTopBar = !hideChrome; const spatialWorkbench = isSpatialWorkbenchPath(pathname); const creationWorkspace = pathname === "/create"; - const slug = pathname.split("/").filter(Boolean)[0]; - const activeToolSlug = navigationTools.some((tool) => tool.slug === slug) ? (slug as NavigationToolSlug) : undefined; - const visibleNavigationTools = (spatialWorkbench ? navigationTools : navigationTools.filter((tool) => tool.section === "创作空间")) - .filter((tool) => { - if (tool.slug === "projects") return features.shortDramaEnabled; - if (tool.slug === "tasks") return features.taskCenterEnabled; - if (tool.slug === "wallet") return features.creditsEnabled; - return true; - }); - const topRailNavTools = visibleNavigationTools.filter((tool) => tool.section === "创作空间" || tool.slug === "skills"); - const bottomRailNavTools = visibleNavigationTools.filter((tool) => tool.slug === "wallet"); + + const isMobileViewport = () => window.innerWidth < 1024; const toggleSidebar = () => { - if (window.innerWidth < 1024) { + if (isMobileViewport()) { setMobileSidebarExpanded((current) => !current); return; } @@ -49,40 +34,20 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) { }; const handleNavClick = () => { - if (window.innerWidth < 1024) setMobileSidebarExpanded(false); + if (isMobileViewport()) setMobileSidebarExpanded(false); }; - const renderSidebarLink = (tool: (typeof navigationTools)[number]) => { - const Icon = tool.icon; - const active = tool.slug === activeToolSlug; - return ( - preloadWorkspaceRoute(tool.slug)} - onPointerDown={() => preloadWorkspaceRoute(tool.slug)} - onPointerEnter={() => preloadWorkspaceRoute(tool.slug)} - className={cn( - "app-workspace-nav-link app-workspace-rail-tile grid size-10 place-items-center rounded-md text-[var(--fs-tiny)] transition-colors", - active ? "is-active font-medium" : "", - )} - > - - - ); - }; - - const handleScroll = () => { - const element = scrollRef.current; - if (!element) return; - const { scrollTop, scrollHeight, clientHeight } = element; - setScrollState({ - hasTopFade: scrollTop > 0, - hasBottomFade: scrollTop + clientHeight < scrollHeight - 1, - }); - }; + // ⌘K / Ctrl+K 全局呼出搜索面板。 + useEffect(() => { + const handler = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { + event.preventDefault(); + setPaletteOpen((open) => !open); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, []); useEffect(() => { const handleWorkspaceNavigation = (rawEvent: Event) => { @@ -95,10 +60,6 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) { return () => window.removeEventListener("workspace:navigate", handleWorkspaceNavigation); }, [navigate]); - useEffect(() => { - handleScroll(); - }, [visibleNavigationTools.length, mobileSidebarExpanded]); - useEffect(() => { if (!user) return; const refresh = () => void refreshFeatureAvailability().catch((error) => console.warn("功能开放状态刷新失败", error)); @@ -112,93 +73,30 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) { return ( <> -
+
{!hideChrome && mobileSidebarExpanded ?
+ setPaletteOpen(false)} />