feat: Icons, better UI

This commit is contained in:
Felix Rieseberg 2019-08-23 22:58:56 +02:00
parent 33ef8abcc8
commit 7653d7294c
19 changed files with 368 additions and 157 deletions

View file

@ -1,7 +1,7 @@
@import "./status.less";
@import "./emulator.less";
@import "./info.less";
@import "./floppy.less";
@import "./settings.less";
/* GENERAL RESETS */
@ -40,13 +40,35 @@ section {
width: 75%;
max-width: 700px;
min-width: 400px;
.card-title {
img {
margin-right: 5px;
}
}
}
.nav-link > img,
.btn > img {
height: 24px;
margin-top: -3px;
margin-right: 4px;
}
.windows95 {
* {
user-select: none;
}
*:focus {
outline: none;
}
nav .nav-link,
nav .nav-logo {
height: 30px;
height: 33px;
line-height: 1.5;
display: flex;
}
nav .nav-logo img {
@ -61,6 +83,11 @@ section {
font-weight: bold;
}
.btn {
height: 40px;
padding-top: 6px;
}
.btn:focus {
border-color: #fff #000 #000 #fff;
outline: 5px auto -webkit-focus-ring-color;

View file

@ -13,3 +13,9 @@
#file-input {
display: none;
}
.settings {
legend > img {
margin-right: 5px;
}
}

111
src/renderer/card-drive.tsx Normal file
View file

@ -0,0 +1,111 @@
import * as React from "react";
import { shell } from "electron";
interface CardDriveProps {
showDiskImage: () => void;
}
interface CardDriveState {}
export class CardDrive extends React.Component<CardDriveProps, CardDriveState> {
constructor(props: CardDriveProps) {
super(props);
this.state = {};
}
public render() {
let advice: JSX.Element | null = null;
if (process.platform === "win32") {
advice = this.renderAdviceWindows();
} else if (process.platform === "darwin") {
advice = this.renderAdviceMac();
} else {
advice = this.renderAdviceLinux();
}
return (
<section>
<div className="card settings">
<div className="card-header">
<h2 className="card-title">
<img src="../../static/drive.png" />
Modify C: Drive
</h2>
</div>
<div className="card-body">
<p>
windows95 (this app) uses a raw disk image. Windows 95 (the
operating system) is fragile, so adding or removing files is
risky.
</p>
{advice}
</div>
</div>
</section>
);
}
public renderAdviceWindows(): JSX.Element {
return (
<fieldset>
<legend>Changing the disk on Windows</legend>
<p>
Windows 10 cannot mount raw disk images (ironically, macOS and Linux
can). However, tools exist that let you mount this drive, like the
freeware tool{" "}
<a
href="#"
onClick={() =>
shell.openExternal(
"https://www.osforensics.com/tools/mount-disk-images.html"
)
}
>
OSFMount
</a>
. I am not affiliated with it, so please use it at your own risk.
</p>
{this.renderMountButton("Windows Explorer")}
</fieldset>
);
}
public renderAdviceMac(): JSX.Element {
return (
<fieldset>
<legend>Changing the disk on macOS</legend>
<p>
macOS can mount the disk image directly. Click the button below to see
the disk image in Finder. Then, double-click the image to mount it.
</p>
{this.renderMountButton("Finder")}
</fieldset>
);
}
public renderAdviceLinux(): JSX.Element {
return (
<fieldset>
<legend>Changing the disk on Linux</legend>
<p>
There are plenty of tools that enable Linux users to mount and modify
disk images. The disk image used by windows95 is a raw "img" disk
image and can probably be mounted using the <code>mount</code> tool,
which is likely installed on your machine.
</p>
{this.renderMountButton("file viewer")}
</fieldset>
);
}
public renderMountButton(explorer: string) {
return (
<button className="btn" onClick={this.props.showDiskImage}>
<img src="../../static/show-disk-image.png" />
<span>Show disk image in {explorer}</span>
</button>
);
}
}

View file

@ -1,78 +0,0 @@
import * as React from "react";
export interface CardFloppyProps {
setFloppyPath: (path: string) => void;
floppyPath?: string;
}
export class CardFloppy extends React.Component<CardFloppyProps, {}> {
constructor(props: CardFloppyProps) {
super(props);
this.onChange = this.onChange.bind(this);
}
public render() {
return (
<section>
<div className="card">
<div className="card-header">
<h2 className="card-title">Floppy Drive</h2>
</div>
<div className="card-body">
<input
id="floppy-input"
type="file"
onChange={this.onChange}
style={{ display: "none" }}
/>
<p>
windows95 comes with a virtual floppy drive. If you have floppy
disk images in the "img" format, you can mount them here.
</p>
<p>
Back in the 90s and before CD-ROM became a popular format,
software was typically distributed on floppy disks. Some
developers have since released their apps or games for free,
usually on virtual floppy disks using the "img" format.
</p>
<p>
Once you've mounted a disk image, you might have to reboot your
virtual windows95 machine from scratch.
</p>
<p id="floppy-path">
{this.props.floppyPath
? `Inserted Floppy Disk: ${this.props.floppyPath}`
: `No floppy mounted`}
</p>
<button
id="floppy-select"
className="btn"
onClick={() =>
(document.querySelector("#floppy-input") as any).click()
}
>
Mount floppy disk
</button>
<button id="floppy-reboot" className="btn">
Reboot from scratch
</button>
</div>
</div>
</section>
);
}
public onChange(event: React.ChangeEvent<HTMLInputElement>) {
const floppyFile =
event.target.files && event.target.files.length > 0
? event.target.files[0]
: null;
if (floppyFile) {
this.props.setFloppyPath(floppyFile.path);
} else {
console.log(`Floppy: Input changed but no file selected`);
}
}
}

View file

@ -0,0 +1,161 @@
import * as React from "react";
import * as fs from "fs-extra";
import { CONSTANTS } from "../constants";
interface CardSettingsProps {
bootFromScratch: () => void;
setFloppy: (file: File) => void;
floppy?: File;
}
interface CardSettingsState {
isStateReset: boolean;
}
export class CardSettings extends React.Component<
CardSettingsProps,
CardSettingsState
> {
constructor(props: CardSettingsProps) {
super(props);
this.onChangeFloppy = this.onChangeFloppy.bind(this);
this.onResetState = this.onResetState.bind(this);
this.state = {
isStateReset: false
};
}
public render() {
return (
<section>
<div className="card settings">
<div className="card-header">
<h2 className="card-title">
<img src="../../static/settings.png" />
Settings
</h2>
</div>
<div className="card-body">
{this.renderFloppy()}
<hr />
{this.renderState()}
</div>
</div>
</section>
);
}
public renderFloppy() {
const { floppy } = this.props;
return (
<fieldset>
<legend>
<img src="../../static/floppy.png" />
Floppy
</legend>
<input
id="floppy-input"
type="file"
onChange={this.onChangeFloppy}
style={{ display: "none" }}
/>
<p>
windows95 comes with a virtual floppy drive. It can mount floppy disk
images in the "img" format.
</p>
<p>
Back in the 90s and before CD-ROMs became a popular, software was
typically distributed on floppy disks. Some developers have since
released their apps or games for free, usually on virtual floppy disks
using the "img" format.
</p>
<p>
Once you've mounted a disk image, you might have to boot your virtual
windows95 machine from scratch.
</p>
<p id="floppy-path">
{floppy
? `Inserted Floppy Disk: ${floppy.path}`
: `No floppy mounted`}
</p>
<button
className="btn"
onClick={() =>
(document.querySelector("#floppy-input") as any).click()
}
>
<img src="../../static/select-floppy.png" />
<span>Mount floppy disk</span>
</button>
</fieldset>
);
}
public renderState() {
const { isStateReset } = this.state;
const { bootFromScratch } = this.props;
return (
<fieldset>
<legend>
<img src="../../static/reset.png" />
Reset machine state
</legend>
<div>
<p>
windows95 stores changes to your machine (like saved files) in a
state file. If you encounter any trouble, you can reset your state
or boot Windows 95 from scratch.{" "}
<strong>All your changes will be lost.</strong>
</p>
<button
className="btn"
onClick={this.onResetState}
disabled={isStateReset}
style={{ marginRight: "5px" }}
>
<img src="../../static/reset-state.png" />
{isStateReset ? "State reset" : "Reset state"}
</button>
<button className="btn" onClick={bootFromScratch}>
<img src="../../static/boot-fresh.png" />
Boot from scratch
</button>
</div>
</fieldset>
);
}
/**
* Handle a change in the floppy input
*
* @param event
*/
private onChangeFloppy(event: React.ChangeEvent<HTMLInputElement>) {
const floppyFile =
event.target.files && event.target.files.length > 0
? event.target.files[0]
: null;
if (floppyFile) {
this.props.setFloppy(floppyFile);
} else {
console.log(`Floppy: Input changed but no file selected`);
}
}
/**
* Handle the state reset
*/
private async onResetState() {
if (fs.existsSync(CONSTANTS.STATE_PATH)) {
await fs.remove(CONSTANTS.STATE_PATH);
}
this.setState({ isStateReset: true });
}
}

View file

@ -13,7 +13,9 @@ export class CardStart extends React.Component<CardStartProps, {}> {
id="win95"
onClick={this.props.startEmulator}
>
Start Windows 95
<img src="../../static/run.png" />
<span>Start Windows 95</span>
<br />
<br />
<small>Hit ESC to lock or unlock your mouse</small>
</div>

View file

@ -1,47 +0,0 @@
import * as React from "react";
import * as fs from "fs-extra";
import { CONSTANTS } from "../constants";
export interface CardStateProps {
bootFromScratch: () => void;
}
export class CardState extends React.Component<CardStateProps, {}> {
constructor(props: CardStateProps) {
super(props);
this.onReset = this.onReset.bind(this);
}
public render() {
return (
<section>
<div className="card">
<div className="card-header">
<h2 className="card-title">Machine State</h2>
</div>
<div className="card-body">
<p>
windows95 stores any changes to your machine (like saved files) in
a state file. If you encounter any trouble, you can either reset
your state or boot Windows 95 from scratch.
</p>
<button className="btn" onClick={this.onReset}>
Reset state
</button>
<button className="btn" onClick={this.props.bootFromScratch}>
Reboot from scratch
</button>
</div>
</div>
</section>
);
}
public async onReset() {
if (fs.existsSync(CONSTANTS.STATE_PATH)) {
await fs.remove(CONSTANTS.STATE_PATH);
}
}
}

View file

@ -1,21 +1,21 @@
import * as React from "react";
import * as fs from "fs-extra";
import * as path from "path";
import { ipcRenderer, remote, shell, webFrame } from "electron";
import { ipcRenderer, remote, shell } from "electron";
import { CONSTANTS, IPC_COMMANDS } from "../constants";
import { getDiskImageSize } from "../utils/disk-image-size";
import { CardStart } from "./card-start";
import { StartMenu } from "./start-menu";
import { CardFloppy } from "./card-floppy";
import { CardState } from "./card-state";
import { CardSettings } from "./card-settings";
import { EmulatorInfo } from "./emulator-info";
import { CardDrive } from "./card-drive";
export interface EmulatorState {
currentUiCard: string;
emulator?: any;
scale: number;
floppyFile?: string;
floppyFile?: File;
isBootingFresh: boolean;
isCursorCaptured: boolean;
isInfoDisplayed: boolean;
@ -33,6 +33,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
this.stopEmulator = this.stopEmulator.bind(this);
this.restartEmulator = this.restartEmulator.bind(this);
this.resetEmulator = this.resetEmulator.bind(this);
this.bootFromScratch = this.bootFromScratch.bind(this);
this.state = {
isBootingFresh: false,
@ -87,13 +88,22 @@ export class Emulator extends React.Component<{}, EmulatorState> {
public setupUnloadListeners() {
const handleClose = async () => {
await this.saveState();
console.log(`Unload: Now done, quitting again.`);
this.isQuitting = true;
remote.app.quit();
setImmediate(() => {
remote.app.quit();
});
};
window.onbeforeunload = event => {
if (this.isQuitting) return;
if (this.isResetting) return;
if (this.isQuitting || this.isResetting) {
console.log(`Unload: Not preventing`);
return;
}
console.log(`Unload: Preventing to first save state`);
handleClose();
event.preventDefault();
@ -167,6 +177,10 @@ export class Emulator extends React.Component<{}, EmulatorState> {
ipcRenderer.on(IPC_COMMANDS.ZOOM_OUT, () => {
this.setScale(this.state.scale * 0.8);
});
ipcRenderer.on(IPC_COMMANDS.ZOOM_RESET, () => {
this.setScale(1);
});
}
/**
@ -175,7 +189,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
* 🤡
*/
public renderUI() {
const { isRunning, currentUiCard } = this.state;
const { isRunning, currentUiCard, floppyFile } = this.state;
if (isRunning) {
return null;
@ -183,15 +197,16 @@ export class Emulator extends React.Component<{}, EmulatorState> {
let card;
if (currentUiCard === "floppy") {
if (currentUiCard === "settings") {
card = (
<CardFloppy
setFloppyPath={floppyFile => this.setState({ floppyFile })}
floppyPath={this.state.floppyFile}
<CardSettings
setFloppy={floppyFile => this.setState({ floppyFile })}
bootFromScratch={this.bootFromScratch}
floppy={floppyFile}
/>
);
} else if (currentUiCard === "state") {
card = <CardState bootFromScratch={this.bootFromScratch} />;
} else if (currentUiCard === "drive") {
card = <CardDrive showDiskImage={this.showDiskImage} />;
} else {
card = <CardStart startEmulator={this.startEmulator} />;
}
@ -348,16 +363,16 @@ export class Emulator extends React.Component<{}, EmulatorState> {
private async saveState(): Promise<void> {
const { emulator } = this.state;
if (!emulator || !emulator.save_state) {
console.log(`restoreState: No emulator present`);
return;
}
return new Promise(resolve => {
if (!emulator || !emulator.save_state) {
console.log(`restoreState: No emulator present`);
return resolve();
}
emulator.save_state(async (error: Error, newState: ArrayBuffer) => {
if (error) {
console.warn(`saveState: Could not save state`, error);
return;
return resolve();
}
await fs.outputFile(CONSTANTS.STATE_PATH, Buffer.from(newState));

View file

@ -14,19 +14,23 @@ export class StartMenu extends React.Component<StartMenuProps, {}> {
public render() {
return (
<nav className="nav nav-bottom">
<a onClick={this.navigate} href="#" id="start" className="nav-logo">
<img src="../../static/start.png" alt="" />
<a onClick={this.navigate} href="#" id="start" className="nav-link">
<img src="../../static/start.png" alt="Start" />
<span>Start</span>
</a>
<div className="nav-menu">
<a onClick={this.navigate} href="#" id="floppy" className="nav-link">
Floppy Disk
<a
onClick={this.navigate}
href="#"
id="settings"
className="nav-link"
>
<img src="../../static/settings.png" />
<span>Settings</span>
</a>
<a onClick={this.navigate} href="#" id="state" className="nav-link">
Reset Machine
</a>
<a onClick={this.navigate} href="#" id="disk" className="nav-link">
Modify C: Drive
<a onClick={this.navigate} href="#" id="drive" className="nav-link">
<img src="../../static/drive.png" />
<span>Modify C: Drive</span>
</a>
</div>
</nav>

BIN
static/boot-fresh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
static/drive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/floppy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -12,5 +12,15 @@
<body class="paused windows95">
<div id="app"></div>
<script src="../src/renderer/app.tsx"></script>
<link rel="prefetch" href="../static/boot-fresh.png" />
<link rel="prefetch" href="../static/drive.png" />
<link rel="prefetch" href="../static/floppy.png" />
<link rel="prefetch" href="../static/reset-state.png" />
<link rel="prefetch" href="../static/reset.png" />
<link rel="prefetch" href="../static/run.png" />
<link rel="prefetch" href="../static/select-floppy.png" />
<link rel="prefetch" href="../static/settings.png" />
<link rel="prefetch" href="../static/show-disk-image.png" />
<link rel="prefetch" href="../static/start.png" />
</body>
</html>

BIN
static/reset-state.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
static/run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/select-floppy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
static/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
static/show-disk-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB