optimle/web-optimle/www/src/index.tsx

118 lines
No EOL
4.6 KiB
TypeScript

import { h, render, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks';
import { Game } from './Game';
import { gameName, urlParam } from "./util";
import "./App.css";
import { About } from './About';
import { useLocalStorage } from './localstorage';
export function App() {
type Page = "game" | "about" | "settings";
const [page, setPage] = useState<Page>("game");
const prefersDark =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
const [dark, setDark] = useLocalStorage<boolean>("dark", prefersDark);
const [colorBlind, setColorBlind] = useLocalStorage<boolean>("colorblind", false);
const [keyboard, setKeyboard] = useLocalStorage<string>(
"keyboard",
"qwertyuiop-asdfghjkl-BzxcvbnmE"
);
const [enterLeft, setEnterLeft] = useLocalStorage<boolean>("enter-left", false);
useEffect(() => {
document.body.className = dark ? "dark" : "";
setTimeout(() => {
// Avoid transition on page load
document.body.style.transition = "0.3s background-color ease-out";
}, 1);
}, [dark]);
const link = (emoji: string, label: string, page: Page) => (
<button
className="emoji-link"
onClick={() => setPage(page)}
title={label}
aria-label={label}
>
{emoji}
</button>
);
return (
<div className={"App-container" + (colorBlind ? " color-blind" : "")}>
<h1>
{gameName}
</h1>
<div className="top-right">
{page !== "game" ? (
link("❌", "Close", "game")
) : (
<>
{link("❓", "About", "about")}
{link("⚙️", "Settings", "settings")}
</>
)}
</div>
{page === "about" && <About />}
{page === "settings" && (
<div className="Settings">
<div className="Settings-setting">
<input
id="dark-setting"
type="checkbox"
checked={dark}
onChange={() => setDark((x: boolean) => !x)}
/>
<label htmlFor="dark-setting">Dark theme</label>
</div>
<div className="Settings-setting">
<input
id="colorblind-setting"
type="checkbox"
checked={colorBlind}
onChange={() => setColorBlind((x: boolean) => !x)}
/>
<label htmlFor="colorblind-setting">High-contrast colors</label>
</div>
<div className="Settings-setting">
<label htmlFor="keyboard-setting">Keyboard layout:</label>
<select
name="keyboard-setting"
id="keyboard-setting"
value={keyboard}
onChange={(e) => setKeyboard((e as any).target.value)}
>
<option value="qwertyuiop-asdfghjkl-BzxcvbnmE">QWERTY</option>
<option value="azertyuiop-qsdfghjklm-BwxcvbnE">AZERTY</option>
<option value="qwertzuiop-asdfghjkl-ByxcvbnmE">QWERTZ</option>
<option value="BpyfgcrlE-aoeuidhtns-qjkxbmwvz">Dvorak</option>
<option value="qwfpgjluy-arstdhneio-BzxcvbkmE">Colemak</option>
</select>
<input
style={{ marginLeft: 20 }}
id="enter-left-setting"
type="checkbox"
checked={enterLeft}
onChange={() => setEnterLeft((x: boolean) => !x)}
/>
<label htmlFor="enter-left-setting">"Enter" on left side</label>
</div>
</div>
)}
<Game
hidden={page !== "game"}
colorBlind={colorBlind}
keyboardLayout={keyboard.replaceAll(
/[BE]/g,
(x) => (enterLeft ? "EB" : "BE")["BE".indexOf(x)]
)}
/>
</div>
);
}
render(<App />, document.body);