feat: Added more ways to detect duplicates

feat: Added resolution and widescreen settings
feat: Added Xenia and Xemu integration
This commit is contained in:
Simeon Radivoev 2026-04-06 00:05:00 +03:00
parent 764691fc86
commit 05fafced07
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
25 changed files with 407 additions and 49 deletions

View file

@ -8,7 +8,6 @@ import { oneShot } from "@/mainview/scripts/audio/audio";
export function OptionDropdown (data: {
name: string;
type: HTMLInputTypeAttribute;
className?: string;
placeholder?: string;
icon?: JSX.Element;

View file

@ -0,0 +1,55 @@
import { HTMLInputTypeAttribute, JSX, useCallback, useEffect, useState } from "react";
import { SettingsType } from "../../../shared/constants";
import { useMutation, useQuery } from "@tanstack/react-query";
import { OptionSpace } from "./OptionSpace";
import { OptionInput } from "./OptionInput";
import { getSettingQuery, setSettingMutation } from "@queries/settings";
import { OptionDropdown } from "./OptionDropdown";
export function SettingsDropdown (data: {
label: string;
id: KeysWithValueAssignableTo<SettingsType, string>;
values: string[];
placeholder?: string;
icon?: JSX.Element;
children?: any;
})
{
const [dirty, setDirty] = useState(false);
const [localValue, setLocalValue] = useState<string | undefined>();
const { data: serverValue } = useQuery(getSettingQuery(data.id));
const setMutation = useMutation(setSettingMutation(data.id));
useEffect(() =>
{
setLocalValue(serverValue as any);
setDirty(false);
}, [serverValue]);
const handleSave = useCallback(() =>
{
if (dirty)
{
setDirty(false);
setMutation.mutate(localValue);
}
}, [dirty, setDirty, localValue]);
return (
<OptionSpace id={`${data.id}-space`} label={data.label}>
<OptionDropdown
icon={data.icon}
name={data.id ?? ""}
placeholder={data.placeholder}
onBlur={handleSave}
onChange={(v) =>
{
setLocalValue(v);
setMutation.mutate(v);
}}
value={localValue} values={data.values}
/>
{data.children}
</OptionSpace>
);
}

View file

@ -8,7 +8,7 @@ import { Check, ChevronDown, FileQuestion, FolderSearch, Plug, SearchAlert, Stor
import { ContextDialog, ContextList, DialogEntry, OptionElement } from '../../components/ContextDialog';
import classNames from 'classnames';
import { twMerge } from 'tailwind-merge';
import { RPC_URL } from '../../../shared/constants';
import { RPC_URL, SettingsSchema } from '../../../shared/constants';
import emulators from '@emulators';
import { FocusContext, setFocus, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { GamePadButtonCode, Shortcut, useShortcuts } from '@/mainview/scripts/shortcuts';
@ -19,6 +19,7 @@ import Carousel from '@/mainview/components/Carousel';
import { FOCUS_KEYS } from '@/mainview/scripts/types';
import { scrollIntoNearestParent, scrollIntoViewHandler, useDragScroll } from '@/mainview/scripts/utils';
import { SettingsOption } from '@/mainview/components/options/SettingsOption';
import { SettingsDropdown } from '@/mainview/components/options/SettingsDropdown';
export const Route = createFileRoute('/settings/emulators')({
component: RouteComponent,
@ -328,6 +329,8 @@ function RouteComponent ()
<EmulatorBadges addOverride={addOverrideMutation.mutate} onFocus={scrollIntoViewHandler({ block: 'center' })} />
<div className="divider text-base-content/40">Preferences</div>
<SettingsOption label="Launch In Fullscreen" id="launchInFullscreen" type="checkbox" />
<SettingsOption label="Widescreen" id="emulatorWidescreen" type="checkbox" />
<SettingsDropdown label='Resolution' id='emulatorResolution' values={SettingsSchema.shape.emulatorResolution.unwrap().options} />
<div className="divider text-base-content/40">Overrides</div>
<NewEmulatorPath isAddingOverride={addOverrideMutation.isPending} addOverride={addOverrideMutation.mutate} />
{!!customEmulators && customEmulators.map((key) => <EmulatorPath key={key} id={key} />)}