Compare commits
2 commits
494b60db3b
...
8206ee2303
| Author | SHA1 | Date | |
|---|---|---|---|
| 8206ee2303 | |||
| e52a637f96 |
2 changed files with 60 additions and 10 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "polaris-task-force",
|
||||
"version": "0.2.20",
|
||||
"version": "0.2.21",
|
||||
"description": "Web app for the Arma 3 unit Polaris Task Force. Uses Next.js for client- and server-side rendering, with a Postgres database and some other neat libraries.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -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