import { JSX, Suspense, useContext } from "react"; import { Gamepad2, Settings, MessageSquare, ShoppingBag, Image, Search, Power, OctagonAlert, } from "lucide-react"; import { createFileRoute, useLocation, useNavigate, } from "@tanstack/react-router"; import { useSuspenseQuery } from "@tanstack/react-query"; import { FocusContext, useFocusable, } from "@noriginmedia/norigin-spatial-navigation"; import classNames from "classnames"; import { DefaultRommStaleTime, RPC_URL } from "../../shared/constants"; import { useLocalStorage, useSessionStorage } from "usehooks-ts"; import { getCollectionsApiCollectionsGetOptions, getPlatformsApiPlatformsGetOptions, } from "../../clients/romm/@tanstack/react-query.gen"; import { CardList } from "../components/CardList"; import { HeaderUI } from "../components/Header"; import { FilterUI } from "../components/Filters"; import { AnimatedBackground, AnimatedBackgroundContext } from "../components/AnimatedBackground"; import { GameList } from "../components/GameList"; import { SaveSource } from "../scripts/spatialNavigation"; import LoadingCardList from "../components/LoadingCardList"; import { AutoFocus } from "../components/AutoFocus"; import SaveScroll from "../components/SaveScroll"; import { ErrorBoundary, useErrorBoundary } from "react-error-boundary"; import { twMerge } from "tailwind-merge"; import Shortcuts from "../components/Shortcuts"; export const Route = createFileRoute("/")({ component: ConsoleHomeUI, }); const filters = { consoles: { label: "Consoles", }, games: { label: "Games", }, collections: { label: "Collections", }, }; function PlatformList (data: { id: string, setBackground: (url: string) => void; }) { const navigate = useNavigate(); const { data: platforms } = useSuspenseQuery({ ...getPlatformsApiPlatformsGetOptions(), refetchOnWindowFocus: false, staleTime: DefaultRommStaleTime, }); return ( Date.parse(a.updated_at) - Date.parse(b.updated_at)) .map((g) => ({ id: g.id, focusKey: g.slug, title: g.display_name, subtitle: g.family_name ?? "", previewUrl: g.url_logo ?? "", badge: ( {g.rom_count} ), preview: (
), }))} onSelectGame={(id) => { navigate({ to: `/platform/${id}`, viewTransition: { types: ['zoom-in'] } }); }} onGameFocus={(id) => { data.setBackground( `https://picsum.photos/id/${10 + (id ?? 0)}/1920/1080.webp`, ); }} /> ); } function CollectionList (data: { id: string, setBackground: (url: string) => void; }) { const navigate = useNavigate(); const { data: collections } = useSuspenseQuery({ ...getCollectionsApiCollectionsGetOptions(), refetchOnWindowFocus: false, staleTime: DefaultRommStaleTime }); return ( Date.parse(a.updated_at) - Date.parse(b.updated_at)) .map((g) => ({ id: g.id, title: g.name, focusKey: `collection-${g.id}`, subtitle: g.user__username, previewUrl: `${RPC_URL(__HOST__)}/api/romm/${g.path_covers_large[0]}`, badge: ( {g.rom_count} ), }))} onSelectGame={(id) => { navigate({ to: `/collection/${id}`, viewTransition: { types: ['zoom-in'] } }); }} onGameFocus={(id) => { data.setBackground( `https://picsum.photos/id/${10 + (id ?? 0)}/1920/1080.webp`, ); }} /> ); } function HomeListError (data: { focused: boolean; }) { const error = useErrorBoundary(); return
{(error.error as any).detail}
; } function HomeList (data: { selectedFilter: keyof typeof filters; }) { const bg = useContext(AnimatedBackgroundContext); const { ref, focused, focusKey, focusSelf } = useFocusable({ focusKey: "home-list", preferredChildFocusKey: `${data.selectedFilter}-list` }); const lists = { consoles: , games: , collections: , }; return (
}> }> {lists[data.selectedFilter]}
); } export default function ConsoleHomeUI () { const [selectedFilter, setSelectedFilter] = useLocalStorage< keyof typeof filters >("home-filter-selected", "games"); const { ref, focusKey, focusSelf } = useFocusable({ forceFocus: true, autoRestoreFocus: false, saveLastFocusedChild: false, focusKey: "Home", preferredChildFocusKey: `home-list`, }); return (
}, { id: "power-button", icon: , external: true } ]} />
); } function MainMenu (data: {}) { const { ref, focusKey, hasFocusedChild } = useFocusable({ focusKey: `main-menu`, trackChildren: true, onBlur: (layout, props, details) => { }, }); const location = useLocation(); const navigate = useNavigate(); return (
    navigate({ to: "/" })} icon={} label="Home" type="secondary" /> } label="News" /> } label="Shop" /> } label="Album" /> } label="Controllers" /> { SaveSource('settings', location.pathname); navigate({ to: "/settings/accounts", viewTransition: { types: ['zoom-in'] } }); }} icon={} label="Settings" type="accent" />
); } function CircleIcon (data: { action?: () => void; type?: "secondary" | "accent"; label?: string; icon?: JSX.Element; }) { const { ref, focused } = useFocusable({ focusKey: `navigation-icon-${data.label}`, onEnterPress: data.action, }); const typeClasses = { secondary: "bg-secondary text-secondary-content", accent: "bg-accent text-accent-content", none: "bg-base-content", }; return (
  • {data.icon}
  • ); }