diff --git a/src/less/root.less b/src/less/root.less index 9061eb0..1fc57fb 100644 --- a/src/less/root.less +++ b/src/less/root.less @@ -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; diff --git a/src/less/floppy.less b/src/less/settings.less similarity index 81% rename from src/less/floppy.less rename to src/less/settings.less index 1318db4..c04a124 100644 --- a/src/less/floppy.less +++ b/src/less/settings.less @@ -13,3 +13,9 @@ #file-input { display: none; } + +.settings { + legend > img { + margin-right: 5px; + } +} diff --git a/src/renderer/card-drive.tsx b/src/renderer/card-drive.tsx new file mode 100644 index 0000000..0277a92 --- /dev/null +++ b/src/renderer/card-drive.tsx @@ -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 { + 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 ( +
+
+
+

+ + Modify C: Drive +

+
+
+

+ windows95 (this app) uses a raw disk image. Windows 95 (the + operating system) is fragile, so adding or removing files is + risky. +

+ {advice} +
+
+
+ ); + } + + public renderAdviceWindows(): JSX.Element { + return ( +
+ Changing the disk on Windows +

+ 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{" "} + + shell.openExternal( + "https://www.osforensics.com/tools/mount-disk-images.html" + ) + } + > + OSFMount + + . I am not affiliated with it, so please use it at your own risk. +

+ {this.renderMountButton("Windows Explorer")} +
+ ); + } + + public renderAdviceMac(): JSX.Element { + return ( +
+ Changing the disk on macOS +

+ 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. +

+ {this.renderMountButton("Finder")} +
+ ); + } + + public renderAdviceLinux(): JSX.Element { + return ( +
+ Changing the disk on Linux +

+ 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 mount tool, + which is likely installed on your machine. +

+ {this.renderMountButton("file viewer")} +
+ ); + } + + public renderMountButton(explorer: string) { + return ( + + ); + } +} diff --git a/src/renderer/card-floppy.tsx b/src/renderer/card-floppy.tsx deleted file mode 100644 index 9b316ec..0000000 --- a/src/renderer/card-floppy.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react"; - -export interface CardFloppyProps { - setFloppyPath: (path: string) => void; - floppyPath?: string; -} - -export class CardFloppy extends React.Component { - constructor(props: CardFloppyProps) { - super(props); - - this.onChange = this.onChange.bind(this); - } - - public render() { - return ( -
-
-
-

Floppy Drive

-
-
- -

- windows95 comes with a virtual floppy drive. If you have floppy - disk images in the "img" format, you can mount them here. -

-

- 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. -

-

- Once you've mounted a disk image, you might have to reboot your - virtual windows95 machine from scratch. -

-

- {this.props.floppyPath - ? `Inserted Floppy Disk: ${this.props.floppyPath}` - : `No floppy mounted`} -

