Small refactoring

This commit is contained in:
momo5502
2025-04-30 13:07:40 +02:00
parent 3128ed5c42
commit 8b69f8c93c
6 changed files with 207 additions and 149 deletions

View File

@@ -1,3 +1,3 @@
Module['preRun'] = () => { Module["preRun"] = () => {
ENV = process.env; ENV = process.env;
}; };

View File

@@ -1,8 +1,8 @@
import { ThemeProvider } from "@/components/theme-provider"; import { ThemeProvider } from "@/components/theme-provider";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { HashRouter, Route, Routes, Navigate } from "react-router-dom"; import { HashRouter, Route, Routes, Navigate } from "react-router-dom";
import { Playground } from "./Playground"; import { Playground } from "./playground";
import { LandingPage } from "./LandingPage"; import { LandingPage } from "./landing-page";
import "@fontsource/inter/100.css"; import "@fontsource/inter/100.css";
import "@fontsource/inter/200.css"; import "@fontsource/inter/200.css";

View File

@@ -1,7 +1,6 @@
import { useState, useRef, useReducer } from "react"; import React from "react";
import { Output } from "@/components/output";
import { Separator } from "@/components/ui/separator"; import { Output } from "@/components/output";
import { Emulator, EmulationState } from "./emulator"; import { Emulator, EmulationState } from "./emulator";
import { Filesystem, setupFilesystem } from "./filesystem"; import { Filesystem, setupFilesystem } from "./filesystem";
@@ -13,7 +12,7 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { createDefaultSettings } from "./settings"; import { createDefaultSettings, Settings } from "./settings";
import { SettingsMenu } from "@/components/settings-menu"; import { SettingsMenu } from "@/components/settings-menu";
import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons"; import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons";
@@ -30,176 +29,229 @@ import {
DrawerHeader, DrawerHeader,
DrawerTitle, DrawerTitle,
} from "@/components/ui/drawer"; } from "@/components/ui/drawer";
import { FilesystemExplorer } from "./FilesystemExplorer"; import { FilesystemExplorer } from "./filesystem-explorer";
export function Playground() { interface PlaygroundProps {}
const output = useRef<Output>(null); interface PlaygroundState {
const [settings, setSettings] = useState(createDefaultSettings()); settings: Settings;
const [emulator, setEmulator] = useState<Emulator | null>(null); filesystemPromise: Promise<Filesystem> | null;
const [drawerOpen, setDrawerOpen] = useState<boolean>(false); filesystem: Filesystem | null;
const [filesystem, setFilesystem] = useState<Filesystem | null>(null); emulator: Emulator | null;
const [filesystemPromise, setFilesystemPromise] = drawerOpen: boolean;
useState<Promise<Filesystem> | null>(null); }
const [, forceUpdate] = useReducer((x) => x + 1, 0);
async function resetFilesys() { export class Playground extends React.Component<
const fs = await initFilesys(); PlaygroundProps,
await fs.delete(); PlaygroundState
> {
private output: React.RefObject<Output | null>;
setFilesystemPromise(null); constructor(props: PlaygroundProps) {
setFilesystem(null); super(props);
setDrawerOpen(false);
output.current?.clear(); 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: createDefaultSettings(),
filesystemPromise: null,
filesystem: null,
emulator: null,
drawerOpen: false,
};
} }
function initFilesys() { async resetFilesys() {
if (filesystemPromise) { if (!this.state.filesystem) {
return filesystemPromise; return;
}
await this.state.filesystem.delete();
this.setState({
filesystemPromise: null,
filesystem: null,
drawerOpen: false,
});
this.output.current?.clear();
}
initFilesys() {
if (this.state.filesystemPromise) {
return this.state.filesystemPromise;
} }
const promise = new Promise<Filesystem>((resolve) => { const promise = new Promise<Filesystem>((resolve) => {
logLine("Loading filesystem..."); this.logLine("Loading filesystem...");
setupFilesystem((current, total, file) => { setupFilesystem((current, total, file) => {
logLine(`Processing filesystem (${current}/${total}): ${file}`); this.logLine(`Processing filesystem (${current}/${total}): ${file}`);
}).then(resolve); }).then(resolve);
}); });
promise.then(setFilesystem); promise.then((filesystem) => this.setState({ filesystem }));
setFilesystemPromise(promise); this.setState({ filesystemPromise: promise });
return promise; return promise;
} }
async function start() { setDrawerOpen(drawerOpen: boolean) {
await initFilesys(); this.setState({ drawerOpen });
setDrawerOpen(true);
} }
function logLine(line: string) { async start() {
output.current?.logLine(line); await this.initFilesys();
this.setDrawerOpen(true);
} }
function logLines(lines: string[]) { logLine(line: string) {
output.current?.logLines(lines); this.output.current?.logLine(line);
} }
function isEmulatorPaused() { logLines(lines: string[]) {
return emulator && emulator.getState() == EmulationState.Paused; this.output.current?.logLines(lines);
} }
function toggleEmulatorState() { isEmulatorPaused() {
if (isEmulatorPaused()) { return (
emulator?.resume(); this.state.emulator &&
this.state.emulator.getState() == EmulationState.Paused
);
}
toggleEmulatorState() {
if (this.isEmulatorPaused()) {
this.state.emulator?.resume();
} else { } else {
emulator?.pause(); this.state.emulator?.pause();
} }
} }
async function createEmulator(userFile: string) { async createEmulator(userFile: string) {
emulator?.stop(); this.state.emulator?.stop();
output.current?.clear(); this.output.current?.clear();
setDrawerOpen(false); this.setDrawerOpen(false);
logLine("Starting emulation..."); this.logLine("Starting emulation...");
if (filesystemPromise) { if (this.state.filesystemPromise) {
await filesystemPromise; await this.state.filesystemPromise;
} }
const new_emulator = new Emulator(logLines, (_) => forceUpdate()); const new_emulator = new Emulator(
new_emulator.onTerminate().then(() => setEmulator(null)); (l) => this.logLines(l),
setEmulator(new_emulator); (_) => this.forceUpdate(),
);
new_emulator.onTerminate().then(() => this.setState({ emulator: null }));
new_emulator.start(settings, userFile); this.setState({ emulator: new_emulator });
new_emulator.start(this.state.settings, userFile);
} }
return ( render() {
<> return (
<Header <>
title="Playground - Sogen" <Header
description="Playground to test and run Sogen, the Windows user space emulator, right in your browser." title="Playground - Sogen"
/> description="Playground to test and run Sogen, the Windows user space emulator, right in your browser."
<div className="h-[100dvh] flex flex-col"> />
<header className="flex shrink-0 items-center gap-2 border-b p-2 overflow-y-auto"> <div className="h-[100dvh] flex flex-col">
<Button size="sm" className="fancy" onClick={start}> <header className="flex shrink-0 items-center gap-2 border-b p-2 overflow-y-auto">
<PlayFill /> <span>Start</span> <Button size="sm" className="fancy" onClick={this.start}>
</Button> <PlayFill /> <span>Start</span>
</Button>
<Button <Button
disabled={!emulator} disabled={!this.state.emulator}
size="sm" size="sm"
variant="secondary" variant="secondary"
className="fancy" className="fancy"
onClick={() => emulator?.stop()} onClick={() => this.state.emulator?.stop()}
> >
<StopFill /> <span className="hidden sm:inline">Stop</span> <StopFill /> <span className="hidden sm:inline">Stop</span>
</Button> </Button>
<Button <Button
size="sm" size="sm"
disabled={!emulator} disabled={!this.state.emulator}
variant="secondary" variant="secondary"
className="fancy" className="fancy"
onClick={toggleEmulatorState} onClick={this.toggleEmulatorState}
> >
{isEmulatorPaused() ? ( {this.isEmulatorPaused() ? (
<> <>
<PlayFill /> <span className="hidden sm:inline">Resume</span> <PlayFill /> <span className="hidden sm:inline">Resume</span>
</> </>
) : (
<>
<PauseFill /> <span className="hidden sm:inline">Pause</span>
</>
)}
</Button>
<Popover>
<PopoverTrigger asChild>
<Button size="sm" variant="secondary" className="fancy">
<GearFill />{" "}
<span className="hidden sm:inline">Settings</span>
</Button>
</PopoverTrigger>
<PopoverContent>
<SettingsMenu
settings={this.state.settings}
onChange={(s) => this.setState({ settings: s })}
/>
</PopoverContent>
</Popover>
{!this.state.filesystem ? (
<></>
) : ( ) : (
<> <Drawer
<PauseFill /> <span className="hidden sm:inline">Pause</span> open={this.state.drawerOpen}
</> onOpenChange={(o) => this.setState({ drawerOpen: o })}
>
<DrawerContent>
<DrawerHeader>
<DrawerTitle className="hidden">
Filesystem Explorer
</DrawerTitle>
<DrawerDescription className="hidden">
Filesystem Explorer
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<FilesystemExplorer
filesystem={this.state.filesystem}
runFile={this.createEmulator}
resetFilesys={this.resetFilesys}
path={["c"]}
/>
</DrawerFooter>
</DrawerContent>
</Drawer>
)} )}
</Button>
<Popover> <div className="text-right flex-1">
<PopoverTrigger asChild> <StatusIndicator
<Button size="sm" variant="secondary" className="fancy"> state={
<GearFill /> <span className="hidden sm:inline">Settings</span> this.state.emulator
</Button> ? this.state.emulator.getState()
</PopoverTrigger> : EmulationState.Stopped
<PopoverContent> }
<SettingsMenu settings={settings} onChange={setSettings} /> />
</PopoverContent> </div>
</Popover> </header>
<div className="flex flex-1 flex-col gap-2 p-2 overflow-auto">
{!filesystem ? ( <Output ref={this.output} />
<></>
) : (
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerContent>
<DrawerHeader>
<DrawerTitle className="hidden">
Filesystem Explorer
</DrawerTitle>
<DrawerDescription className="hidden">
Filesystem Explorer
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<FilesystemExplorer
filesystem={filesystem}
runFile={createEmulator}
resetFilesys={resetFilesys}
path={["c"]}
/>
</DrawerFooter>
</DrawerContent>
</Drawer>
)}
<div className="text-right flex-1">
<StatusIndicator
state={emulator ? emulator.getState() : EmulationState.Stopped}
/>
</div> </div>
</header>
<div className="flex flex-1 flex-col gap-2 p-2 overflow-auto">
<Output ref={output} />
</div> </div>
</div> </>
</> );
); }
} }

