107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
/** Tauchgang - the core drill, and the run that counts for the unlock.
|
|
*
|
|
* A line of chunks with a moving cursor, the finger hint above it and the keyboard
|
|
* below. Everything else in the game is a variation on this; this is the one that is
|
|
* measured. */
|
|
|
|
import { useEffect } from "react";
|
|
|
|
import { currentChar } from "../../lib/engine";
|
|
import type { RunResult } from "../../lib/grading";
|
|
import type { Progress } from "../../lib/progress";
|
|
import { say, spellKey } from "../../lib/speech";
|
|
import { useRun } from "../../hooks/useRun";
|
|
import { HandHint } from "../HandHint";
|
|
import { Keyboard } from "../Keyboard";
|
|
import { Target } from "../Target";
|
|
|
|
interface Props {
|
|
chunks: readonly string[];
|
|
text: string;
|
|
spaceActive: boolean;
|
|
activeKeys: readonly string[];
|
|
progress: Progress;
|
|
paused: boolean;
|
|
onFinished: (result: RunResult) => void;
|
|
}
|
|
|
|
export function TauchgangRun({
|
|
chunks,
|
|
text,
|
|
spaceActive,
|
|
activeKeys,
|
|
progress,
|
|
paused,
|
|
onFinished,
|
|
}: Props) {
|
|
const { state, daneben } = useRun({
|
|
target: text,
|
|
sound: progress.settings.sound,
|
|
paused,
|
|
onFinished,
|
|
});
|
|
|
|
const next = currentChar(state);
|
|
|
|
// Say the first key of a line, and any key she gets stuck on. Not every key: a voice
|
|
// talking over every keystroke is noise, and it would lag behind a good streak.
|
|
useEffect(() => {
|
|
if (state.index === 0 && next) say(spellKey(next), progress.settings.speech);
|
|
}, [state.index, next, progress.settings.speech]);
|
|
|
|
useEffect(() => {
|
|
if (daneben && next) say(spellKey(next), progress.settings.speech);
|
|
}, [daneben, next, progress.settings.speech]);
|
|
|
|
return (
|
|
<div
|
|
className="view-enter"
|
|
style={{
|
|
flex: 1,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: 26,
|
|
padding: "0 32px",
|
|
minHeight: 0,
|
|
}}
|
|
>
|
|
<HandHint nextKey={next} />
|
|
<Target chunks={chunks} spaceActive={spaceActive} index={state.index} daneben={daneben} />
|
|
<Fortschritt done={state.index} total={text.length} />
|
|
<Keyboard
|
|
activeKeys={activeKeys}
|
|
nextKey={next}
|
|
progress={progress}
|
|
mode={progress.settings.keyboardHint}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** How far through the line she is - a bar, not a number, because "18 von 24" is a
|
|
* reading task and a filling bar is not. */
|
|
function Fortschritt({ done, total }: { done: number; total: number }) {
|
|
return (
|
|
<div
|
|
style={{
|
|
width: "min(520px, 80%)",
|
|
height: 8,
|
|
borderRadius: 999,
|
|
background: "oklch(97% 0.01 175 / 0.16)",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
width: `${total === 0 ? 0 : (done / total) * 100}%`,
|
|
height: "100%",
|
|
background: "var(--paper)",
|
|
transition: "width 120ms ease-out",
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|