- - -
-
-
- ); - } - - public onChange(event: React.ChangeEvent) { - 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`); - } - } -} diff --git a/src/renderer/card-settings.tsx b/src/renderer/card-settings.tsx new file mode 100644 index 0000000..93962a3 --- /dev/null +++ b/src/renderer/card-settings.tsx @@ -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 ( +
+
+
+

+ + Settings +

+
+
+ {this.renderFloppy()} +
+ {this.renderState()} +
+
+
+ ); + } + + public renderFloppy() { + const { floppy } = this.props; + + return ( +
+ + + Floppy + + +

+ windows95 comes with a virtual floppy drive. It can mount floppy disk + images in the "img" format. +

+

+ 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. +

+

+ Once you've mounted a disk image, you might have to boot your virtual + windows95 machine from scratch. +

+

+ {floppy + ? `Inserted Floppy Disk: ${floppy.path}` + : `No floppy mounted`} +

+ +
+ ); + } + + public renderState() { + const { isStateReset } = this.state; + const { bootFromScratch } = this.props; + + return ( +
+ + + Reset machine state + +
+

+ 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.{" "} + All your changes will be lost. +

+ + +
+
+ ); + } + + /** + * Handle a change in the floppy input + * + * @param event + */ + private onChangeFloppy(event: React.ChangeEvent) { + 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 }); + } +} diff --git a/src/renderer/card-start.tsx b/src/renderer/card-start.tsx index 96d9612..fdcce69 100644 --- a/src/renderer/card-start.tsx +++ b/src/renderer/card-start.tsx @@ -13,7 +13,9 @@ export class CardStart extends React.Component { id="win95" onClick={this.props.startEmulator} > - Start Windows 95 + + Start Windows 95 +

Hit ESC to lock or unlock your mouse diff --git a/src/renderer/card-state.tsx b/src/renderer/card-state.tsx deleted file mode 100644 index 92d3d2e..0000000 --- a/src/renderer/card-state.tsx +++ /dev/null @@ -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 { - constructor(props: CardStateProps) { - super(props); - - this.onReset = this.onReset.bind(this); - } - - public render() { - return ( -
-
-
-

Machine State

-
-
-

- 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. -

- - -
-
-
- ); - } - - public async onReset() { - if (fs.existsSync(CONSTANTS.STATE_PATH)) { - await fs.remove(CONSTANTS.STATE_PATH); - } - } -} diff --git a/src/renderer/emulator.tsx b/src/renderer/emulator.tsx index d33c6b6..46780bd 100644 --- a/src/renderer/emulator.tsx +++ b/src/renderer/emulator.tsx @@ -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 = ( - this.setState({ floppyFile })} - floppyPath={this.state.floppyFile} + this.setState({ floppyFile })} + bootFromScratch={this.bootFromScratch} + floppy={floppyFile} /> ); - } else if (currentUiCard === "state") { - card = ; + } else if (currentUiCard === "drive") { + card = ; } else { card = ; } @@ -348,16 +363,16 @@ export class Emulator extends React.Component<{}, EmulatorState> { private async saveState(): Promise { 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)); diff --git a/src/renderer/start-menu.tsx b/src/renderer/start-menu.tsx index 38c0f55..b9b599e 100644 --- a/src/renderer/start-menu.tsx +++ b/src/renderer/start-menu.tsx @@ -14,19 +14,23 @@ export class StartMenu extends React.Component { public render() { return ( diff --git a/static/boot-fresh.png b/static/boot-fresh.png new file mode 100644 index 0000000..bb1a384 Binary files /dev/null and b/static/boot-fresh.png differ diff --git a/static/drive.png b/static/drive.png new file mode 100644 index 0000000..9150a0b Binary files /dev/null and b/static/drive.png differ diff --git a/static/floppy.png b/static/floppy.png new file mode 100644 index 0000000..6c77f7e Binary files /dev/null and b/static/floppy.png differ diff --git a/static/index.html b/static/index.html index cb7b4a5..336f952 100644 --- a/static/index.html +++ b/static/index.html @@ -12,5 +12,15 @@
+ + + + + + + + + + \ No newline at end of file diff --git a/static/reset-state.png b/static/reset-state.png new file mode 100644 index 0000000..6cc0a4b Binary files /dev/null and b/static/reset-state.png differ diff --git a/static/reset.png b/static/reset.png new file mode 100644 index 0000000..4d78502 Binary files /dev/null and b/static/reset.png differ diff --git a/static/run.png b/static/run.png new file mode 100644 index 0000000..de696a1 Binary files /dev/null and b/static/run.png differ diff --git a/static/select-floppy.png b/static/select-floppy.png new file mode 100644 index 0000000..9232c1b Binary files /dev/null and b/static/select-floppy.png differ diff --git a/static/settings.png b/static/settings.png new file mode 100644 index 0000000..90e5340 Binary files /dev/null and b/static/settings.png differ diff --git a/static/show-disk-image.png b/static/show-disk-image.png new file mode 100644 index 0000000..42f6338 Binary files /dev/null and b/static/show-disk-image.png differ