import React from "react"; import { Output } from "@/components/output"; import { Emulator, EmulationState, isFinalState } from "./emulator"; import { Filesystem, setupFilesystem } from "./filesystem"; import "./App.css"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Settings, loadSettings, saveSettings } from "./settings"; import { SettingsMenu } from "@/components/settings-menu"; import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons"; import { StatusIndicator } from "@/components/status-indicator"; import { Header } from "./Header"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; import { FilesystemExplorer } from "./filesystem-explorer"; interface PlaygroundProps {} interface PlaygroundState { settings: Settings; filesystemPromise: Promise | null; filesystem: Filesystem | null; emulator: Emulator | null; application: string | undefined; drawerOpen: boolean; } function makePercentHandler( handler: (percent: number) => void, ): (current: number, total: number) => void { const progress = { tracked: 0, }; return (current, total) => { if (total == 0) { return; } const percent = Math.floor((current * 100) / total); const sanePercent = Math.max(Math.min(percent, 100), 0); if (sanePercent + 1 > progress.tracked) { progress.tracked = sanePercent + 1; handler(sanePercent); } }; } export class Playground extends React.Component< PlaygroundProps, PlaygroundState > { private output: React.RefObject; private iconCache: Map = new Map(); constructor(props: PlaygroundProps) { super(props); this.output = React.createRef(); this.start = this.start.bind(this); this.resetFilesys = this.resetFilesys.bind(this); this.createEmulator = this.createEmulator.bind(this); this.toggleEmulatorState = this.toggleEmulatorState.bind(this); this.state = { settings: loadSettings(), filesystemPromise: null, filesystem: null, emulator: null, drawerOpen: false, application: undefined, }; } async resetFilesys() { if (!this.state.filesystem) { return; } await this.state.filesystem.delete(); this.setState({ filesystemPromise: null, filesystem: null, drawerOpen: false, }); this.output.current?.clear(); location.reload(); } _onEmulatorStateChanged(s: EmulationState, persistFs: boolean) { if (isFinalState(s) && persistFs) { this.setState({ filesystemPromise: null, filesystem: null }); this.initFilesys(true); } else { this.forceUpdate(); } } initFilesys(force: boolean = false) { if (!force && this.state.filesystemPromise) { return this.state.filesystemPromise; } const promise = new Promise((resolve) => { if (!force) { this.logLine("Loading filesystem..."); } setupFilesystem( (current, total, file) => { this.logLine(`Processing filesystem (${current}/${total}): ${file}`); }, makePercentHandler((percent) => { this.logLine(`Downloading filesystem: ${percent}%`); }), ).then(resolve); }); promise.then((filesystem) => this.setState({ filesystem })); this.setState({ filesystemPromise: promise }); return promise; } setDrawerOpen(drawerOpen: boolean) { this.setState({ drawerOpen }); } async start() { await this.initFilesys(); this.setDrawerOpen(true); } logLine(line: string) { this.output.current?.logLine(line); } logLines(lines: string[]) { this.output.current?.logLines(lines); } isEmulatorPaused() { return ( this.state.emulator && this.state.emulator.getState() == EmulationState.Paused ); } toggleEmulatorState() { if (this.isEmulatorPaused()) { this.state.emulator?.resume(); } else { this.state.emulator?.pause(); } } async createEmulator(userFile: string) { this.state.emulator?.stop(); this.output.current?.clear(); this.setDrawerOpen(false); this.logLine("Starting emulation..."); if (this.state.filesystemPromise) { await this.state.filesystemPromise; } const persistFs = this.state.settings.persist; const new_emulator = new Emulator( (l) => this.logLines(l), (s) => this._onEmulatorStateChanged(s, persistFs), ); //new_emulator.onTerminate().then(() => this.setState({ emulator: null })); this.setState({ emulator: new_emulator, application: userFile }); new_emulator.start(this.state.settings, userFile); } render() { return ( <>
{ saveSettings(s); this.setState({ settings: s }); }} /> {!this.state.filesystem ? ( <> ) : ( this.setState({ drawerOpen: o })} > Filesystem Explorer Filesystem Explorer )}
); } }