hello-wordl/src/App.tsx
2022-01-22 22:43:34 +01:00

156 lines
4.2 KiB
TypeScript

import "./App.css";
import { maxGuesses, seed } from "./util";
import Game from "./Game";
import { useEffect, useState } from "react";
import { About } from "./About";
function useSetting<T>(
key: string,
initial: T
): [T, (value: T | ((t: T) => T)) => void] {
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];
}
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] = useSetting<boolean>("dark", prefersDark);
const [difficulty, setDifficulty] = useSetting<number>("difficulty", 0);
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) => (
<a
className="emoji-link"
href="#"
onClick={() => setPage(page)}
title={label}
aria-label={label}
>
{emoji}
</a>
);
return (
<div className="App-container">
<h1>
<span
style={{
color: difficulty > 0 ? "#e66" : "inherit",
fontStyle: difficulty > 1 ? "italic" : "inherit",
}}
>
hell
</span>
o wordl
</h1>
<div className="top-right">
{page !== "game" ? (
link("❌", "Close", "game")
) : (
<>
{link("❓", "About", "about")}
{link("⚙️", "Settings", "settings")}
</>
)}
</div>
<div
style={{
position: "absolute",
left: 5,
top: 5,
visibility: page === "game" ? "visible" : "hidden",
}}
>
<a
href="#"
onClick={() =>
(document.location = seed
? "?"
: "?seed=" +
new Date().toISOString().replace(/-/g, "").slice(0, 8))
}
>
{seed ? "Random" : "Today's"}
</a>
</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="difficulty-setting"
type="range"
min="0"
max="2"
value={difficulty}
onChange={(e) => setDifficulty(+e.target.value)}
/>
<div>
<label htmlFor="difficulty-setting">Difficulty:</label>
&nbsp;
<strong>{["Normal", "Hard", "Ultra Hard"][difficulty]}</strong>
<div
style={{
fontSize: 14,
height: 40,
marginLeft: 8,
marginTop: 8,
}}
>
{
[
`Guesses must be valid dictionary words.`,
`Wordle's "Hard Mode". Green letters must stay fixed, and yellow letters must be reused.`,
`An even stricter Hard Mode. Yellow letters must move away from where they were clued, and gray clues must be obeyed.`,
][difficulty]
}
</div>
</div>
</div>
</div>
)}
<Game
maxGuesses={maxGuesses}
hidden={page !== "game"}
difficulty={difficulty}
/>
</div>
);
}
export default App;