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",
|
"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.",
|
"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",
|
"license": "MIT",
|
||||||
"type": "module",
|
"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 }) {
|
function Metre({ label, value }: { label: string; value: string | number }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-border bg-background px-4 py-3 text-center">
|
<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;
|
submittingRef.current = false;
|
||||||
setSubmitting(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) {
|
if (res.success && res.data) {
|
||||||
statsRef.current = res.data;
|
statsRef.current = res.data;
|
||||||
onGameRecordedAction(res.data);
|
onGameRecordedAction(res.data);
|
||||||
|
|
@ -364,14 +379,16 @@ export function OpordGame({
|
||||||
);
|
);
|
||||||
window.setTimeout(() => router.refresh(), 1700);
|
window.setTimeout(() => router.refresh(), 1700);
|
||||||
}
|
}
|
||||||
setLastRun({
|
if (!cancelled) {
|
||||||
correct,
|
setLastRun({
|
||||||
total: preview.total,
|
correct,
|
||||||
score: res.data.score,
|
total: preview.total,
|
||||||
xpEarned: res.data.xpEarned,
|
score: res.data.score,
|
||||||
best: res.data.bestScore,
|
xpEarned: res.data.xpEarned,
|
||||||
});
|
best: res.data.bestScore,
|
||||||
} else {
|
});
|
||||||
|
}
|
||||||
|
} else if (!cancelled) {
|
||||||
setLastRun({
|
setLastRun({
|
||||||
correct,
|
correct,
|
||||||
total: preview.total,
|
total: preview.total,
|
||||||
|
|
@ -381,7 +398,7 @@ export function OpordGame({
|
||||||
error: res.error ?? "Could not record score.",
|
error: res.error ?? "Could not record score.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setPhaseSafe("over");
|
if (!cancelled) setPhaseSafe("over");
|
||||||
}, [difficulty, expiredRef, onGameRecordedAction, router, setPhaseSafe]);
|
}, [difficulty, expiredRef, onGameRecordedAction, router, setPhaseSafe]);
|
||||||
|
|
||||||
const startBriefing = useCallback(() => {
|
const startBriefing = useCallback(() => {
|
||||||
|
|
@ -411,6 +428,36 @@ export function OpordGame({
|
||||||
setPhaseSafe("quiz");
|
setPhaseSafe("quiz");
|
||||||
}, [setPhaseSafe]);
|
}, [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(
|
const answerCurrent = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
if (phaseRef.current !== "quiz") return;
|
if (phaseRef.current !== "quiz") return;
|
||||||
|
|
@ -484,6 +531,7 @@ export function OpordGame({
|
||||||
<Button type="button" size="lg" onClick={startBriefing} disabled={expired}>
|
<Button type="button" size="lg" onClick={startBriefing} disabled={expired}>
|
||||||
Receive briefing
|
Receive briefing
|
||||||
</Button>
|
</Button>
|
||||||
|
<ResetHint />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -606,6 +654,8 @@ export function OpordGame({
|
||||||
Your session is about to end. The app will refresh when it expires.
|
Your session is about to end. The app will refresh when it expires.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{phase !== "ready" && <ResetHint className="text-center" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue