First implementation of 10 finger typing
This commit is contained in:
106
tippen/src/components/modes/TauchgangRun.tsx
Normal file
106
tippen/src/components/modes/TauchgangRun.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
/** 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user