feat: sentence trainer idle timeout

This commit is contained in:
2025-01-16 17:50:52 +01:00
parent 7819f546a6
commit 77e2d2b20e
2 changed files with 30 additions and 107 deletions

View File

@@ -13,21 +13,22 @@
import TrackText from "$lib/charrecorder/TrackText.svelte";
import { browser } from "$app/environment";
function initialThresholds(): [slow: number, fast: number][] {
function viaLocalStorage<T>(key: string, initial: T) {
try {
return JSON.parse(localStorage.getItem("mastery-thresholds") ?? "");
return JSON.parse(localStorage.getItem(key) ?? "");
} catch {
return [
[1500, 1050],
[3000, 2500],
[5000, 3500],
[6000, 5000],
];
return initial;
}
}
let masteryThresholds: [slow: number, fast: number][] =
$state(initialThresholds());
let masteryThresholds: [slow: number, fast: number][] = $state(
viaLocalStorage("mastery-thresholds", [
[1500, 1050],
[3000, 2500],
[5000, 3500],
[6000, 5000],
]),
);
let inputSentence = $derived(
(browser && $page.url.searchParams.get("sentence")) || "Hello World",
@@ -46,6 +47,10 @@
let wpm = $state(0);
let chords: InferredChord[] = $state([]);
let recorder = $state(new ReplayRecorder());
let idle = $state(true);
let idleTime = $state(viaLocalStorage("idle-timeout", 100));
let idleTimeout: ReturnType<typeof setTimeout> | null = null;
let cooldown = $state(false);
@@ -59,6 +64,10 @@
}
});
$effect(() => {
localStorage.setItem("idle-timeout", idleTime.toString());
});
$effect(() => {
localStorage.setItem(
"mastery-thresholds",
@@ -148,7 +157,7 @@
function checkInput() {
if (recorder.player.stepper.challenge.length === 0) return;
const replay = recorder.finish(false);
const elapsed = replay.finish - replay.start!;
const elapsed = replay.finish - replay.start! - idleTime;
if (elapsed < masteryThresholds[level]![0]) {
lastWPM = wpm;
@@ -157,19 +166,22 @@
wordStats.set(currentWord, prevStats.slice(-10));
}
cooldown = true;
setTimeout(() => {
selectNextWord();
cooldown = false;
});
selectNextWord();
}
$effect(() => {
if (!cooldown && text && text === currentWord) checkInput();
if (idle && text && text.trim() === currentWord.trim()) checkInput();
});
function onkey(event: KeyboardEvent) {
if (idleTimeout) {
clearTimeout(idleTimeout);
}
idle = false;
recorder.next(event);
idleTimeout = setTimeout(() => {
idle = true;
}, idleTime);
}
</script>
@@ -273,6 +285,7 @@
</div>
{#if devTools}
<div>Dev Tools</div>
<label>Idle Time <input bind:value={idleTime} /></label>
<table>
<tbody>
{#each masteryThresholds as _, i}

View File

@@ -1,90 +0,0 @@
<script lang="ts">
let {
wordStats,
level,
masteryThresholds,
}: { wordStats: object; level: number; masteryThresholds: number[] } =
$props();
// Function to calculate average time for a word
function calculateAverageTime(times: number[]): number {
if (times.length === 0) return Number.NaN;
const totalTime = times.reduce((a, b) => a + b, 0);
return totalTime / times.length;
}
// Function to calculate WPM for each timing
function calculateWPM(elapsedTime: number, wordLength: number): number {
const minutesElapsed = elapsedTime / 60000;
return Math.floor(wordLength / minutesElapsed);
}
// Function to get the best WPM for a word
function getBestWPM(times: number[], wordLength: number): number {
if (times.length === 0) return Number.NaN;
const wpms = times.map((time) => calculateWPM(time, wordLength));
return Math.max(...wpms);
}
function getLastWPM(times: number[], wordLength: number) {
const lastTime = times[times.length - 1];
return lastTime === undefined
? Number.NaN
: calculateWPM(lastTime, wordLength);
}
</script>
<div class="stats-table">
<h2>Stats for Level {level}</h2>
<table>
<thead>
<tr>
<th>Word</th>
<th>Last WPM</th>
<!-- Individualized -->
<th>Best WPM</th>
<!-- Individualized -->
<th>Attempts</th>
<th>Average Time</th>
</tr>
</thead>
<tbody>
{#each Object.entries(wordStats) as [word, times]}
<tr>
<td>{word}</td>
<td>{getLastWPM(times, word.split(" ").length)}</td>
<!-- Individualized -->
<td>{getBestWPM(times, word.split(" ").length)}</td>
<!-- Individualized -->
<td>{times.length}</td>
<td>{calculateAverageTime(times)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<style>
.stats-table {
margin-top: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th,
td {
padding: 10px;
border: 1px solid #ccc;
text-align: left;
}
th {
background-color: #f4f4f4;
font-weight: bold;
}
</style>