import { RPC_URL, SERVER_URL } from '@/shared/constants';
import { createFileRoute } from '@tanstack/react-router';
import { zodValidator } from '@tanstack/zod-adapter';
import z from 'zod';
import { RefObject, useEffect, useRef, useState } from 'react';
import { Router } from '..';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { ButtonStyle } from '../components/options/Button';
import { DoorOpen, RefreshCw, Undo } from 'lucide-react';
import { GamePadButtonCode, useShortcutContext, useShortcuts } from '../scripts/shortcuts';
import Shortcuts from '../components/Shortcuts';
import { useEventListener } from 'usehooks-ts';
import useActiveControl from '../scripts/gamepads';
import { twMerge } from 'tailwind-merge';
import { HeaderAccounts, HeaderStatusBar } from '../components/Header';
import { RoundButton } from '../components/RoundButton';
import { gameQuery } from '@queries/romm';
export const Route = createFileRoute('/embedded/$source/$id')({
component: RouteComponent,
loader: async (ctx) =>
{
const data = await ctx.context.queryClient.fetchQuery(gameQuery(ctx.params.source, ctx.params.id));
return { data };
},
validateSearch: zodValidator(z.record(z.string(), z.string().optional().nullable()))
});
function OverlayButton (data: {
id: string,
style: ButtonStyle,
tooltip: string, setTooltip: (tooltip: string) => void,
className?: string;
children?: any;
} & InteractParams)
{
return
data.setTooltip(data.tooltip)} style={data.style} className={twMerge("", data.className)} id={data.id} onAction={data.onAction} >
{data.children}
;
}
function Overlay (data: {
open: boolean;
iframeRef: RefObject;
close: () => void;
goBack: () => void;
})
{
const { ref, focusSelf, focusKey } = useFocusable({ focusable: data.open, focusKey: 'overlay', forceFocus: true, isFocusBoundary: true });
const [tooltip, setTooltip] = useState(undefined);
useShortcuts(focusKey, () => data.open ? [{ label: 'Return', button: GamePadButtonCode.B, action: data.close }] : [], [data.open, data.close]);
useEffect(() =>
{
if (data.open)
{
focusSelf();
}
}, [data.open]);
const { isPointer } = useActiveControl();
const handleEvent = (type: string, value?: any) => data.iframeRef.current?.contentWindow?.postMessage({ type, data: value });
return
{
data.close();
handleEvent('restart');
}} >
{!!tooltip && data.open && !isPointer &&
{tooltip}
}
;
}
function Frame (data: { ref: RefObject; })
{
const { ref } = useFocusable({ focusKey: 'frame' });
const { data: game } = Route.useLoaderData();
const search = Route.useSearch();
search['gameName'] = game.name;
search['backgroundImage'] = `${RPC_URL(__HOST__)}${game.path_cover}`;
search['backgroundBlur'] = "true";
if (!__PUBLIC__)
{
search['threads'] = "true";
}
const params = Object.entries(search)
.filter(kvp => kvp[1] !== null && kvp[1] !== undefined)
.map(kvp => `${kvp[0]}=${encodeURIComponent(kvp[1]!)}`).join('&');
return ;
}
function RouteComponent ()
{
const { ref, focusSelf, focusKey } = useFocusable({
focusKey: 'emulatorjs',
preferredChildFocusKey: 'frame',
forceFocus: true
});
const iframeRef = useRef(null);
const [overlayOpen, setOverlayOpen] = useState(false);
const { source, id } = Route.useParams();
function HandleGoBack ()
{
Router.navigate({ to: '/game/$source/$id', params: { source, id }, replace: true });
}
useEventListener('message', e =>
{
if (e.data.type === 'exit')
{
HandleGoBack();
}
});
useShortcuts(focusKey, () => [
{
button: GamePadButtonCode.Steam,
action: () =>
{
setOverlayOpen(!overlayOpen);
}
},
{
button: GamePadButtonCode.Select,
heldTime: 1000,
action: () =>
{
setOverlayOpen(!overlayOpen);
}
}
], [overlayOpen, setOverlayOpen]);
const setPaused = (paused: boolean) =>
{
if (paused) iframeRef.current?.contentWindow?.postMessage({ type: 'pause', data: true });
else
{
// we want to prevent input from closing the overlay spilling
setTimeout(() => iframeRef.current?.contentWindow?.postMessage({ type: 'pause', data: false }), 100);
}
};
useEffect(() => setPaused(overlayOpen), [overlayOpen]);
const { shortcuts } = useShortcutContext();
useEffect(() => { if (!overlayOpen) focusSelf(); }, [overlayOpen]);
function handleClose ()
{
setOverlayOpen(false);
}
return ;
}