feat: Implemented emulator installation
feat: Updated romm API version feat: Updated es-de rules feat: Added tabs to game details refactor: returned to global query definitions to help with typescript performance
This commit is contained in:
parent
cf6fff6fac
commit
3750e9ed8f
103 changed files with 4888 additions and 1632 deletions
|
|
@ -8,7 +8,7 @@ import { StoreEmulatorCard } from '@/mainview/components/store/StoreEmulatorCard
|
|||
import { StoreContext } from '@/mainview/scripts/contexts';
|
||||
import { GetFocusedElement } from '@/mainview/scripts/spatialNavigation';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import queries from '@/mainview/scripts/queries';
|
||||
import { storeEmulatorsQuery } from '@queries/store';
|
||||
|
||||
export const Route = createFileRoute('/store/tab/emulators')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -22,7 +22,7 @@ function RouteComponent ()
|
|||
preferredChildFocusKey: focus
|
||||
});
|
||||
const storeContext = useContext(StoreContext);
|
||||
const { data: emulators } = useQuery(queries.store.storeEmulatorsQuery);
|
||||
const { data: emulators } = useQuery(storeEmulatorsQuery);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useInfiniteQuery } from '@tanstack/react-query';
|
|||
import FrontEndGameCard from '@/mainview/components/FrontEndGameCard';
|
||||
import { GetFocusedElement } from '@/mainview/scripts/spatialNavigation';
|
||||
import LoadMoreButton from '@/mainview/components/LoadMoreButton';
|
||||
import queries from '@/mainview/scripts/queries';
|
||||
import { storeGamesInfiniteQuery } from '@queries/store';
|
||||
|
||||
export const Route = createFileRoute('/store/tab/games')({
|
||||
component: RouteComponent
|
||||
|
|
@ -17,7 +17,7 @@ function RouteComponent ()
|
|||
const { focus } = useSearch({ from: '/store/tab' });
|
||||
const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "main-area", preferredChildFocusKey: focus });
|
||||
|
||||
const { data, fetchNextPage, isFetchingNextPage, isFetching } = useInfiniteQuery(queries.store.storeGamesInfiniteQuery);
|
||||
const { data, fetchNextPage, isFetchingNextPage, isFetching } = useInfiniteQuery(storeGamesInfiniteQuery);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
|
@ -52,7 +52,7 @@ function RouteComponent ()
|
|||
<div className="skeleton h-4 w-[40%]"></div>
|
||||
</div>)}
|
||||
<LoadMoreButton
|
||||
lastId={data?.pages.at(-1)?.data.at(-1)?.id.id}
|
||||
lastId={data?.pages.at(-1)?.data.at(-1)?.id}
|
||||
onFocus={handleFocus}
|
||||
isFetching={isFetchingNextPage || isFetching}
|
||||
onAction={() =>
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ import { EmulatorsSection } from "../../../components/store/EmulatorsSection";
|
|||
import { GamesSection } from "../../../components/store/GamesSection";
|
||||
import { StatsSection } from "../../../components/store/StatsSection";
|
||||
import { FrontEndGameTypeDetailed, RPC_URL } from '@/shared/constants';
|
||||
import queries from '@/mainview/scripts/queries';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { scrollIntoViewHandler } from '@/mainview/scripts/utils';
|
||||
import { StoreContext } from '@/mainview/scripts/contexts';
|
||||
import { useInterval } from 'usehooks-ts';
|
||||
import { Button } from '@/mainview/components/options/Button';
|
||||
import { HardDrive, Search } from 'lucide-react';
|
||||
import { Gamepad2, HardDrive, Search, Star } from 'lucide-react';
|
||||
import { GetFocusedElement } from '@/mainview/scripts/spatialNavigation';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { autoEmulatorsQuery } from '@queries/settings';
|
||||
import { storeEmulatorsRecommendedQuery, storeFeaturedGamesQuery } from '@queries/store';
|
||||
|
||||
export const Route = createFileRoute('/store/tab/')({
|
||||
component: RouteComponent
|
||||
|
|
@ -106,9 +107,9 @@ function Main (data: { games?: FrontEndGameTypeDetailed[]; })
|
|||
export function RouteComponent ()
|
||||
{
|
||||
const { focus } = useSearch({ from: '/store/tab' });
|
||||
const { data: crucialEmulators, isSuccess } = useQuery({ ...queries.settings.autoEmulatorsQuery, select: (data) => data.filter(e => !e.exists && e.isCritical) });
|
||||
const { data: featuredGames } = useQuery(queries.store.storeFeaturedGamesQuery);
|
||||
const { data: recommendedEmulators } = useQuery(queries.store.storeEmulatorsRecommendedQuery);
|
||||
const { data: crucialEmulators, isSuccess } = useQuery({ ...autoEmulatorsQuery, select: (data) => data.filter(e => !e.validSource && e.isCritical) });
|
||||
const { data: featuredGames } = useQuery(storeFeaturedGamesQuery);
|
||||
const { data: recommendedEmulators } = useQuery(storeEmulatorsRecommendedQuery);
|
||||
|
||||
const { focusKey, ref, focusSelf } = useFocusable({ focusKey: 'main-area', preferredChildFocusKey: focus ?? "recommended-emulators" });
|
||||
const storeContext = useContext(StoreContext);
|
||||
|
|
@ -137,11 +138,22 @@ export function RouteComponent ()
|
|||
emulators={recommendedEmulators} />
|
||||
</div>
|
||||
|
||||
<GamesSection
|
||||
onSelect={(id, focus) => storeContext.showDetails('game', id.source, id.id, focus)}
|
||||
onFocus={scrollIntoViewHandler({ block: 'center' })}
|
||||
games={featuredGames}
|
||||
/>
|
||||
<div className="px-6 py-3">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-2 h-5 rounded-full bg-accent shadow-sm shadow-error/40" />
|
||||
<Gamepad2 className="text-accent" />
|
||||
<h2 className="font-bold uppercase tracking-widest text-accent grow">
|
||||
Featured Games
|
||||
</h2>
|
||||
<div className="flex gap-2 bg-accent text-accent-content rounded-full py-1 px-4 font-semibold opacity-80"><Star />Creator Picks</div>
|
||||
</div>
|
||||
<GamesSection
|
||||
onSelect={(id, focus) => storeContext.showDetails('game', id.source, id.id, focus)}
|
||||
onFocus={scrollIntoViewHandler({ block: 'center' })}
|
||||
games={featuredGames}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<StatsSection
|
||||
romCount={1240}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { HeaderUI } from '@/mainview/components/Header';
|
|||
import Shortcuts from '@/mainview/components/Shortcuts';
|
||||
import { StoreContext } from '@/mainview/scripts/contexts';
|
||||
import { GamePadButtonCode, useShortcutContext, useShortcuts } from '@/mainview/scripts/shortcuts';
|
||||
import { SaveSource } from '@/mainview/scripts/spatialNavigation';
|
||||
import { GetFocusedElement } from '@/mainview/scripts/spatialNavigation';
|
||||
import { mobileCheck, useStickyDataAttr } from '@/mainview/scripts/utils';
|
||||
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import { FocusContext, getCurrentFocusKey, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import { useMatchRoute } from '@tanstack/react-router';
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
||||
import { zodValidator } from '@tanstack/zod-adapter';
|
||||
|
|
@ -33,29 +33,51 @@ function TopArea (data: { filters: Record<string, FilterOption>; })
|
|||
{
|
||||
const { ref, focusKey } = useFocusable({
|
||||
focusKey: 'top-area',
|
||||
preferredChildFocusKey: 'store-tabs',
|
||||
preferredChildFocusKey: `store-tabs`,
|
||||
onFocus: () =>
|
||||
{
|
||||
(ref.current as HTMLElement).scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
}
|
||||
});
|
||||
|
||||
useShortcuts("STORE_ROOT", () => [{
|
||||
label: "Return",
|
||||
action: () => Router.navigate({ to: '/', viewTransition: { types: ['zoom-out'] } }),
|
||||
button: GamePadButtonCode.B
|
||||
}], []);
|
||||
|
||||
const handleNavigate = (s: string) =>
|
||||
{
|
||||
Router.navigate({ to: `/store/tab/${s === 'home' ? '' : s}`, viewTransition: { types: ['slide-up'] }, replace: true });
|
||||
};
|
||||
|
||||
return <div ref={ref}>
|
||||
<FocusContext value={focusKey}>
|
||||
<div className='w-full'>
|
||||
<FilterUI containerClassName='flex w-full justify-center' id="store-tabs" options={data.filters} setSelected={(s) => Router.navigate({ to: `/store/tab/${s === 'home' ? '' : s}` })} />
|
||||
<FilterUI rootFocusKey='STORE_ROOT' containerClassName='flex w-full justify-center' id="store-tabs" options={data.filters}
|
||||
setSelected={handleNavigate} />
|
||||
</div>
|
||||
</FocusContext>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function StoreOutlet ()
|
||||
{
|
||||
const { ref, focusKey } = useFocusable({ focusKey: "STORE_OUTLET" });
|
||||
return <div ref={ref}>
|
||||
<FocusContext value={focusKey}>
|
||||
<Outlet />
|
||||
</FocusContext>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function RouteComponent ()
|
||||
{
|
||||
// Root spatial nav container
|
||||
const { ref, focusKey, focusSelf } = useFocusable({
|
||||
focusKey: "STORE_ROOT",
|
||||
trackChildren: true,
|
||||
preferredChildFocusKey: 'top-area'
|
||||
preferredChildFocusKey: 'top-area',
|
||||
forceFocus: true
|
||||
});
|
||||
const headerRef = useRef(null);
|
||||
const sentinelRef = useRef(null);
|
||||
|
|
@ -65,34 +87,6 @@ function RouteComponent ()
|
|||
games: { label: "Games", selected: useIsSettings('games') }
|
||||
};
|
||||
|
||||
useShortcuts(focusKey, () => [{
|
||||
label: "Return",
|
||||
action: () => Router.navigate({ to: '/', viewTransition: { types: ['zoom-out'] } }),
|
||||
button: GamePadButtonCode.B
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
{
|
||||
const filterKeys = Object.keys(filters);
|
||||
const filterIndex = Math.max(0, filterKeys.findIndex(f => filters[f].selected));
|
||||
const selectedFilterIndex = Math.min(filterIndex + 1, filterKeys.length - 1);
|
||||
const newFilter = filterKeys[selectedFilterIndex];
|
||||
Router.navigate({ to: `/store/tab/${newFilter === 'home' ? '' : newFilter}` });
|
||||
},
|
||||
button: GamePadButtonCode.R1
|
||||
},
|
||||
{
|
||||
action: () =>
|
||||
{
|
||||
const filterKeys = Object.keys(filters);
|
||||
const filterIndex = Math.max(0, filterKeys.findIndex(f => filters[f as any].selected));
|
||||
const selectedFilterIndex = Math.max(0, filterIndex - 1,);
|
||||
const newFilter = filterKeys[selectedFilterIndex];
|
||||
Router.navigate({ to: `/store/tab/${newFilter === 'home' ? '' : newFilter}` });
|
||||
},
|
||||
button: GamePadButtonCode.L1
|
||||
}], [filters]);
|
||||
|
||||
const { shortcuts } = useShortcutContext();
|
||||
const { focus } = Route.useSearch();
|
||||
|
||||
|
|
@ -102,31 +96,24 @@ function RouteComponent ()
|
|||
{
|
||||
focusSelf();
|
||||
}
|
||||
|
||||
}, []);
|
||||
|
||||
const handleDetails = (type: string, source: string, id: string, focus: string) =>
|
||||
{
|
||||
|
||||
if (type === 'emulator')
|
||||
{
|
||||
SaveSource('store-details', { url: location.hash.replaceAll(/#|(\?.+)/g, ''), search: { focus } });
|
||||
Router.navigate({ to: '/store/details/emulator/$id', params: { id }, viewTransition: { types: ['zoom-in'] } });
|
||||
Router.navigate({ to: '/store/details/emulator/$id', params: { id } });
|
||||
}
|
||||
else if (type === 'game')
|
||||
{
|
||||
console.log(source, id);
|
||||
SaveSource('details', { url: location.hash.replaceAll(/#|(\?.+)/g, ''), search: { focus } });
|
||||
Router.navigate({ to: '/game/$source/$id', params: { source: source, id: id }, viewTransition: { types: ['zoom-in'] } });
|
||||
Router.navigate({ to: '/game/$source/$id', params: { source: source, id: id } });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const match = Route.useMatch();
|
||||
const goToSettings = () =>
|
||||
{
|
||||
SaveSource('settings', { url: match.pathname, search: { focus: "settings" } });
|
||||
Router.navigate({ to: '/settings', viewTransition: { types: ['zoom-in'] } });
|
||||
Router.navigate({ to: '/settings' });
|
||||
};
|
||||
|
||||
const isMobile = mobileCheck();
|
||||
|
|
@ -141,7 +128,7 @@ function RouteComponent ()
|
|||
<HeaderUI buttons={[{ icon: <Settings />, id: "settings", action: goToSettings, external: true }]} />
|
||||
</div>
|
||||
<TopArea filters={filters} />
|
||||
<Outlet />
|
||||
<StoreOutlet />
|
||||
<div className='flex fixed bottom-4 left-4 right-4 justify-end z-15'>
|
||||
<Shortcuts shortcuts={shortcuts} />
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue