138 lines
3.1 KiB
TypeScript
138 lines
3.1 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import styles from './Emulator.module.scss';
|
|
import { GAME_SYSTEM_CORES, GameSystem, GameSystemCore } from '@/lib/game';
|
|
import { useLanguage } from '@/providers/LanguageProvider';
|
|
import { useAPI } from '@/providers/APIProvider';
|
|
|
|
type SendEmulatorMessageInit = {
|
|
message:'init';
|
|
core:GameSystemCore;
|
|
system:GameSystem;
|
|
gameId:string;
|
|
gameName:string;
|
|
}
|
|
|
|
type SendEmulatorMessageVolume = {
|
|
message:'volume';
|
|
volume:number;
|
|
};
|
|
|
|
type SendEmulatorMessage = (
|
|
SendEmulatorMessageInit |
|
|
SendEmulatorMessageVolume
|
|
);
|
|
|
|
type ReceiveEmulatorMessage = (
|
|
{ message: 'iframe_loaded' } |
|
|
{ message: 'start' } |
|
|
{ message: 'ready' } |
|
|
{ message: 'save_state' } |
|
|
{ message: 'load_state' } |
|
|
{ message: 'save', data:string } |
|
|
{ message: 'error', error:string }
|
|
);
|
|
|
|
export type EmulatorProps = {
|
|
system:GameSystem;
|
|
gameId:string;
|
|
gameName:string;
|
|
};
|
|
|
|
export const Emulator:React.FC<EmulatorProps> = props => {
|
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
const [ hasInit, setHasInit ] = useState(false);
|
|
const { t } = useLanguage();
|
|
const [ saving, setSaving ] = useState<boolean>(false);
|
|
const [ savingError, setSavingError ] = useState<string|null>(null);
|
|
const { saveUpdate } = useAPI();
|
|
|
|
const send = (msg:SendEmulatorMessage) => {
|
|
iframeRef.current?.contentWindow?.postMessage(msg, '*');
|
|
};
|
|
|
|
const save = async (data:string) => {
|
|
if(saving) return;
|
|
if(!data) return;
|
|
setSaving(true);
|
|
|
|
try {
|
|
const res = await saveUpdate({
|
|
gameId: props.gameId,
|
|
data
|
|
});
|
|
console.log('res', res);
|
|
} catch(e) {
|
|
console.error(e);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if(hasInit) return;
|
|
setHasInit(true);
|
|
|
|
window.onmessage = e => {
|
|
if(!e.data) {
|
|
console.error('Invalid message received:', e.data);
|
|
return;
|
|
}
|
|
|
|
const msg = e.data as ReceiveEmulatorMessage;
|
|
switch(msg.message) {
|
|
case 'iframe_loaded':
|
|
if(hasInit) return;
|
|
send({
|
|
message: 'init',
|
|
core: GAME_SYSTEM_CORES[props.system],
|
|
system: props.system,
|
|
gameId: props.gameId,
|
|
gameName: props.gameName
|
|
});
|
|
break;
|
|
|
|
case 'save':
|
|
save(msg.data).catch(console.error);
|
|
break;
|
|
|
|
case 'error':
|
|
console.error('Emulator error:', msg.error);
|
|
|
|
case 'start':
|
|
case 'ready':
|
|
case 'load_state':
|
|
case 'save_state':
|
|
break;
|
|
|
|
default:
|
|
console.error('Invalid message received:', msg);
|
|
break
|
|
}
|
|
};
|
|
|
|
window.onbeforeunload = (e: BeforeUnloadEvent) => {
|
|
e.preventDefault();
|
|
return t('emulator.exit_unsaved');
|
|
};
|
|
|
|
|
|
return () => {
|
|
window.onbeforeunload = null;
|
|
};
|
|
}, [ ]);
|
|
|
|
return (
|
|
<div className={styles.emulator}>
|
|
<div className={[
|
|
styles.emulator__box,
|
|
styles[`emulator__box--${props.system}`]
|
|
].join(' ')} />
|
|
|
|
<iframe
|
|
src="/emulator.html"
|
|
className={styles.emulator__iframe}
|
|
ref={iframeRef}
|
|
/>
|
|
</div>
|
|
);
|
|
} |