feat: Implemented download page for downloading roms from various sources using plugins. Added support for internet archive external plugin. feat: Added tasks page to track running tasks/downloads feat: Added tanstack caching feat: Added quick play action Fixes #6 feat: Added quick emulator launch action fix: Made task queue only support 1 task per group and task ID should now be unique
105 lines
No EOL
3.8 KiB
TypeScript
105 lines
No EOL
3.8 KiB
TypeScript
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
|
|
import { oneShot } from "../scripts/audio/audio";
|
|
import { Search } from "lucide-react";
|
|
import { RoundButton } from "./RoundButton";
|
|
import { useEventListener } from "usehooks-ts";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
function SearchInput (data: {
|
|
id: string;
|
|
autoSearch?: boolean;
|
|
search: string | undefined;
|
|
compact: boolean | undefined;
|
|
onInputFocus: () => void;
|
|
setShowInput: (show: boolean) => void;
|
|
className?: string;
|
|
onSubmit: (search: string | undefined) => void;
|
|
} & FocusParams)
|
|
{
|
|
const { ref, focusKey } = useFocusable({
|
|
onBlur: () => inputRef.current?.blur(),
|
|
onFocus: (l, p, d) =>
|
|
{
|
|
data.onFocus?.(focusKey, ref.current, { ...d, inputRef });
|
|
if (data.autoSearch) inputRef.current?.focus();
|
|
},
|
|
focusKey: data.id,
|
|
onEnterPress: () =>
|
|
{
|
|
if (document.activeElement === inputRef.current)
|
|
{
|
|
if (inputRef.current)
|
|
data.onSubmit?.(inputRef.current.value);
|
|
} else
|
|
{
|
|
inputRef.current?.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [localSearch, setLocalSearch] = useState(data.search);
|
|
|
|
useEffect(() =>
|
|
{
|
|
setLocalSearch(data.search ?? "");
|
|
}, [data.search]);
|
|
|
|
useShortcuts(focusKey, () => document.activeElement === inputRef.current ? [{
|
|
label: "Cancel",
|
|
button: GamePadButtonCode.B, action (e)
|
|
{
|
|
inputRef.current?.blur();
|
|
oneShot('returnGeneric');
|
|
},
|
|
}] : [], [inputRef.current, document.activeElement]);
|
|
|
|
useEventListener('search' as any, e =>
|
|
{
|
|
data.onSubmit?.(undefined);
|
|
}, inputRef as any);
|
|
|
|
return <label ref={ref} onFocus={data.onInputFocus} className={twMerge('input rounded-full input-lg w-full max-w-xs bg-base-200 has-focus:bg-base-300 ring-primary focused:ring-7 has-focus:ring-7 has-focus:ring-base-content', data.className)}>
|
|
<Search />
|
|
<input
|
|
onBlur={e =>
|
|
{
|
|
data.setShowInput(false);
|
|
setLocalSearch(data.search);
|
|
}}
|
|
autoFocus={data.compact}
|
|
ref={inputRef}
|
|
value={localSearch ?? ""}
|
|
onChange={v => setLocalSearch(v.target.value)}
|
|
type='search'
|
|
placeholder='Search'
|
|
/>
|
|
</label>;
|
|
}
|
|
|
|
export default function HeaderSearchField (data: {
|
|
id: string;
|
|
autoSearch?: boolean;
|
|
search: string | undefined,
|
|
onSubmit: (search: string | undefined) => void;
|
|
className?: string;
|
|
compact?: boolean;
|
|
} & FocusParams)
|
|
{
|
|
const [showInput, setShowInput] = useState(false);
|
|
|
|
const { ref, focusKey, focusSelf } = useFocusable({
|
|
focusKey: data.id,
|
|
focusBoundaryDirections: ['left', "right"],
|
|
isFocusBoundary: data.compact && showInput
|
|
});
|
|
|
|
return <div ref={ref} className='flex items-center' style={{ viewTransitionName: 'header-search' }}>
|
|
<FocusContext value={focusKey}>
|
|
{(!data.compact || showInput) && <SearchInput className={data.className} autoSearch={data.autoSearch} onFocus={data.onFocus} id={`${data.id}-field`} search={data.search} onSubmit={data.onSubmit} compact={data.compact} setShowInput={setShowInput} onInputFocus={focusSelf} />}
|
|
{data.compact && !showInput && <RoundButton cssStyle={{ viewTransitionName: 'search-button' }} onAction={e => setShowInput(true)} className="header-icon sm:size-10 md:size-14" id={`${data.id}-field`} ><Search /></RoundButton>}
|
|
</FocusContext>
|
|
</div>;
|
|
} |