import { JSX, Suspense, useContext, useState } from "react"; import { Gamepad2, Settings, MessageSquare, Image, Search, Power, OctagonAlert, Maximize, Store, } from "lucide-react"; import { createFileRoute, } from "@tanstack/react-router"; import { useMutation } from "@tanstack/react-query"; import { FocusContext, FocusDetails, useFocusable, } from "@noriginmedia/norigin-spatial-navigation"; import classNames from "classnames"; import { useEventListener } from "usehooks-ts"; import { HeaderAccounts, HeaderButton, HeaderStatusBar } from "../components/Header"; import { FilterUI } from "../components/Filters"; import { AnimatedBackground } from "../components/AnimatedBackground"; import { GameList } from "../components/GameList"; 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"; import { PlatformsList } from "../components/PlatformsList"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts"; import z from "zod"; import { Router } from ".."; import CollectionList from "../components/CollectionList"; import { zodValidator } from '@tanstack/zod-adapter'; import { mobileCheck, useDragScroll } from "../scripts/utils"; import { AnimatedBackgroundContext } from "../scripts/contexts"; import Carousel from "../components/Carousel"; import { closeMutation } from "@queries/system"; export const Route = createFileRoute("/")({ component: ConsoleHomeUI, validateSearch: zodValidator(z.object({ filter: z.string().optional().default('games') })) }); const filters = { consoles: { label: "Consoles", }, games: { label: "Games", }, collections: { label: "Collections", }, }; let screenLock: WakeLockSentinel | undefined = undefined; async function handleFullscreen () { if (document.fullscreenElement) { await document.exitFullscreen(); if (screenLock) screenLock.release(); } else { await document.documentElement.requestFullscreen(); screenLock = await navigator.wakeLock.request('screen'); return screenLock; } } function HomeListError (data: { focused: boolean; }) { const error = useErrorBoundary(); return
{(error.error as any).detail}
; } function ShowAllGamesCard () { const handleNavigate = () => { Router.navigate({ to: '/games', viewTransition: { types: ['zoom-in'] } }); }; const { ref } = useFocusable({ focusKey: 'all-games-btn', onEnterPress: handleNavigate }); return
All Games
; } function HomeList (data: { selectedFilter: string; }) { const [initFocus, setInitFocus] = useState(false); const bg = useContext(AnimatedBackgroundContext); const { } = Route.useSearch; const { ref, focused, focusKey, focusSelf } = useFocusable({ focusKey: "home-list", preferredChildFocusKey: `${data.selectedFilter}-list` }); const handleNodeFocus = (id: string, node: HTMLElement, details: FocusDetails) => { const isMouseEvent = details.nativeEvent instanceof MouseEvent; if (!isMouseEvent) { node?.scrollIntoView({ inline: 'center', block: 'center', behavior: initFocus ? 'smooth' : 'instant' }); } setInitFocus(true); }; function handleGameSelect (id: FrontEndId, source: string | null, sourceId: string | null) { Router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); }; const handleCollectionSelect = (id: string) => { Router.navigate({ to: `/collection/${id}` }); }; const handlePlatformSelect = (source: string, id: string) => { Router.navigate({ to: `/platform/${source}/${id}` }); }; let activeList: JSX.Element; switch (data.selectedFilter) { case 'consoles': activeList = <> ; break; case 'collections': activeList = <> ; break; default: activeList = <> } /> ; break; } useEventListener('wheel', e => { const deltaY = e.deltaY; const deltaYSign = Math.sign(e.deltaY); if (deltaYSign == -1) { (ref.current as HTMLElement)?.scrollBy({ top: 0, left: deltaY, behavior: 'instant' }); } else { (ref.current as HTMLElement)?.scrollBy({ top: 0, left: deltaY, behavior: 'instant' }); } }); useDragScroll(ref); return (
}> }> {activeList}
); } function MainMenu () { const { ref, focusKey } = useFocusable({ focusKey: `main-menu`, trackChildren: true, }); return (
    Router.navigate({ to: "/games" })} icon={} label="Home" type="secondary" /> } label="News" /> } action={() => Router.navigate({ to: "/store/tab" })} label="Shop" /> } label="Album" /> } label="Controllers" /> { Router.navigate({ to: '/settings/accounts' }); }} icon={} label="Settings" type="accent" />
); } function CircleIcon (data: { action?: () => void; type?: "secondary" | "accent" | "info"; label?: string; icon?: JSX.Element; }) { const { ref, focusKey } = useFocusable({ focusKey: `navigation-icon-${data.label}`, onEnterPress: data.action, }); useShortcuts(focusKey, () => [{ label: data.label, action: (e) => data.action?.(), button: GamePadButtonCode.A }]); const typeClasses = { secondary: "bg-secondary text-secondary-content", accent: "bg-accent text-accent-content", info: "bg-info text-info-content", none: "bg-base-content", }; return (
  • {data.icon}
  • ); } export default function ConsoleHomeUI () { const { filter } = Route.useSearch(); const close = useMutation(closeMutation); const { ref, focusKey } = useFocusable({ forceFocus: true, autoRestoreFocus: false, saveLastFocusedChild: false, focusKey: "HomePage", preferredChildFocusKey: `home-list`, }); const setFilter = (filter: string) => Router.navigate({ to: '/', search: { filter }, viewTransition: false, replace: true }); const { shortcuts } = useShortcutContext(); const headerButtons: HeaderButton[] = []; if (mobileCheck()) headerButtons.push({ id: "fullscreen", icon: , action: handleFullscreen }); headerButtons.push( { id: "search-header-button", icon: }, { id: "power-button", icon: , external: true, action: () => close.mutate() }, { id: "settings-header-button", icon: , external: true, action: () => Router.navigate({ to: "/settings/accounts" }) } ); return (
    [key, { ...value, selected: key === filter }]))} setSelected={setFilter} />
    ); }