fix: Fixed romm login, now uses token

feat: Moved romm to internal plugin
fix: Made focusing and navigation more reliable
fix: Loading errors on first time launch
This commit is contained in:
Simeon Radivoev 2026-03-28 17:32:51 +02:00
parent 7c10f4e4c2
commit 816d50ae4d
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
81 changed files with 1659 additions and 1097 deletions

View file

@ -22,6 +22,7 @@ import { GameDetailsContext } from "@/mainview/scripts/contexts";
import { gameQuery, gamesRecommendedBasedOnGameQuery } from "@queries/romm";
import { GamesSection } from "@/mainview/components/store/GamesSection";
import Details, { DetailElement } from "@/mainview/components/game/Details";
import { AutoFocus } from "@/mainview/components/AutoFocus";
export const Route = createFileRoute("/game/$source/$id")({
loader: async ({ params, context }) =>
@ -29,7 +30,6 @@ export const Route = createFileRoute("/game/$source/$id")({
context.queryClient.prefetchQuery(gameQuery(params.source, params.id));
},
component: RouteComponent,
pendingComponent: GameDetailsUIPending,
errorComponent: Error,
validateSearch: zodValidator(z.object({ focus: z.string().optional() }))
});
@ -71,81 +71,6 @@ function Error (data: ErrorComponentProps)
</AnimatedBackground>;
}
function MainDetailsPending ()
{
const { ref } = useFocusable({ focusKey: "main-details" });
return <main ref={ref} className="flex p-3 flex-col flex-1 min-h-0">
<section className="flex portrait:flex-col my-4 sm:p-0 md:px-12 md:pb-8 pt-4 sm:gap-8 md:gap-12 portrait:w-full h-full min-h-0 rounded-4xl flex-1 z-0 sm:text-sm md:text-base">
<div className="flex gap-6 overflow-hidden bg-base-100 justify-end portrait:w-full rounded-3xl aspect-3/4 portrait:h-24 p-4">
<div className="skeleton w-full h-full"></div>
</div>
<div className="flex-2 flex flex-col sm:gap-1 md:gap-6 sm:pt-2 md:pt-16 min-h-0">
<div className="flex flex-wrap sm:gap-4 md:gap-6 shrink-0">
<DetailElement icon={<Clock />} ></DetailElement>
<DetailElement icon={<div className="skeleton size-6" />} ><div className="skeleton h-4 w-32"></div></DetailElement>
<DetailElement icon={
<Store />
} >
</DetailElement>
</div>
<div className="md:hidden divider divider-vertical m-0"></div>
<div className="text-base-content/80 flex-1 min-h-0 leading-relaxed grow text-wrap whitespace-break-spaces text-ellipsis overflow-hidden text-lg">
<div className="flex flex-col gap-4 w-full">
<div className="skeleton h-4 w-[30%]"></div>
<div className="skeleton h-4 w-[80%]"></div>
<div className="skeleton h-4 w-full"></div>
<div className="skeleton h-4 w-[60%]"></div>
<div className="skeleton h-4 w-full"></div>
<div className="skeleton h-4 w-[80%]"></div>
</div>
</div>
</div>
</section>
</main>;
}
function GameDetailsUIPending ()
{
const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "game-details-error", preferredChildFocusKey: "main-details" });
useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]);
const { shortcuts } = useShortcutContext();
useEffect(() =>
{
focusSelf();
}, []);
return <AnimatedBackground ref={ref} backgroundKey="game-details">
<div className="z-10">
<FocusContext value={focusKey}>
<div className="h-0" />
<div className="sticky group top-0 bg-base-100/40 group p-2 z-15 transition-colors data-stuck:backdrop-blur-3xl">
<HeaderUI />
</div>
<div className="flex flex-col h-[80vh] overflow-hidden bg-linear-to-t from-base-100 to-base-100/40">
<MainDetailsPending />
</div>
<div className="bg-base-200">
<div className="divider m-0 pb-12"><div className="flex items-center gap-3 opacity-60"><Image className="sm:size-4 md:size-6" />Screenshots</div></div>
<div className="flex flex-col w-full z-0 min-h-0">
<div
className="flex gap-6 px-16 py-2 sm:overflow-scroll md:overflow-hidden no-scrollbar justify-center-safe"
>
{Array.from({ length: 5 }).map((s, i) => <div key={i} className="skeleton h-64 w-lg"></div>)}
</div>
</div>
<footer className="fixed left-0 right-0 bottom-0 w-full p-4 flex items-center justify-end z-10">
<Shortcuts shortcuts={shortcuts} />
</footer>
</div>
</FocusContext>
</div>
</AnimatedBackground>;
}
function MoreDetails (data: { game: FrontEndGameTypeDetailed | undefined; })
{
const [details] = useDetailsSection();
@ -219,7 +144,7 @@ function RouteComponent ()
const { data } = useQuery(gameQuery(source, id));
const { focus } = Route.useSearch();
const [, setUpdate] = useState(0);
const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "game-details", preferredChildFocusKey: "main-details" });
const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "game-details", preferredChildFocusKey: "main-details", forceFocus: true });
const headerRef = useRef(null);
const sentinelRef = useRef(null);
const backgroundImage = data ? new URL(`${RPC_URL(__HOST__)}${data.path_cover}`) : undefined;
@ -228,20 +153,8 @@ function RouteComponent ()
useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]);
const { shortcuts } = useShortcutContext();
useEffect(() =>
{
if (focus)
{
setFocus(focus, { instant: true });
} else
{
focusSelf();
}
}, []);
useStickyDataAttr(headerRef, sentinelRef, ref);
const recommendedEmulators = data?.emulators?.filter(e => e.validSource);
const recommendedEmulators = data?.emulators?.filter(e => e.validSources.some(em => em.exists));
const { ref: intersct } = useIntersectionObserver({
onChange: (isIntersecting, entry) =>
@ -252,6 +165,7 @@ function RouteComponent ()
return (
<AnimatedBackground ref={ref} backgroundKey="game-details" backgroundUrl={backgroundImage} scrolling>
<AutoFocus focus={focusSelf} />
<GameDetailsContext value={{
update: () => setUpdate(v => v + 1)
}} >