228 lines
8.8 KiB
TypeScript
228 lines
8.8 KiB
TypeScript
import * as React from 'react';
|
|
|
|
import CenterElement from '/components/center-element';
|
|
import FormSelectFiles from '/components/form-select-files';
|
|
import CanvasGBAEmulator from '/components/canvas-gba-emulator';
|
|
import OverlayControls from '/components/overlay-controls';
|
|
import OverlayMenu from '/components/overlay-menu';
|
|
import OverlaySelectFiles from '/components/overlay-select-files';
|
|
import CloseButton from '/components/close-button';
|
|
import {sendHello, handleSendFrame} from '/packet';
|
|
|
|
import Endian from '/endian';
|
|
|
|
import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME} from '/constants';
|
|
|
|
export interface handleClickStartEmulationButtonObjectArgs {
|
|
e: React.MouseEvent<HTMLInputElement>;
|
|
inputRom: HTMLInputElement | null;
|
|
inputSaveState: HTMLInputElement | null;
|
|
canvas: HTMLCanvasElement | null;
|
|
printingFrame: boolean;
|
|
setPrintingFrame: (c: boolean) => void;
|
|
setEmulationStarted: (c: boolean) => void,
|
|
setHiddenMenu: (c: boolean) => void;
|
|
setHiddenFormSelectFiles: (c: boolean) => void;
|
|
setWebSocket: (c: WebSocket) => void;
|
|
};
|
|
|
|
function handleClickStartEmulationButton({e, inputRom, inputSaveState, canvas, printingFrame,
|
|
setPrintingFrame, setEmulationStarted, setHiddenMenu,
|
|
setHiddenFormSelectFiles, setWebSocket}: handleClickStartEmulationButtonObjectArgs) {
|
|
e.preventDefault();
|
|
if (canvas == null) {
|
|
alert('Canvas does not exists?');
|
|
return;
|
|
}
|
|
const ctx = canvas.getContext('2d')
|
|
if (ctx == null) {
|
|
alert('Unable to create canvas context, doing nothing');
|
|
return;
|
|
}
|
|
if (inputRom == null || inputSaveState == null || inputRom.files == null || inputSaveState.files == null) {
|
|
alert('Unable to read the files ');
|
|
return;
|
|
}
|
|
if (inputRom.files.length == 0) {
|
|
alert('There is no rom still');
|
|
return;
|
|
}
|
|
if (inputSaveState.files.length == 0) {
|
|
alert('There is no savestate still');
|
|
return;
|
|
}
|
|
const rom_file = inputRom.files[0];
|
|
const savestate_file = inputSaveState.files[0];
|
|
rom_file.arrayBuffer().then((rom_buffer) => {
|
|
savestate_file.arrayBuffer().then((savestate_buffer) => {
|
|
const rom_array = new Uint8Array(rom_buffer);
|
|
const savestate_array = new Uint8Array(savestate_buffer);
|
|
const webSocket = new WebSocket(`ws://${window.location.host}/ws`);
|
|
setWebSocket(webSocket);
|
|
webSocket.binaryType = 'arraybuffer';
|
|
webSocket.onclose = (message) => {
|
|
setEmulationStarted(false);
|
|
console.log('Closing websocket.');
|
|
}
|
|
webSocket.onopen = () => {
|
|
console.log('Opened websocket.');
|
|
setEmulationStarted(true);
|
|
sendHello(webSocket, rom_array, savestate_array);
|
|
setHiddenMenu(true);
|
|
setHiddenFormSelectFiles(true);
|
|
};
|
|
setPrintingFrame(false);
|
|
webSocket.addEventListener('message', (event) => {
|
|
onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame)
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: boolean) => void) {
|
|
const buffer = event.data;
|
|
let packet_u8: Uint8Array | null = new Uint8Array(buffer);
|
|
const id = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
|
|
packet_u8 = packet_u8.slice(8, packet_u8.length);
|
|
const size = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
|
|
const raw_data = packet_u8.slice(8, packet_u8.length);
|
|
packet_u8 = null;
|
|
switch (id) {
|
|
case PACKET_ID_SEND_FRAME:
|
|
handleSendFrame(raw_data, canvas, ctx, printingFrame, setPrintingFrame);
|
|
break;
|
|
default:
|
|
console.log(`Received unknown packet ${id}`);
|
|
}
|
|
}
|
|
|
|
export default function Page() {
|
|
const screenDimensions = useScreenDimensions();
|
|
const emulatorDimensions = calculateSizeEmulator(screenDimensions);
|
|
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
|
function resizeCanvas() {
|
|
const canvas = canvasRef.current;
|
|
if (canvas == null) {
|
|
return;
|
|
}
|
|
if (emulatorDimensions.width === undefined || emulatorDimensions.height === undefined) {
|
|
return;
|
|
}
|
|
canvas.width = emulatorDimensions.width;
|
|
canvas.height = emulatorDimensions.height;
|
|
const ctx = canvas.getContext('2d')
|
|
if (ctx == null) {
|
|
return;
|
|
}
|
|
fillBlack(canvas, ctx);
|
|
};
|
|
const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = React.useState<boolean>(true);
|
|
const [printingFrame, setPrintingFrame] = React.useState<boolean>(false);
|
|
React.useEffect(resizeCanvas, [emulatorDimensions]);
|
|
const refInputRom = React.useRef<HTMLInputElement | null>(null);
|
|
const refInputSaveState = React.useRef<HTMLInputElement | null>(null);
|
|
const [emulationStarted, setEmulationStarted] = React.useState<boolean>(false);
|
|
const [hiddenMenu, setHiddenMenu] = React.useState<boolean>(true);
|
|
const [webSocket, setWebSocket] = React.useState<WebSocket | null>(null);
|
|
const onStartEmulation = (e: React.MouseEvent<HTMLInputElement>) => {
|
|
handleClickStartEmulationButton({
|
|
e: e,
|
|
setEmulationStarted: setEmulationStarted,
|
|
inputRom: refInputRom.current,
|
|
inputSaveState: refInputSaveState.current,
|
|
canvas: canvasRef.current,
|
|
setPrintingFrame: setPrintingFrame,
|
|
printingFrame: printingFrame,
|
|
setHiddenMenu: setHiddenMenu,
|
|
setHiddenFormSelectFiles: setHiddenFormSelectFiles,
|
|
setWebSocket: setWebSocket,
|
|
|
|
});
|
|
};
|
|
const firstMenuElement = React.useRef<HTMLAnchorElement>(null);
|
|
const screenRef = React.useRef<HTMLDivElement>(null);
|
|
const [isFullscreen, setIsFullscreen] = React.useState<boolean>(false);
|
|
return (
|
|
<div ref={screenRef}>
|
|
<OverlayControls firstMenuElement={firstMenuElement}
|
|
setHiddenMenu={setHiddenMenu} webSocket={webSocket}/>
|
|
<OverlayMenu hiddenMenu={hiddenMenu}
|
|
setHiddenMenu={setHiddenMenu} emulationStarted={emulationStarted}
|
|
setHiddenFormSelectFiles={setHiddenFormSelectFiles} screenRef={screenRef}
|
|
isFullscreen={isFullscreen} setIsFullscreen={setIsFullscreen}
|
|
firstMenuElement={firstMenuElement}/>
|
|
<OverlaySelectFiles hiddenFormSelectFiles={hiddenFormSelectFiles}
|
|
setHiddenFormSelectFiles={setHiddenFormSelectFiles}
|
|
refInputRom={refInputRom} refInputSaveState={refInputSaveState}
|
|
onStartEmulation={onStartEmulation}/>
|
|
<div>
|
|
<CenterElement full={true}>
|
|
<CanvasGBAEmulator canvasRef={canvasRef}/>
|
|
</CenterElement>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getScreenDimensions() {
|
|
return {
|
|
width: document.body.clientWidth,
|
|
height: document.body.clientHeight
|
|
};
|
|
}
|
|
|
|
function useScreenDimensions() {
|
|
const [screenDimensions, setScreenDimensions] = React.useState<EmulatorDimensions>(getScreenDimensions());
|
|
|
|
React.useEffect(() => {
|
|
function onResize() {
|
|
setScreenDimensions(getScreenDimensions());
|
|
}
|
|
|
|
window.addEventListener("resize", onResize);
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", onResize);
|
|
}
|
|
}, []);
|
|
|
|
return screenDimensions;
|
|
}
|
|
|
|
function fillBlack(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, canvas.width, canvas.height);
|
|
ctx.fillStyle = '#0E0E10';
|
|
ctx.fill();
|
|
}
|
|
|
|
export interface EmulatorDimensions {
|
|
width?: number;
|
|
height?: number;
|
|
};
|
|
function calculateSizeEmulator(screenDimensions: EmulatorDimensions): EmulatorDimensions {
|
|
if (screenDimensions.width === undefined || screenDimensions.height === undefined) {
|
|
console.error(screenDimensions, 'screenDimensions has undefined fields');
|
|
return {};
|
|
}
|
|
const width = screenDimensions.width;
|
|
const height = screenDimensions.height;
|
|
const emulatorDimensions: EmulatorDimensions = {};
|
|
if (width < MIN_WIDTH || height < MIN_HEIGHT) {
|
|
return {
|
|
width: MIN_WIDTH,
|
|
height: MIN_HEIGHT,
|
|
};
|
|
}
|
|
const ratioWidth = width / MIN_WIDTH;
|
|
const ratioHeight = height / MIN_HEIGHT;
|
|
if (ratioWidth < ratioHeight) {
|
|
emulatorDimensions.width = MIN_WIDTH * ratioWidth;
|
|
emulatorDimensions.height = MIN_HEIGHT * ratioWidth;
|
|
} else {
|
|
emulatorDimensions.height = MIN_HEIGHT * ratioHeight;
|
|
emulatorDimensions.width = MIN_WIDTH * ratioHeight;
|
|
}
|
|
return emulatorDimensions;
|
|
}
|