From 7653d7294c165bbfb67cfeaa673e39bd388e6d26 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Fri, 23 Aug 2019 22:58:56 +0200 Subject: [PATCH] feat: Icons, better UI --- src/less/root.less | 31 ++++- src/less/{floppy.less => settings.less} | 6 + src/renderer/card-drive.tsx | 111 ++++++++++++++++ src/renderer/card-floppy.tsx | 78 ------------ src/renderer/card-settings.tsx | 161 ++++++++++++++++++++++++ src/renderer/card-start.tsx | 4 +- src/renderer/card-state.tsx | 47 ------- src/renderer/emulator.tsx | 55 +++++--- src/renderer/start-menu.tsx | 22 ++-- static/boot-fresh.png | Bin 0 -> 2038 bytes static/drive.png | Bin 0 -> 1899 bytes static/floppy.png | Bin 0 -> 1810 bytes static/index.html | 10 ++ static/reset-state.png | Bin 0 -> 2118 bytes static/reset.png | Bin 0 -> 2052 bytes static/run.png | Bin 0 -> 1923 bytes static/select-floppy.png | Bin 0 -> 2037 bytes static/settings.png | Bin 0 -> 2025 bytes static/show-disk-image.png | Bin 0 -> 1983 bytes 19 files changed, 368 insertions(+), 157 deletions(-) rename src/less/{floppy.less => settings.less} (81%) create mode 100644 src/renderer/card-drive.tsx delete mode 100644 src/renderer/card-floppy.tsx create mode 100644 src/renderer/card-settings.tsx delete mode 100644 src/renderer/card-state.tsx create mode 100644 static/boot-fresh.png create mode 100644 static/drive.png create mode 100644 static/floppy.png create mode 100644 static/reset-state.png create mode 100644 static/reset.png create mode 100644 static/run.png create mode 100644 static/select-floppy.png create mode 100644 static/settings.png create mode 100644 static/show-disk-image.png 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 0000000000000000000000000000000000000000..bb1a384dc4e731f673ae2236a1452336f17dc40c GIT binary patch literal 2038 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6cc0a4b7c243ce4045b16afa062582e4687e19fb GIT binary patch literal 2118 zcmY*Z2{;s58=e`Bku14FM8=?0%w%M2V{Bu~PS)&9)>+IUO`6K20`85Dwmu#4snPY^Rr5Q1jNOmCw1mP@k1ROam$l1~q0MO0OcJ&FBcio3+ zrGzM}47o&}xIn#aB&sTU#qC5Tt0if25=AJqMvcSfgSj7viu;CiwOThT9n2l$H5beF zbvFxR3Fx<(zAAA0LKr!1E8Q zJqmO80i{dtYRDj`DfFB)!(*3I=?odcN_QuPV?K9mK`4hk(T%6ynLUm3d;M~9tD?5q zLO}`|r5M=o;r5=i9N?#0uKnkL=ht%;jTXHQas%g^HI2Iv=O0W3{oP5yBxZ-5nN^>D zkp^~k2~R89n={b;)k1`)dH@;G=2VXTmY$GD0GpcAKy|kX<%-F^@WRjEbc?#AzWo0{zYdl;hB3R8>@SXHn=pCAvAaY;rKG_Vv$}=(t*zM@g`mB<{j<%=y zTfM!0v~LfFSVx2DnBS%Awt_7$wGg7O&j(_T61%wkU&^$ z2HK8$MZR7#n6o~1GSb`^t@CR0QI?i-Rhjvu9;=n%zpZuD*4yQ8rn*?uMdkh@Jh0E> zjc0y!-V5qFPEFIY_(>?>j4Tk}DC65(PN%p&l5_%}CM`)n+8jkZQ-sXsoPptkw! z@nFL9gu#TNgbp82u0enKOxqNkq)735#tmPU^LiI29lO6dWT7yEc~VI^QtF^I`6+73 zDFFHTu!vBQsnb|WVeJx6x&m>{P(dt6@xv{icN^Rdn9i#Mt=Vn4x}I7fP%KZm>p>#nAb*H3gPts~<$f!vlhefjD>! zp2Yt|004B1K0n0cr~%Lzd?bOQA7cpnj?m}FJ81+A`W-@zFob#7IYP~dWE@l*egb|R zh7pEBp=fgO8GUE0#m{p7$`BSxrIPd!i0J5Oc(fLrNDe`0>gnkrjw2CBqy`_ML5U?$ z17b7?6y+Zz|KY*nC_&^f5;crSfbQ@H1QO3u4Pmeyp=4|xghW#gfg$Lb!6%4TA_XXSfUv0joJ zK7C65f7)lCDlNH2*Y-vGe_0D^IXPKt`G*^GlAv=}Xw{Ujx)vhnPzD15RINE$QEA!rL?) zrccqsOC82heNCIpFFoeBD(a15dTu<=W>vLIKuCw^%PpMhyAfy62c>$go(w3w5CAbv z)yKfm9mdoDOuD=_JmfqG0tg zY+lQNYI=;gu&$H+!_uk}JK`f*(!`&N_KTaX+b0F8S3Y#sJIqxd74QLTLB)*~FLrF? zuh+!A!CHTkw<=)Hp}Z+V*!xu($yv2RL_Lw}5h+qpEn_4)GbvL>!r$3umx`m(o($pS ztZN1rA&g8jK7F#XHUym-x5egWIz1^*lwI&unwc#a5nE`xopJQj{fF-15iTZ>^a-N_ z%2U9OyaH8!KzZb&z~cEE)TbFuuUp%}`JNPvY#HBki;+}+acj#}1Z1A881-8ExWrvI zkZ&z$R{;{D*4NjdXBcV%FK#OWAR)MX06W=zu%umS_s8|(6RNmviRg2Jw%g5LsxdDQ zel_iih2Ko3a6`efK)30cDW{e!`=rs>XT`OE{(g5>?z&!%<%*55kG(ahSU@54othUA znCCbZLMzi5>silXsHEu8Vm%i+fw_Pw(@s5*ZJahNVz+m;)sd7y7=;rB|H`$JOLvA2 sX3gi6%=PN|3tD*o6`Mk(pLP8hA~0ZR9@+?z*m>F3=JwdTrvAVG8^qAT_y7O^ literal 0 HcmV?d00001 diff --git a/static/reset.png b/static/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..4d785026bfdd928bcb985eeaa6bf9296e56ec39f GIT binary patch literal 2052 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 TyPsdQ`4AoL4x?|{96R+dPf3#< literal 0 HcmV?d00001 diff --git a/static/settings.png b/static/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..90e5340a4abe70eb4342bc9fed07afd02304764a GIT binary patch literal 2025 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 MxcK0zo%SC42grbZ-T(jq literal 0 HcmV?d00001