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}
/>
);
}