mirror of
https://github.com/felixrieseberg/windows95.git
synced 2026-05-09 00:24:09 +00:00
Redesign launcher UI with 98.css
Replace the sparse start/settings cards with a Win95-styled launcher: - Start screen is now a 'Welcome to Windows 95' dialog with a gradient side stripe, a rotating tip box, and a button column. - Settings is a tabbed Properties sheet (Floppy / Network / State) with group boxes, sunken read-only path fields, and OK/Cancel. - Vendored 98.css and its Pixelated MS Sans Serif fonts; dropped the old 95css-based .btn/.card/.nav classes. - Removed the bottom taskbar nav (start-menu.tsx) — navigation now goes through the dialog buttons so the launcher isn't mistaken for the running OS.
This commit is contained in:
@@ -1,117 +1,77 @@
|
|||||||
@import "./status.less";
|
@import "./status.less";
|
||||||
@import "./emulator.less";
|
@import "./emulator.less";
|
||||||
@import "./info.less";
|
@import "./info.less";
|
||||||
@import "./settings.less";
|
|
||||||
@import "./start.less";
|
@import "./start.less";
|
||||||
|
@import "./settings.less";
|
||||||
|
|
||||||
/* GENERAL RESETS */
|
// 98.css uses the actual MS Sans Serif bitmap font and pixel-exact bevels.
|
||||||
|
// Everything below is layout — the chrome comes from 98.css.
|
||||||
|
|
||||||
html, body {
|
@win-teal: #008080;
|
||||||
|
@win-silver: silver;
|
||||||
|
@win-font: "Pixelated MS Sans Serif", Arial, sans-serif;
|
||||||
|
|
||||||
|
* {
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
font-family: @win-font;
|
||||||
|
-webkit-font-smoothing: none;
|
||||||
body.paused > #emulator {
|
image-rendering: pixelated;
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.paused {
|
body.paused {
|
||||||
background: #008080;
|
background: @win-teal;
|
||||||
font-family: Courier;
|
|
||||||
|
> #emulator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#buttons {
|
button:not(:disabled),
|
||||||
user-select: none;
|
li[role="tab"],
|
||||||
|
.title-bar-controls button:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 98.css renders button text via text-shadow (color: transparent) so the
|
||||||
|
// bitmap font stays crisp; <img> children need their own alignment.
|
||||||
|
button img {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: @win-font;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100vw;
|
inset: 0;
|
||||||
height: 100vh;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
|
||||||
width: 75%;
|
|
||||||
max-width: 700px;
|
|
||||||
min-width: 400px;
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
img {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link > img,
|
|
||||||
.btn > img {
|
|
||||||
height: 24px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.windows95 {
|
|
||||||
* {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
*:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav .nav-link,
|
|
||||||
nav .nav-logo {
|
|
||||||
height: 37px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav .nav-logo img {
|
|
||||||
margin-left: 2px;
|
|
||||||
max-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav .nav-logo > span {
|
|
||||||
position: absolute;
|
|
||||||
top: 9px;
|
|
||||||
left: 37px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
height: 40px;
|
|
||||||
padding-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus {
|
|
||||||
border-color: #fff #000 #000 #fff;
|
|
||||||
outline: 5px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.active:before,
|
|
||||||
.btn:focus:before,
|
|
||||||
button.active:before,
|
|
||||||
button:focus:before,
|
|
||||||
input[type=submit].active:before,
|
|
||||||
input[type=submit]:focus:before {
|
|
||||||
border-color: #dedede grey grey #dedede;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
// Fix link colors
|
|
||||||
.link, .link:active, .link:link, .link:visited, a, a:active, a:link, a:visited {
|
|
||||||
color: #008080;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure a-elements in fieldsets receive click events
|
|
||||||
fieldset:before {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,21 +1,71 @@
|
|||||||
#floppy-path {
|
.settings-window {
|
||||||
font-size: .6rem;
|
width: 460px;
|
||||||
width: 100%;
|
|
||||||
height: 30px;
|
|
||||||
padding-left: 8px;
|
|
||||||
border-color: #000 #fff #fff #000;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 2px;
|
|
||||||
background-color: #c3c3c3;
|
|
||||||
line-height: 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-input {
|
> .window-body {
|
||||||
display: none;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.settings {
|
|
||||||
legend > img {
|
.settings-panel {
|
||||||
margin-right: 5px;
|
padding: 3px;
|
||||||
|
|
||||||
|
> .window-body {
|
||||||
|
margin: 12px;
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-window .field-row-stacked {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
font-family: "Pixelated MS Sans Serif", Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:read-only {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 75px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,99 @@
|
|||||||
#section-start {
|
// "Welcome to Windows" splash — modelled on the real first-boot dialog.
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> small {
|
.welcome {
|
||||||
margin-top: 25px;
|
width: 540px;
|
||||||
font-size: .8rem;
|
}
|
||||||
|
|
||||||
|
.welcome-body {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 4px;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-stripe {
|
||||||
|
width: 26px;
|
||||||
|
background: linear-gradient(180deg, #000 0%, navy 60%, #1084d0 100%);
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(180deg);
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-family: "Times New Roman", serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
vertical-align: baseline;
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-tip {
|
||||||
|
flex: 1;
|
||||||
|
background: #ffffe1;
|
||||||
|
box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf,
|
||||||
|
inset 2px 2px #0a0a0a;
|
||||||
|
padding: 12px 14px;
|
||||||
|
|
||||||
|
.welcome-tip-header {
|
||||||
|
border-bottom: 1px solid grey;
|
||||||
|
box-shadow: 0 1px 0 #fff;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions {
|
||||||
|
width: 130px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-spacer {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/less/vendor/98.css
vendored
Normal file
2
src/less/vendor/98.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/less/vendor/ms_sans_serif.woff
vendored
Normal file
BIN
src/less/vendor/ms_sans_serif.woff
vendored
Normal file
Binary file not shown.
BIN
src/less/vendor/ms_sans_serif.woff2
vendored
Normal file
BIN
src/less/vendor/ms_sans_serif.woff2
vendored
Normal file
Binary file not shown.
BIN
src/less/vendor/ms_sans_serif_bold.woff
vendored
Normal file
BIN
src/less/vendor/ms_sans_serif_bold.woff
vendored
Normal file
Binary file not shown.
BIN
src/less/vendor/ms_sans_serif_bold.woff2
vendored
Normal file
BIN
src/less/vendor/ms_sans_serif_bold.woff2
vendored
Normal file
Binary file not shown.
@@ -8,12 +8,16 @@ interface CardSettingsProps {
|
|||||||
setCdrom: (cdrom: File) => void;
|
setCdrom: (cdrom: File) => void;
|
||||||
setSmbSharePath: (path: string) => void;
|
setSmbSharePath: (path: string) => void;
|
||||||
pickFolder: () => Promise<string | null>;
|
pickFolder: () => Promise<string | null>;
|
||||||
|
navigate: (to: string) => void;
|
||||||
floppy?: File;
|
floppy?: File;
|
||||||
cdrom?: File;
|
cdrom?: File;
|
||||||
smbSharePath: string;
|
smbSharePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tab = "floppy" | "network" | "state";
|
||||||
|
|
||||||
interface CardSettingsState {
|
interface CardSettingsState {
|
||||||
|
tab: Tab;
|
||||||
isStateReset: boolean;
|
isStateReset: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,187 +33,166 @@ export class CardSettings extends React.Component<
|
|||||||
this.onResetState = this.onResetState.bind(this);
|
this.onResetState = this.onResetState.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
tab: "floppy",
|
||||||
isStateReset: false,
|
isStateReset: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const { tab } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<div className="window settings-window">
|
||||||
<div className="card settings">
|
<div className="title-bar">
|
||||||
<div className="card-header">
|
<div className="title-bar-text">windows95 Properties</div>
|
||||||
<h2 className="card-title">
|
<div className="title-bar-controls">
|
||||||
<img src="../../static/settings.png" />
|
<button aria-label="Help" disabled />
|
||||||
Settings
|
<button
|
||||||
</h2>
|
aria-label="Close"
|
||||||
</div>
|
onClick={() => this.props.navigate("start")}
|
||||||
<div className="card-body">
|
/>
|
||||||
{this.renderCdrom()}
|
|
||||||
<hr />
|
|
||||||
{this.renderFloppy()}
|
|
||||||
<hr />
|
|
||||||
{this.renderSmbShare()}
|
|
||||||
<hr />
|
|
||||||
{this.renderState()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div className="window-body">
|
||||||
|
<menu role="tablist">
|
||||||
|
{this.renderTab("floppy", "Floppy Drive")}
|
||||||
|
{this.renderTab("network", "Network Share")}
|
||||||
|
{this.renderTab("state", "Machine State")}
|
||||||
|
</menu>
|
||||||
|
<div className="window settings-panel" role="tabpanel">
|
||||||
|
<div className="window-body">
|
||||||
|
{tab === "floppy" && this.renderFloppy()}
|
||||||
|
{tab === "network" && this.renderSmbShare()}
|
||||||
|
{tab === "state" && this.renderState()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="settings-footer">
|
||||||
|
<button
|
||||||
|
className="default"
|
||||||
|
onClick={() => this.props.navigate("start")}
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
<button onClick={() => this.props.navigate("start")}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderCdrom() {
|
private renderTab(id: Tab, label: string) {
|
||||||
// CD is currently not working, so.. let's return nothing.
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const { cdrom } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset>
|
<li
|
||||||
<legend>
|
role="tab"
|
||||||
<img src="../../static/cdrom.png" />
|
aria-selected={this.state.tab === id}
|
||||||
CD-ROM
|
onClick={() => this.setState({ tab: id })}
|
||||||
</legend>
|
>
|
||||||
<input
|
<a href="#">{label}</a>
|
||||||
id="cdrom-input"
|
</li>
|
||||||
type="file"
|
|
||||||
onChange={this.onChangeCdrom}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
/>
|
|
||||||
<p>
|
|
||||||
windows95 comes with a virtual CD drive. It can mount images in the
|
|
||||||
"iso" format.
|
|
||||||
</p>
|
|
||||||
<p id="floppy-path">
|
|
||||||
{cdrom ? `Inserted CD: ${cdrom?.name}` : `No CD mounted`}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
className="btn"
|
|
||||||
onClick={() =>
|
|
||||||
(document.querySelector("#cdrom-input") as any).click()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<img src="../../static/select-cdrom.png" />
|
|
||||||
<span>Mount CD</span>
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderSmbShare() {
|
private renderFloppy() {
|
||||||
const { smbSharePath } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset>
|
|
||||||
<legend>Network Share</legend>
|
|
||||||
<p>
|
|
||||||
A folder on your computer is exposed inside Windows 95 as a
|
|
||||||
network drive. From inside Windows, open Start → Run and type{" "}
|
|
||||||
<code>\\HOST\HOST</code> to browse it, or use Map Network Drive to
|
|
||||||
give it a drive letter.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Shared folder: <code>{smbSharePath}</code>
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
className="btn"
|
|
||||||
onClick={async () => {
|
|
||||||
const picked = await this.props.pickFolder();
|
|
||||||
if (picked) this.props.setSmbSharePath(picked);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>Choose folder</span>
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderFloppy() {
|
|
||||||
const { floppy } = this.props;
|
const { floppy } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>Drive A:</legend>
|
||||||
<img src="../../static/floppy.png" />
|
|
||||||
Floppy
|
|
||||||
</legend>
|
|
||||||
<input
|
<input
|
||||||
id="floppy-input"
|
id="floppy-input"
|
||||||
type="file"
|
type="file"
|
||||||
onChange={this.onChangeFloppy}
|
onChange={this.onChangeFloppy}
|
||||||
style={{ display: "none" }}
|
style={{ display: "none" }}
|
||||||
/>
|
/>
|
||||||
<p>
|
<div className="settings-row">
|
||||||
windows95 comes with a virtual floppy drive. It can mount floppy disk
|
<img className="settings-icon" src="../../static/floppy.png" />
|
||||||
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.name}`
|
|
||||||
: `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>
|
<p>
|
||||||
windows95 stores changes to your machine (like saved files) in a
|
windows95 ships with a virtual 3½" floppy drive. Mount an{" "}
|
||||||
state file. If you encounter any trouble, you can reset your state
|
<code>.img</code> disk image here, then boot the machine to read it
|
||||||
or boot Windows 95 from scratch.{" "}
|
from inside Windows.
|
||||||
<strong>All your changes will be lost.</strong>
|
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="field-row-stacked">
|
||||||
|
<label htmlFor="floppy-path">Mounted image</label>
|
||||||
|
<input
|
||||||
|
id="floppy-path"
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={floppy ? floppy.name : "(No disk in drive)"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="settings-buttons">
|
||||||
<button
|
<button
|
||||||
className="btn"
|
onClick={() =>
|
||||||
onClick={this.onResetState}
|
(document.querySelector("#floppy-input") as any).click()
|
||||||
disabled={isStateReset}
|
}
|
||||||
style={{ marginRight: "5px" }}
|
|
||||||
>
|
>
|
||||||
<img src="../../static/reset-state.png" />
|
Mount image...
|
||||||
{isStateReset ? "State reset" : "Reset state"}
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={bootFromScratch}>
|
|
||||||
<img src="../../static/boot-fresh.png" />
|
|
||||||
Boot from scratch
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private renderSmbShare() {
|
||||||
* Handle a change in the floppy input
|
const { smbSharePath } = this.props;
|
||||||
*
|
|
||||||
* @param event
|
return (
|
||||||
*/
|
<fieldset>
|
||||||
|
<legend>\\HOST\HOST</legend>
|
||||||
|
<div className="settings-row">
|
||||||
|
<img className="settings-icon" src="../../static/show-disk-image.png" />
|
||||||
|
<p>
|
||||||
|
A folder on your computer is exposed inside Windows 95 as a network
|
||||||
|
drive. From inside Windows, open Start → Run and type{" "}
|
||||||
|
<code>\\HOST\HOST</code> — or use Map Network Drive to give it a
|
||||||
|
letter.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="field-row-stacked">
|
||||||
|
<label htmlFor="smb-path">Shared folder</label>
|
||||||
|
<input id="smb-path" type="text" readOnly value={smbSharePath} />
|
||||||
|
</div>
|
||||||
|
<div className="settings-buttons">
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
const picked = await this.props.pickFolder();
|
||||||
|
if (picked) this.props.setSmbSharePath(picked);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Choose folder...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderState() {
|
||||||
|
const { isStateReset } = this.state;
|
||||||
|
const { bootFromScratch } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<legend>Reset</legend>
|
||||||
|
<div className="settings-row">
|
||||||
|
<img className="settings-icon" src="../../static/reset.png" />
|
||||||
|
<p>
|
||||||
|
Changes to your machine (saved files, installed programs) are stored
|
||||||
|
in a state file. If something breaks, you can either discard that
|
||||||
|
state or boot a fresh copy of Windows from scratch.{" "}
|
||||||
|
<strong>All your changes will be lost.</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="settings-buttons">
|
||||||
|
<button onClick={this.onResetState} disabled={isStateReset}>
|
||||||
|
{isStateReset ? "State has been reset" : "Reset state"}
|
||||||
|
</button>
|
||||||
|
<button onClick={bootFromScratch}>Boot from scratch</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private onChangeFloppy(event: React.ChangeEvent<HTMLInputElement>) {
|
private onChangeFloppy(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const floppyFile =
|
const floppyFile =
|
||||||
event.target.files && event.target.files.length > 0
|
event.target.files && event.target.files.length > 0
|
||||||
@@ -223,27 +206,19 @@ export class CardSettings extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a change in the cdrom input
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
private onChangeCdrom(event: React.ChangeEvent<HTMLInputElement>) {
|
private onChangeCdrom(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const CdromFile =
|
const cdromFile =
|
||||||
event.target.files && event.target.files.length > 0
|
event.target.files && event.target.files.length > 0
|
||||||
? event.target.files[0]
|
? event.target.files[0]
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (CdromFile) {
|
if (cdromFile) {
|
||||||
this.props.setCdrom(CdromFile);
|
this.props.setCdrom(cdromFile);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Cdrom: Input changed but no file selected`);
|
console.log(`Cdrom: Input changed but no file selected`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the state reset
|
|
||||||
*/
|
|
||||||
private async onResetState() {
|
private async onResetState() {
|
||||||
await resetState();
|
await resetState();
|
||||||
this.setState({ isStateReset: true });
|
this.setState({ isStateReset: true });
|
||||||
|
|||||||
@@ -2,18 +2,64 @@ import * as React from "react";
|
|||||||
|
|
||||||
export interface CardStartProps {
|
export interface CardStartProps {
|
||||||
startEmulator: () => void;
|
startEmulator: () => void;
|
||||||
|
navigate: (to: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CardStart extends React.Component<CardStartProps, {}> {
|
const TIPS = [
|
||||||
|
"Press the Escape key at any time to release or recapture your mouse cursor.",
|
||||||
|
"You can mount a floppy image from Settings before booting to install vintage software.",
|
||||||
|
"Map a host folder as a network drive: open Start → Run inside Windows and type \\\\HOST\\HOST.",
|
||||||
|
"Your machine state is saved automatically when you quit. Reset it from Settings if things get weird.",
|
||||||
|
"Use the Machine menu in the menubar to send Ctrl+Alt+Del and other special key combos.",
|
||||||
|
];
|
||||||
|
|
||||||
|
export class CardStart extends React.Component<CardStartProps> {
|
||||||
|
private tip = TIPS[Math.floor(Math.random() * TIPS.length)];
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<section id="section-start">
|
<div className="window welcome" id="welcome-window">
|
||||||
<button className="btn" id="win95" onClick={this.props.startEmulator}>
|
<div className="title-bar">
|
||||||
<img src="../../static/run.png" />
|
<div className="title-bar-text">Welcome</div>
|
||||||
<span>Start Windows 95</span>
|
<div className="title-bar-controls">
|
||||||
</button>
|
<button aria-label="Minimize" disabled />
|
||||||
<small>Hit ESC to lock or unlock your mouse</small>
|
<button aria-label="Maximize" disabled />
|
||||||
</section>
|
<button aria-label="Close" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="window-body welcome-body">
|
||||||
|
<aside className="welcome-stripe">
|
||||||
|
<span>Windows 95</span>
|
||||||
|
</aside>
|
||||||
|
<div className="welcome-main">
|
||||||
|
<h1 className="welcome-title">
|
||||||
|
Welcome to <span>Windows</span>
|
||||||
|
<small>95</small>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="welcome-tip">
|
||||||
|
<div className="welcome-tip-header">
|
||||||
|
<strong>Did you know...</strong>
|
||||||
|
</div>
|
||||||
|
<p>{this.tip}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="welcome-actions">
|
||||||
|
<button
|
||||||
|
id="win95"
|
||||||
|
className="default"
|
||||||
|
onClick={this.props.startEmulator}
|
||||||
|
>
|
||||||
|
<u>S</u>tart Windows 95
|
||||||
|
</button>
|
||||||
|
<button onClick={() => this.props.navigate("settings")}>
|
||||||
|
S<u>e</u>ttings...
|
||||||
|
</button>
|
||||||
|
<div className="welcome-spacer" />
|
||||||
|
<button disabled>What's New</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ipcRenderer, shell, webUtils } from "electron";
|
|||||||
import { CONSTANTS, IPC_COMMANDS } from "../constants";
|
import { CONSTANTS, IPC_COMMANDS } from "../constants";
|
||||||
import { getDiskImageSize } from "../utils/disk-image-size";
|
import { getDiskImageSize } from "../utils/disk-image-size";
|
||||||
import { CardStart } from "./card-start";
|
import { CardStart } from "./card-start";
|
||||||
import { StartMenu } from "./start-menu";
|
|
||||||
import { CardSettings } from "./card-settings";
|
import { CardSettings } from "./card-settings";
|
||||||
import { EmulatorInfo } from "./emulator-info";
|
import { EmulatorInfo } from "./emulator-info";
|
||||||
import { getStatePath } from "./utils/get-state-path";
|
import { getStatePath } from "./utils/get-state-path";
|
||||||
@@ -217,6 +216,9 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navigate = (target: string) =>
|
||||||
|
this.setState({ currentUiCard: target as "start" | "settings" });
|
||||||
|
|
||||||
let card;
|
let card;
|
||||||
|
|
||||||
if (currentUiCard === "settings") {
|
if (currentUiCard === "settings") {
|
||||||
@@ -233,22 +235,16 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
|||||||
floppy={floppyFile}
|
floppy={floppyFile}
|
||||||
cdrom={cdromFile}
|
cdrom={cdromFile}
|
||||||
smbSharePath={this.state.smbSharePath}
|
smbSharePath={this.state.smbSharePath}
|
||||||
|
navigate={navigate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
card = <CardStart startEmulator={this.startEmulator} />;
|
card = (
|
||||||
|
<CardStart startEmulator={this.startEmulator} navigate={navigate} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <section>{card}</section>;
|
||||||
<>
|
|
||||||
{card}
|
|
||||||
<StartMenu
|
|
||||||
navigate={(target) =>
|
|
||||||
this.setState({ currentUiCard: target as "start" | "settings" })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
export interface StartMenuProps {
|
|
||||||
navigate: (to: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StartMenu extends React.Component<StartMenuProps, {}> {
|
|
||||||
constructor(props: StartMenuProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.navigate = this.navigate.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
return (
|
|
||||||
<nav className="nav nav-bottom">
|
|
||||||
<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="settings"
|
|
||||||
className="nav-link"
|
|
||||||
>
|
|
||||||
<img src="../../static/settings.png" />
|
|
||||||
<span>Settings</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private navigate(event: React.SyntheticEvent<HTMLAnchorElement>) {
|
|
||||||
this.props.navigate(event.currentTarget.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>windows95</title>
|
<title>windows95</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="../src/less/vendor/95css.css">
|
<link rel="stylesheet" href="../src/less/vendor/98.css">
|
||||||
<link rel="stylesheet" href="../src/less/root.less">
|
<link rel="stylesheet" href="../src/less/root.less">
|
||||||
<!-- libv86 -->
|
<!-- libv86 -->
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user