View File

@@ -24,8 +24,10 @@ export class SettingsMenu extends React.Component<SettingsMenuProps, Settings> {
this.setState(() => settings); this.setState(() => settings);
} }
componentDidUpdate() { componentDidUpdate(_: SettingsMenuProps, oldSettings: Settings) {
this.props.onChange(this.state); if (JSON.stringify(oldSettings) !== JSON.stringify(this.state)) {
this.props.onChange(this.state);
}
} }
render() { render() {

View File

@@ -485,7 +485,7 @@ export class FilesystemExplorer extends React.Component<
_renderBreadcrumbElements() { _renderBreadcrumbElements() {
const elements = generateBreadcrumbElements(this.state.path); const elements = generateBreadcrumbElements(this.state.path);
return elements.map((e, index) => { const nodes = elements.map((e, index) => {
if (index == this.state.path.length) { if (index == this.state.path.length) {
return ( return (
<BreadcrumbItem key={`breadcrumb-item-${index}`}> <BreadcrumbItem key={`breadcrumb-item-${index}`}>
@@ -498,16 +498,20 @@ export class FilesystemExplorer extends React.Component<
const navigate = () => this.setState({ path: e.targetPath }); const navigate = () => this.setState({ path: e.targetPath });
return ( return (
<> <BreadcrumbItem key={`breadcrumb-item-${index}`}>
<BreadcrumbItem key={`breadcrumb-item-${index}`}> <BreadcrumbLink key={`breadcrumb-link-${index}`} onClick={navigate}>
<BreadcrumbLink key={`breadcrumb-link-${index}`} onClick={navigate}> {e.node}
{e.node} </BreadcrumbLink>
</BreadcrumbLink> </BreadcrumbItem>
</BreadcrumbItem>
<BreadcrumbSeparator key={`breadcrumb-separator-${index}`} />
</>
); );
}); });
return [
...nodes.map((n, index) => [
n,
<BreadcrumbSeparator key={`breadcrumb-separator-${index}`} />,
]),
].slice(0, -1);
} }
_renderBreadCrumb() { _renderBreadCrumb() {