feat: Implemented filtering and searching
This commit is contained in:
parent
4806f3487a
commit
444d8c4c27
49 changed files with 841 additions and 290 deletions
|
|
@ -1,44 +1,50 @@
|
|||
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
|
||||
import { StickyHeaderUI } from './Header';
|
||||
import { HeaderButton, StickyHeaderUI } from './Header';
|
||||
import { GameList } from './GameList';
|
||||
import { Search, Settings2 } from 'lucide-react';
|
||||
import { JSX, Suspense } from 'react';
|
||||
import { ArrowDownAz, CalendarArrowDown, ClockArrowDown, Drama, Filter, FunnelX, HardDrive, Rocket, Search, Settings2, SortDesc, Store, Tags, User, UserLock } from 'lucide-react';
|
||||
import { JSX, Suspense, useRef, useState } from 'react';
|
||||
import { FloatingShortcuts } from './Shortcuts';
|
||||
import { AutoFocus } from './AutoFocus';
|
||||
import { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts';
|
||||
import { GameListFilterType } from '@/shared/constants';
|
||||
import { GameCardFocusHandler } from './CardElement';
|
||||
import { GameListFilterSchema, GameListFilterType } from '@/shared/constants';
|
||||
import { HandleGoBack } from '../scripts/utils';
|
||||
import LoadingCardList from './LoadingCardList';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { gameQuery } from '../scripts/queries/romm';
|
||||
import { useRouter } from '@tanstack/react-router';
|
||||
import { useNavigate, useRouter } from '@tanstack/react-router';
|
||||
import SelectMenu from './SelectMenu';
|
||||
import { RoundButton } from './RoundButton';
|
||||
import { ContextList, DialogEntry, useContextDialog } from './ContextDialog';
|
||||
import classNames from 'classnames';
|
||||
import { sourceIconMap } from './Constants';
|
||||
import { stat } from 'fs-extra';
|
||||
import { FilterUI } from './Filters';
|
||||
import SideFilters from './SideFilters';
|
||||
|
||||
export interface CollectionsDetailParams
|
||||
{
|
||||
id?: string;
|
||||
setBackground?: (url: string) => void;
|
||||
filters?: GameListFilterType;
|
||||
builder?: () => Promise<{ filter?: GameListFilterType, title?: JSX.Element; }>;
|
||||
setLocalFilter: (filter: GameListFilterType) => void,
|
||||
localFilter: GameListFilterType,
|
||||
headerTitle?: JSX.Element;
|
||||
headerChildren?: any;
|
||||
title?: JSX.Element;
|
||||
footer?: JSX.Element;
|
||||
focus?: string;
|
||||
countHit?: number;
|
||||
countHint?: number;
|
||||
headerButtons?: HeaderButton[];
|
||||
headerButtonElements?: JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
export function CollectionsDetail (data: CollectionsDetailParams)
|
||||
{
|
||||
const router = useRouter();
|
||||
const builtData = useQuery({
|
||||
queryKey: ['filter', data.id], queryFn: async () =>
|
||||
{
|
||||
return data.builder?.() ?? { filter: data.filters, title: data.title };
|
||||
}
|
||||
});
|
||||
const [filterValues, setFilterValues] = useState<FrontEndFilterLists>();
|
||||
const queryClient = useQueryClient();
|
||||
const focusKey = `game-list-${data.id}-${data?.filters ? Object.values(data?.filters).map(f => String(f)).join(",") : ''}`;
|
||||
const finalFilter = { ...data.localFilter, ...data.filters };
|
||||
const focusKey = `game-list-${data.id}`;
|
||||
const { ref, focusSelf } = useFocusable({
|
||||
focusKey,
|
||||
preferredChildFocusKey: `${focusKey}-list`
|
||||
|
|
@ -46,9 +52,8 @@ export function CollectionsDetail (data: CollectionsDetailParams)
|
|||
|
||||
useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: (e) => HandleGoBack(router, e) }], [router]);
|
||||
|
||||
const handleScroll: GameCardFocusHandler = (cardId, node, details) =>
|
||||
const handleScroll: FocusParams['onFocus'] = (cardId, node, details) =>
|
||||
{
|
||||
|
||||
const [source, id] = cardId.split('@');
|
||||
queryClient.prefetchQuery(gameQuery(source, id));
|
||||
|
||||
|
|
@ -61,22 +66,27 @@ export function CollectionsDetail (data: CollectionsDetailParams)
|
|||
return (
|
||||
<FocusContext value={focusKey}>
|
||||
<div ref={ref} className='absolute w-screen h-screen overflow-y-scroll'>
|
||||
<StickyHeaderUI title={data.headerTitle} buttons={[{ id: "search", icon: <Search /> }, { id: "filter", icon: <Settings2 /> }]} ref={ref} />
|
||||
<div className="w-full grow rounded-2xl justify-center mask-alpha sm:portrait:mask-t-from-transparent md:landscape:mask-t-from-transparent mask-t-to-20 mask-t-to-black">
|
||||
<div className="relative h-fit w-full md:px-6 pt-4 pb-32">
|
||||
{builtData.data?.filter && data.title}
|
||||
{(builtData.data?.filter || (!data.filters && !data.builder)) && <Suspense fallback={<LoadingCardList grid placeholderCount={data.countHit ?? 8} id={`${focusKey}-list`} />}>
|
||||
<StickyHeaderUI title={data.headerTitle} buttonElements={data.headerButtonElements} buttons={data.headerButtons} ref={ref} >
|
||||
{data.headerChildren}
|
||||
</StickyHeaderUI>
|
||||
<div className="w-full grow justify-center mask-alpha sm:portrait:mask-t-from-transparent md:landscape:mask-t-from-transparent mask-t-to-20 mask-t-to-black">
|
||||
<div className="relative h-fit w-full md:pr-6 pt-4 pb-32 pl-16">
|
||||
<div className='absolute top-0 bottom-0 left-0 right-0 bg-radial from-base-100 to-base-300 -z-1'></div>
|
||||
<div className='mobile:hidden bg-noise'></div>
|
||||
<div className='mobile:hidden bg-dots'></div>
|
||||
{finalFilter && data.title}
|
||||
{<Suspense fallback={<LoadingCardList grid placeholderCount={data.countHint ?? 8} id={`${focusKey}-list`} />}>
|
||||
<GameList
|
||||
key={`${data.id}-${JSON.stringify(finalFilter)}`}
|
||||
grid
|
||||
filters={builtData.data?.filter}
|
||||
setFilterValues={setFilterValues}
|
||||
filters={finalFilter}
|
||||
onFocus={handleScroll}
|
||||
focus={data.focus}
|
||||
id={`${focusKey}-list`}>
|
||||
</GameList>
|
||||
<AutoFocus parentKey={focusKey} focus={focusSelf} />
|
||||
<AutoFocus parentKey={focusKey} focus={focusSelf} delay={100} />
|
||||
</Suspense>}
|
||||
<div className='absolute top-0 bottom-0 left-0 right-0 bg-radial from-base-100 to-base-300'></div>
|
||||
<div className='mobile:hidden bg-noise z-1'></div>
|
||||
<div className='mobile:hidden bg-dots z-1'></div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className="px-2 pb-2 fixed bottom-0 w-full h-12 flex items-center justify-between">
|
||||
|
|
@ -85,6 +95,9 @@ export function CollectionsDetail (data: CollectionsDetailParams)
|
|||
</div>
|
||||
<FloatingShortcuts />
|
||||
</footer>
|
||||
<div className='fixed left-2 top-24 bottom-0 sm:w-10 md:w-14'>
|
||||
<SideFilters id='filter-btns' localFilter={data.localFilter} setLocalFilter={data.setLocalFilter} filterValues={filterValues} filters={data.filters} />
|
||||
</div>
|
||||
</div>
|
||||
<SelectMenu rootFocusKey={focusKey} />
|
||||
</FocusContext>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue