msgba-web/js-src/components/page.tsx

222 lines
8.4 KiB
TypeScript
Raw Normal View History

2023-03-22 13:53:16 +01:00
import * as React from 'react';
2023-03-19 20:05:33 +01:00
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';
2023-03-25 15:20:18 +01:00
import {sendHello, handleSendFrame} from '/packet';
2023-03-19 20:05:33 +01:00
import Endian from '/endian';
import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME} from '/constants';
2023-03-22 13:53:16 +01:00
export interface handleClickStartEmulationButtonObjectArgs {
e: React.MouseEvent<HTMLInputElement>;
inputRom: HTMLInputElement | null;
inputSaveState: HTMLInputElement | null;
canvas: React.RefObject<HTMLCanvasElement | null>;
setEmulationStarted: (c: boolean) => void,
setHiddenMenu: (c: boolean) => void;
setHiddenFormSelectFiles: (c: boolean) => void;
setWebSocket: (c: WebSocket | null) => void;
2023-03-22 13:53:16 +01:00
};
function handleClickStartEmulationButton({e, inputRom, inputSaveState, canvas, setEmulationStarted, setHiddenMenu,
2023-03-25 15:20:18 +01:00
setHiddenFormSelectFiles, setWebSocket}: handleClickStartEmulationButtonObjectArgs) {
e.preventDefault();
if (canvas.current == null) {
2023-03-22 13:53:16 +01:00
alert('Canvas does not exists?');
return;
}
const ctx = canvas.current.getContext('2d')
2023-03-22 13:53:16 +01:00
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;
}
2023-03-19 20:05:33 +01:00
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);
2023-03-25 15:20:18 +01:00
const webSocket = new WebSocket(`ws://${window.location.host}/ws`);
setWebSocket(webSocket);
webSocket.binaryType = 'arraybuffer';
webSocket.onclose = (message) => {
setEmulationStarted(false);
2023-03-22 13:53:16 +01:00
console.log('Closing websocket.');
setWebSocket(null);
2023-03-22 13:53:16 +01:00
}
2023-03-25 15:20:18 +01:00
webSocket.onopen = () => {
2023-03-19 20:05:33 +01:00
console.log('Opened websocket.');
setEmulationStarted(true);
2023-03-25 15:20:18 +01:00
sendHello(webSocket, rom_array, savestate_array);
setHiddenMenu(true);
setHiddenFormSelectFiles(true);
2023-03-19 20:05:33 +01:00
};
2023-03-25 15:20:18 +01:00
webSocket.addEventListener('message', (event) => {
onWebSocketPacket(event, canvas.current, ctx)
2023-03-22 13:53:16 +01:00
});
2023-03-19 20:05:33 +01:00
});
});
}
function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement | null, ctx: CanvasRenderingContext2D) {
2023-03-19 20:05:33 +01:00
const buffer = event.data;
2023-03-22 13:53:16 +01:00
let packet_u8: Uint8Array | null = new Uint8Array(buffer);
const id = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
2023-03-19 20:05:33 +01:00
packet_u8 = packet_u8.slice(8, packet_u8.length);
2023-03-22 13:53:16 +01:00
const size = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
2023-03-19 20:05:33 +01:00
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);
2023-03-19 20:05:33 +01:00
break;
default:
console.log(`Received unknown packet ${id}`);
}
}
export default function Page() {
const screenDimensions = useScreenDimensions();
const emulatorDimensions = calculateSizeEmulator(screenDimensions);
2023-03-22 13:53:16 +01:00
const canvasRef = React.useRef<HTMLCanvasElement>(null);
function resizeCanvas() {
2023-03-19 20:05:33 +01:00
const canvas = canvasRef.current;
2023-03-22 13:53:16 +01:00
if (canvas == null) {
return;
}
if (emulatorDimensions.width === undefined || emulatorDimensions.height === undefined) {
return;
2023-03-19 20:05:33 +01:00
}
2023-03-22 13:53:16 +01:00
canvas.width = emulatorDimensions.width;
canvas.height = emulatorDimensions.height;
const ctx = canvas.getContext('2d')
if (ctx == null) {
return;
}
fillBlack(canvas, ctx);
2023-03-19 20:05:33 +01:00
};
const [hiddenFormSelectFiles, setHiddenFormSelectFiles] = React.useState<boolean>(true);
2023-03-19 20:05:33 +01:00
React.useEffect(resizeCanvas, [emulatorDimensions]);
2023-03-22 13:53:16 +01:00
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);
2023-03-25 15:20:18 +01:00
const [webSocket, setWebSocket] = React.useState<WebSocket | null>(null);
2023-03-22 13:53:16 +01:00
const onStartEmulation = (e: React.MouseEvent<HTMLInputElement>) => {
2023-03-19 20:05:33 +01:00
handleClickStartEmulationButton({
e: e,
setEmulationStarted: setEmulationStarted,
2023-03-19 20:05:33 +01:00
inputRom: refInputRom.current,
inputSaveState: refInputSaveState.current,
canvas: canvasRef,
setHiddenMenu: setHiddenMenu,
setHiddenFormSelectFiles: setHiddenFormSelectFiles,
2023-03-25 15:20:18 +01:00
setWebSocket: setWebSocket,
2023-03-19 20:05:33 +01:00
});
};
const firstMenuElement = React.useRef<HTMLAnchorElement>(null);
const screenRef = React.useRef<HTMLDivElement>(null);
const [isFullscreen, setIsFullscreen] = React.useState<boolean>(false);
2023-03-19 20:05:33 +01:00
return (
<div ref={screenRef}>
<OverlayControls firstMenuElement={firstMenuElement}
2023-03-25 15:20:18 +01:00
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>
2023-03-19 20:05:33 +01:00
</div>
);
}
function getScreenDimensions() {
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
}
function useScreenDimensions() {
2023-03-22 13:53:16 +01:00
const [screenDimensions, setScreenDimensions] = React.useState<EmulatorDimensions>(getScreenDimensions());
2023-03-19 20:05:33 +01:00
React.useEffect(() => {
function onResize() {
setScreenDimensions(getScreenDimensions());
}
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
}
}, []);
return screenDimensions;
}
2023-03-22 13:53:16 +01:00
function fillBlack(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
2023-03-19 20:05:33 +01:00
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0E0E10';
2023-03-19 20:05:33 +01:00
ctx.fill();
}
2023-03-22 13:53:16 +01:00
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 {};
}
2023-03-19 20:05:33 +01:00
const width = screenDimensions.width;
const height = screenDimensions.height;
2023-03-22 13:53:16 +01:00
const emulatorDimensions: EmulatorDimensions = {};
2023-03-19 20:05:33 +01:00
if (width < MIN_WIDTH || height < MIN_HEIGHT) {
return {
width: MIN_WIDTH,
height: MIN_HEIGHT,
};
}
const ratioWidth = width / MIN_WIDTH;
const ratioHeight = height / MIN_HEIGHT;
2023-03-19 20:05:33 +01:00
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;
}