feat: Implemented emulator versions and updating

This commit is contained in:
Simeon Radivoev 2026-04-03 23:02:22 +03:00
parent a69147a4f7
commit 34db717ec5
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
22 changed files with 434 additions and 212 deletions

View file

@ -5,11 +5,13 @@ import { Button } from "../options/Button";
import useActiveControl from "@/mainview/scripts/gamepads";
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import { BadgeCheck, ChevronRight, EllipsisVertical, FileQuestion, IceCream2, Package, Sparkles, Store, WandSparkles } from "lucide-react";
import { BadgeCheck, ChevronRight, CircleFadingArrowUp, EllipsisVertical, FileQuestion, IceCream2, Package, Sparkles, Store, WandSparkles } from "lucide-react";
import { FOCUS_KEYS } from "@/mainview/scripts/types";
import { FlatpackIcon } from "@/mainview/scripts/brandIcons";
import { JSX } from "react";
import { oneShot } from "@/mainview/scripts/audio/audio";
import { useQuery } from "@tanstack/react-query";
import { getUpdateInfoForEmulator } from "@/mainview/scripts/queries/store";
export const emulatorStatusIcons: Record<string, JSX.Element> = {
store: <Store />,
@ -42,8 +44,9 @@ export function StoreEmulatorCard (data: {
}
});
const { data: updateInfo } = useQuery(getUpdateInfoForEmulator(data.emulator.name));
useShortcuts(focusKey, () => [{ button: GamePadButtonCode.A, label: "Details", action: handleSelect }], [handleSelect]);
const { isMouse, isTouch } = useActiveControl();
return (
<div
@ -52,8 +55,8 @@ export function StoreEmulatorCard (data: {
tabIndex={0}
data-sound-category="emulator"
data-installed={data.emulator.validSources.some(s => s.exists)}
onClick={isTouch ? handleSelect : undefined}
className={twMerge("relative focusable focusable-info bg-base-100 rounded-4xl transition-shadow focused:not-control-mouse:animate-scale-small shadow-lg border border-base-content/10 active:ring-4 active:ring-base-content active:transition-none", data.className)}
onClick={handleSelect}
className={twMerge("relative focusable focusable-info focusable-hover bg-base-100 rounded-4xl transition-shadow focused:not-control-mouse:animate-scale-small shadow-lg border border-base-content/10 active:ring-4 active:ring-base-content active:transition-none cursor-pointer", data.className)}
>
<div className="flex flex-col justify-between p-4 gap-2 h-full">
<div className="flex flex-col gap-2">
@ -81,21 +84,27 @@ export function StoreEmulatorCard (data: {
</div>
<div className="flex gap-1 mt-1 h-10 items-center">
{!!data.emulator.integration && <div aria-disabled={!data.emulator.integration.possible} className="tooltip not-aria-disabled:tooltip-primary" data-tip={data.emulator.integration.possible ? "Has Integration" : "Can Integrate"}>
<div className="bg-primary in-aria-disabled:bg-base-200 text-primary-content rounded-full p-1.5"><WandSparkles className="size-5" /></div>
{updateInfo?.hasUpdate && <div className="tooltip" data-tip="Has Update">
<div className="flex items-center justify-center rounded-full p-1 size-8 bg-warning text-warning-content">
<CircleFadingArrowUp />
</div>
</div>}
{data.emulator.integrations.length > 0 && <div
aria-disabled={!data.emulator.integrations.some(i => i.supportLevel)}
data-full-support={data.emulator.integrations.some(i => i.supportLevel === 'full')}
className="tooltip not-aria-disabled:tooltip-primary"
data-tip={data.emulator.integrations.some(i => i.supportLevel) ? data.emulator.integrations.some(i => i.supportLevel === 'full') ? "Full Support" : "Partial SUpport" : "Can Integrate"}
>
<div className="bg-primary in-data-[full-support=false]:bg-warning in-data-[full-support=false]:text-warning-content in-aria-disabled:bg-base-200 in-aria-disabled:text-base-content text-primary-content rounded-full p-1.5"><WandSparkles className="size-5" /></div>
</div>}
{data.emulator.validSources.slice(0, 3).map(s =>
{
return <div className="tooltip" data-tip={s.type}>
<div data-source={s.type} className="flex items-center justify-center rounded-full p-1 size-8 bg-warning text-warning-content data-[source=store]:bg-success data-[source=store]:text-success-content">
<div data-source={s.type} className="flex items-center justify-center rounded-full p-1 size-8 bg-base-300 text-base-content data-[source=store]:bg-success data-[source=store]:text-success-content">
{emulatorStatusIcons[s.type]}
</div>
</div>;
})}
{isMouse && <>
<Button onAction={e => data.onSelect?.(data.emulator.name, focusKey)} style="base" className="grow text-base-content/40" id={`${data.emulator.name}-details`} >Details<ChevronRight /></Button>
</>}
</div>
</div>
</div>