hello-wordl/src/App.tsx

198 lines
6 KiB
TypeScript
Raw Normal View History

2021-12-31 03:43:09 +02:00
import "./App.css";
2022-01-17 20:42:56 +02:00
import { maxGuesses, seed } from "./util";
2021-12-31 03:43:09 +02:00
import Game from "./Game";
2022-01-18 00:25:44 +02:00
import { useEffect, useState } from "react";
2022-01-17 20:42:56 +02:00
import { About } from "./About";
2022-01-01 04:04:48 +02:00
2022-01-18 00:25:44 +02:00
function useSetting<T>(
key: string,
initial: T
): [T, (value: T | ((t: T) => T)) => void] {
2022-01-17 20:42:56 +02:00
const [current, setCurrent] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initial;
} catch (e) {
return initial;
}
});
const setSetting = (value: T | ((t: T) => T)) => {
try {
const v = value instanceof Function ? value(current) : value;
setCurrent(v);
window.localStorage.setItem(key, JSON.stringify(v));
} catch (e) {}
};
return [current, setSetting];
2022-01-17 14:34:32 +02:00
}
function App() {
2022-01-22 18:46:14 +02:00
type Page = "game" | "about" | "settings";
const [page, setPage] = useState<Page>("game");
2022-01-17 20:42:56 +02:00
const prefersDark =
2022-01-18 00:25:44 +02:00
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
2022-01-17 20:42:56 +02:00
const [dark, setDark] = useSetting<boolean>("dark", prefersDark);
2022-01-27 23:13:39 +02:00
const [colorBlind, setColorBlind] = useSetting<boolean>("colorblind", false);
2022-01-22 18:46:14 +02:00
const [difficulty, setDifficulty] = useSetting<number>("difficulty", 0);
2022-01-28 00:05:19 +02:00
const [keyboard, setKeyboard] = useSetting<string>(
"keyboard",
"qwertyuiop-asdfghjkl-BzxcvbnmE"
);
const [enterLeft, setEnterLeft] = useSetting<boolean>("enter-left", false);
2022-01-17 20:42:56 +02:00
2022-01-18 00:25:44 +02:00
useEffect(() => {
document.body.className = dark ? "dark" : "";
setTimeout(() => {
2022-01-22 23:43:34 +02:00
// Avoid transition on page load
document.body.style.transition = "0.3s background-color ease-out";
}, 1);
2022-01-18 00:25:44 +02:00
}, [dark]);
2022-01-22 18:46:14 +02:00
const link = (emoji: string, label: string, page: Page) => (
<a
className="emoji-link"
href="#"
onClick={() => setPage(page)}
title={label}
aria-label={label}
>
{emoji}
</a>
);
2022-01-01 04:04:48 +02:00
return (
2022-01-27 23:13:39 +02:00
<div className={"App-container" + (colorBlind ? " color-blind" : "")}>
2022-01-18 00:25:44 +02:00
<h1>
2022-01-22 18:46:14 +02:00
<span
2022-01-22 23:43:34 +02:00
style={{
color: difficulty > 0 ? "#e66" : "inherit",
fontStyle: difficulty > 1 ? "italic" : "inherit",
}}
2022-01-22 18:46:14 +02:00
>
hell
</span>
o wordl
2022-01-18 00:25:44 +02:00
</h1>
<div className="top-right">
2022-01-17 14:34:32 +02:00
{page !== "game" ? (
2022-01-22 18:46:14 +02:00
link("❌", "Close", "game")
2022-01-17 14:34:32 +02:00
) : (
<>
2022-01-22 18:46:14 +02:00
{link("❓", "About", "about")}
{link("⚙️", "Settings", "settings")}
2022-01-17 14:34:32 +02:00
</>
)}
2022-01-01 04:04:48 +02:00
</div>
2022-01-17 14:34:32 +02:00
<div
style={{
position: "absolute",
left: 5,
top: 5,
visibility: page === "game" ? "visible" : "hidden",
}}
>
2022-01-08 01:01:19 +02:00
<a
href="#"
onClick={() =>
(document.location = seed
2022-01-08 02:31:19 +02:00
? "?"
2022-01-18 00:25:44 +02:00
: "?seed=" +
new Date().toISOString().replace(/-/g, "").slice(0, 8))
2022-01-08 01:01:19 +02:00
}
>
{seed ? "Random" : "Today's"}
</a>
</div>
2022-01-27 23:13:39 +02:00
{page === "about" && <About />}
2022-01-17 20:42:56 +02:00
{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>
2022-01-27 22:57:59 +02:00
<div className="Settings-setting">
<input
2022-01-27 23:13:39 +02:00
id="colorblind-setting"
2022-01-27 22:57:59 +02:00
type="checkbox"
2022-01-27 23:13:39 +02:00
checked={colorBlind}
onChange={() => setColorBlind((x: boolean) => !x)}
2022-01-27 22:57:59 +02:00
/>
2022-01-27 23:13:39 +02:00
<label htmlFor="colorblind-setting">Color blind mode</label>
2022-01-27 22:57:59 +02:00
</div>
2022-01-17 20:42:56 +02:00
<div className="Settings-setting">
<input
2022-01-22 18:46:14 +02:00
id="difficulty-setting"
type="range"
min="0"
max="2"
value={difficulty}
onChange={(e) => setDifficulty(+e.target.value)}
2022-01-17 20:42:56 +02:00
/>
2022-01-22 18:46:14 +02:00
<div>
<label htmlFor="difficulty-setting">Difficulty:</label>
<strong>{["Normal", "Hard", "Ultra Hard"][difficulty]}</strong>
<div
style={{
fontSize: 14,
height: 40,
marginLeft: 8,
marginTop: 8,
}}
>
{
[
2022-01-22 23:43:34 +02:00
`Guesses must be valid dictionary words.`,
2022-01-22 18:46:14 +02:00
`Wordle's "Hard Mode". Green letters must stay fixed, and yellow letters must be reused.`,
2022-01-22 23:43:34 +02:00
`An even stricter Hard Mode. Yellow letters must move away from where they were clued, and gray clues must be obeyed.`,
2022-01-22 18:46:14 +02:00
][difficulty]
}
</div>
</div>
2022-01-17 20:42:56 +02:00
</div>
2022-01-28 00:05:19 +02:00
<div className="Settings-setting">
<label htmlFor="keyboard-setting">Keyboard layout:</label>
<select
name="keyboard-setting"
id="keyboard-setting"
value={keyboard}
onChange={(e) => setKeyboard(e.target.value)}
>
<option value="qwertyuiop-asdfghjkl-BzxcvbnmE">QWERTY</option>
2022-01-28 13:29:30 +02:00
<option value="azertyuiop-qsdfghjklm-BwxcvbnE">AZERTY</option>
2022-01-28 00:05:19 +02:00
<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>
2022-01-17 20:42:56 +02:00
</div>
)}
2022-01-22 18:46:14 +02:00
<Game
maxGuesses={maxGuesses}
hidden={page !== "game"}
difficulty={difficulty}
2022-01-27 23:13:39 +02:00
colorBlind={colorBlind}
2022-01-28 00:05:19 +02:00
keyboardLayout={keyboard.replaceAll(
/[BE]/g,
(x) => (enterLeft ? "EB" : "BE")["BE".indexOf(x)]
)}
2022-01-22 18:46:14 +02:00
/>
2022-01-03 17:17:36 +02:00
</div>
2022-01-01 04:04:48 +02:00
);
}
export default App;