feat(minigames): Esc/R reset keybind with hint text for OPORD Recall
This commit is contained in:
parent
494b60db3b
commit
e52a637f96
1 changed files with 59 additions and 9 deletions
|
|
@ -265,6 +265,18 @@ function OpordBriefingCanvas({
|
|||
);
|
||||
}
|
||||
|
||||
function ResetHint({ className }: { className?: string }) {
|
||||
const keyClass =
|
||||
"rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px] leading-none";
|
||||
return (
|
||||
<p className={cn("text-xs text-muted-foreground", className)}>
|
||||
Press{" "}
|
||||
<kbd className={keyClass}>Esc</kbd> or <kbd className={keyClass}>R</kbd> at any point to
|
||||
reset the exercise.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function Metre({ label, value }: { label: string; value: string | number }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-background px-4 py-3 text-center">
|
||||
|
|
@ -353,6 +365,9 @@ export function OpordGame({
|
|||
submittingRef.current = false;
|
||||
setSubmitting(false);
|
||||
|
||||
// Esc/R can reset the run while the submission is in flight; global stats
|
||||
// stay in sync, but the run-result UI is skipped for a cancelled run.
|
||||
const cancelled = phaseRef.current !== "quiz";
|
||||
if (res.success && res.data) {
|
||||
statsRef.current = res.data;
|
||||
onGameRecordedAction(res.data);
|
||||
|
|
@ -364,14 +379,16 @@ export function OpordGame({
|
|||
);
|
||||
window.setTimeout(() => router.refresh(), 1700);
|
||||
}
|
||||
setLastRun({
|
||||
correct,
|
||||
total: preview.total,
|
||||
score: res.data.score,
|
||||
xpEarned: res.data.xpEarned,
|
||||
best: res.data.bestScore,
|
||||
});
|
||||
} else {
|
||||
if (!cancelled) {
|
||||
setLastRun({
|
||||
correct,
|
||||
total: preview.total,
|
||||
score: res.data.score,
|
||||
xpEarned: res.data.xpEarned,
|
||||
best: res.data.bestScore,
|
||||
});
|
||||
}
|
||||
} else if (!cancelled) {
|
||||
setLastRun({
|
||||
correct,
|
||||
total: preview.total,
|
||||
|
|
@ -381,7 +398,7 @@ export function OpordGame({
|
|||
error: res.error ?? "Could not record score.",
|
||||
});
|
||||
}
|
||||
setPhaseSafe("over");
|
||||
if (!cancelled) setPhaseSafe("over");
|
||||
}, [difficulty, expiredRef, onGameRecordedAction, router, setPhaseSafe]);
|
||||
|
||||
const startBriefing = useCallback(() => {
|
||||
|
|
@ -411,6 +428,36 @@ export function OpordGame({
|
|||
setPhaseSafe("quiz");
|
||||
}, [setPhaseSafe]);
|
||||
|
||||
const resetGame = useCallback(() => {
|
||||
scenarioSeedRef.current = 0;
|
||||
setScenario(null);
|
||||
setQuestions([]);
|
||||
setQuestionIndex(0);
|
||||
setTypedAnswer("");
|
||||
answersRef.current = [];
|
||||
lastAnswerAtRef.current = 0;
|
||||
setLastAnswerWasCorrect(null);
|
||||
setLastRun(null);
|
||||
setQuizRemainingMs(tier.quizSeconds * 1000);
|
||||
setPhaseSafe("ready");
|
||||
}, [setPhaseSafe, tier.quizSeconds]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey) return;
|
||||
const target = event.target;
|
||||
const typing =
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
(target instanceof HTMLElement && target.isContentEditable);
|
||||
const key = event.key.toLowerCase();
|
||||
if (key === "escape" || (key === "r" && !typing)) resetGame();
|
||||
};
|
||||
window.addEventListener("keydown", handler);
|
||||
return () => window.removeEventListener("keydown", handler);
|
||||
}, [resetGame]);
|
||||
|
||||
const answerCurrent = useCallback(
|
||||
(value: string) => {
|
||||
if (phaseRef.current !== "quiz") return;
|
||||
|
|
@ -484,6 +531,7 @@ export function OpordGame({
|
|||
<Button type="button" size="lg" onClick={startBriefing} disabled={expired}>
|
||||
Receive briefing
|
||||
</Button>
|
||||
<ResetHint />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -606,6 +654,8 @@ export function OpordGame({
|
|||
Your session is about to end. The app will refresh when it expires.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{phase !== "ready" && <ResetHint className="text-center" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue