From b5968f02527b02c30280cd023d7dad3b0ce15cf2 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 20 Jul 2026 11:10:42 +0200 Subject: [PATCH 1/2] refactor(test restart): move UI code to test-ui.ts (@miodec) (#8255) --- .../ts/commandline/commandline-metadata.ts | 30 ++-- frontend/src/ts/commandline/lists/funbox.ts | 4 +- .../ts/commandline/lists/load-challenge.ts | 2 +- .../src/ts/commandline/lists/result-screen.ts | 25 ++-- .../ts/components/modals/QuoteSearchModal.tsx | 2 +- .../src/ts/controllers/preset-controller.ts | 2 +- .../src/ts/controllers/pw-ad-controller.ts | 4 +- frontend/src/ts/controllers/url-handler.tsx | 4 +- frontend/src/ts/event-handlers/global.ts | 4 +- frontend/src/ts/modals/practise-words.ts | 2 +- frontend/src/ts/pages/test.ts | 4 +- frontend/src/ts/test/pace-caret.ts | 3 +- frontend/src/ts/test/result.ts | 7 +- frontend/src/ts/test/test-logic.ts | 129 +++++++----------- frontend/src/ts/test/test-screenshot.ts | 6 +- frontend/src/ts/test/test-state.ts | 5 - frontend/src/ts/test/test-ui.ts | 50 ++++++- frontend/src/ts/ui.ts | 5 +- 18 files changed, 146 insertions(+), 142 deletions(-) diff --git a/frontend/src/ts/commandline/commandline-metadata.ts b/frontend/src/ts/commandline/commandline-metadata.ts index 5c8af2d95cfa..999614654a29 100644 --- a/frontend/src/ts/commandline/commandline-metadata.ts +++ b/frontend/src/ts/commandline/commandline-metadata.ts @@ -99,7 +99,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: "fromSchema", afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -107,7 +107,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: "fromSchema", afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -116,14 +116,14 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: [10, 25, 50, 100], afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, input: { inputValueConvert: Number, afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -131,13 +131,13 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: [15, 30, 60, 120], afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, input: { inputValueConvert: Number, afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -145,7 +145,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: "fromSchema", afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -176,7 +176,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { return map[value[0] as number] as string; }, afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -262,7 +262,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: "fromSchema", afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -285,7 +285,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { val.trim().split(" ") as ConfigSchemas.CustomPolyglot, afterExec: () => { if (getActivePage() === "test") { - TestLogic.restart(); + void TestLogic.restart(); } }, }, @@ -339,7 +339,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { lazyMode: { subgroup: { options: "fromSchema", - afterExec: () => TestLogic.restart(), + afterExec: () => void TestLogic.restart(), }, }, layout: { @@ -347,7 +347,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { options: "fromSchema", display: (layout) => layout === "default" ? "off" : layout.replace(/_/g, " "), - afterExec: () => TestLogic.restart(), + afterExec: () => void TestLogic.restart(), }, }, codeUnindentOnBackspace: { @@ -484,7 +484,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { subgroup: { options: ["off", "pb", "tagPb", "last", "average", "daily"], afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, input: { @@ -496,7 +496,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { return newVal; }, afterExec: () => { - TestLogic.restart(); + void TestLogic.restart(); }, }, }, @@ -666,7 +666,7 @@ export const commandlineConfigMetadata: CommandlineConfigMetadataObject = { alias: (val) => (val === "overrideSync" ? "default" : ""), display: (layout) => layout === "overrideSync" ? "emulator sync" : layout.replace(/_/g, " "), - afterExec: () => TestLogic.restart(), + afterExec: () => void TestLogic.restart(), }, }, keymapKeys: { diff --git a/frontend/src/ts/commandline/lists/funbox.ts b/frontend/src/ts/commandline/lists/funbox.ts index 5c6e6c5e4021..5b0ba584fd7a 100644 --- a/frontend/src/ts/commandline/lists/funbox.ts +++ b/frontend/src/ts/commandline/lists/funbox.ts @@ -13,7 +13,7 @@ const list: Command[] = [ sticky: true, exec: (): void => { if (Funbox.setFunbox([])) { - TestLogic.restart(); + void TestLogic.restart(); } }, }, @@ -34,7 +34,7 @@ for (const funbox of getAllFunboxes()) { configValueMode: "include", exec: (): void => { Funbox.toggleFunbox(funbox.name); - TestLogic.restart(); + void TestLogic.restart(); }, }); } diff --git a/frontend/src/ts/commandline/lists/load-challenge.ts b/frontend/src/ts/commandline/lists/load-challenge.ts index a7dfbbfc1524..ccea9340058b 100644 --- a/frontend/src/ts/commandline/lists/load-challenge.ts +++ b/frontend/src/ts/commandline/lists/load-challenge.ts @@ -15,7 +15,7 @@ const subgroup: CommandsSubgroup = { exec: async (): Promise => { await navigate("/"); await ChallengeController.setup(challenge.name); - TestLogic.restart({ + void TestLogic.restart({ nosave: true, }); }, diff --git a/frontend/src/ts/commandline/lists/result-screen.ts b/frontend/src/ts/commandline/lists/result-screen.ts index d629b69ffaf0..ae60d8f2bd3c 100644 --- a/frontend/src/ts/commandline/lists/result-screen.ts +++ b/frontend/src/ts/commandline/lists/result-screen.ts @@ -12,6 +12,7 @@ import * as PractiseWords from "../../test/practise-words"; import { Command, CommandsSubgroup } from "../types"; import * as TestScreenshot from "../../test/test-screenshot"; import { getInputHistory } from "../../test/events/stats"; +import { getResultVisible } from "../../states/test"; const practiceSubgroup: CommandsSubgroup = { title: "Practice words...", @@ -21,7 +22,7 @@ const practiceSubgroup: CommandsSubgroup = { display: "missed", exec: (): void => { PractiseWords.init("words", false); - TestLogic.restart({ + void TestLogic.restart({ practiseMissed: true, }); }, @@ -31,7 +32,7 @@ const practiceSubgroup: CommandsSubgroup = { display: "slow", exec: (): void => { PractiseWords.init("off", true); - TestLogic.restart({ + void TestLogic.restart({ practiseMissed: true, }); }, @@ -41,7 +42,7 @@ const practiceSubgroup: CommandsSubgroup = { display: "both", exec: (): void => { PractiseWords.init("words", true); - TestLogic.restart({ + void TestLogic.restart({ practiseMissed: true, }); }, @@ -67,10 +68,10 @@ const commands: Command[] = [ alias: "restart start begin type test typing", icon: "fa-chevron-right", available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, exec: (): void => { - TestLogic.restart(); + void TestLogic.restart(); }, }, { @@ -78,12 +79,12 @@ const commands: Command[] = [ display: "Repeat test", icon: "fa-sync-alt", exec: (): void => { - TestLogic.restart({ + void TestLogic.restart({ withSameWordset: true, }); }, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, { @@ -92,7 +93,7 @@ const commands: Command[] = [ icon: "fa-exclamation-triangle", subgroup: practiceSubgroup, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, { @@ -103,7 +104,7 @@ const commands: Command[] = [ void TestUI.toggleResultWords(); }, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, { @@ -117,7 +118,7 @@ const commands: Command[] = [ }, 500); }, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, { @@ -131,7 +132,7 @@ const commands: Command[] = [ }, 500); }, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, { @@ -164,7 +165,7 @@ const commands: Command[] = [ ); }, available: (): boolean => { - return TestState.resultVisible; + return getResultVisible(); }, }, ]; diff --git a/frontend/src/ts/components/modals/QuoteSearchModal.tsx b/frontend/src/ts/components/modals/QuoteSearchModal.tsx index 989d03936377..97812c5c5363 100644 --- a/frontend/src/ts/components/modals/QuoteSearchModal.tsx +++ b/frontend/src/ts/components/modals/QuoteSearchModal.tsx @@ -400,7 +400,7 @@ export function QuoteSearchModal(): JSXElement { } TestState.setSelectedQuoteId(quoteId); setConfig("quoteLength", [-2]); - TestLogic.restart(); + void TestLogic.restart(); hideModalAndClearChain("QuoteSearch"); }; diff --git a/frontend/src/ts/controllers/preset-controller.ts b/frontend/src/ts/controllers/preset-controller.ts index 4c6ec1481a09..4ac26a93cf80 100644 --- a/frontend/src/ts/controllers/preset-controller.ts +++ b/frontend/src/ts/controllers/preset-controller.ts @@ -41,7 +41,7 @@ export async function apply(_id: string): Promise { saveActiveToLocalStorage(); } } - TestLogic.restart(); + void TestLogic.restart(); showSuccessNotification("Preset applied", { durationMs: 2000 }); saveFullConfigToLocalStorage(); } diff --git a/frontend/src/ts/controllers/pw-ad-controller.ts b/frontend/src/ts/controllers/pw-ad-controller.ts index 5b17c9841466..cfef4cbf506b 100644 --- a/frontend/src/ts/controllers/pw-ad-controller.ts +++ b/frontend/src/ts/controllers/pw-ad-controller.ts @@ -5,7 +5,7 @@ import { Config } from "../config/store"; import { getActivePage } from "../states/core"; -import * as TestState from "../test/test-state"; +import { getResultVisible } from "../states/test"; // Step 1: Create the Ramp Object, NOTE: selector id needed for tagged units only const resultUnits = [ @@ -207,7 +207,7 @@ function getUnits(): unknown { export async function reinstate(): boolean { if (!rampReady) return; - if (getActivePage() === "test" && !TestState.resultVisible) { + if (getActivePage() === "test" && !getResultVisible()) { ramp.destroyUnits("all"); return; } diff --git a/frontend/src/ts/controllers/url-handler.tsx b/frontend/src/ts/controllers/url-handler.tsx index 472bb46a5ab8..247c11137030 100644 --- a/frontend/src/ts/controllers/url-handler.tsx +++ b/frontend/src/ts/controllers/url-handler.tsx @@ -288,7 +288,7 @@ export function loadTestSettingsFromUrl(getOverride?: string): void { applied["funbox"] = val.join(", "); } - restartTest({ + void restartTest({ nosave: true, }); @@ -331,7 +331,7 @@ export async function loadChallengeFromUrl( ChallengeController.setup(challengeName) .then((result) => { if (result) { - restartTest({ + void restartTest({ nosave: true, }); } diff --git a/frontend/src/ts/event-handlers/global.ts b/frontend/src/ts/event-handlers/global.ts index 907e6be695b0..8920ab54dac9 100644 --- a/frontend/src/ts/event-handlers/global.ts +++ b/frontend/src/ts/event-handlers/global.ts @@ -6,7 +6,7 @@ import { getActivePage } from "../states/core"; import { ModifierKeys } from "../constants/modifier-keys"; import { focusWords } from "../test/test-ui"; import { isInputElementFocused } from "../input/input-element"; -import * as TestState from "../test/test-state"; +import { getResultVisible } from "../states/test"; import { isDevEnvironment } from "../utils/env"; document.addEventListener("keydown", (e) => { @@ -24,7 +24,7 @@ document.addEventListener("keydown", (e) => { } const pageTestActive: boolean = getActivePage() === "test"; - if (pageTestActive && !TestState.resultVisible && !isInputElementFocused()) { + if (pageTestActive && !getResultVisible() && !isInputElementFocused()) { const popupVisible: boolean = Misc.isAnyPopupVisible(); // this is nested because isAnyPopupVisible is a bit expensive // and we don't want to call it during the test diff --git a/frontend/src/ts/modals/practise-words.ts b/frontend/src/ts/modals/practise-words.ts index bf05f050fa96..23e3eb417c0d 100644 --- a/frontend/src/ts/modals/practise-words.ts +++ b/frontend/src/ts/modals/practise-words.ts @@ -71,7 +71,7 @@ function hide(clearChain = false): void { function apply(): void { PractiseWords.init(state.missed, state.slow); hide(true); - TestLogic.restart({ + void TestLogic.restart({ practiseMissed: true, }); } diff --git a/frontend/src/ts/pages/test.ts b/frontend/src/ts/pages/test.ts index bd0f0e67e724..337b6e2cf178 100644 --- a/frontend/src/ts/pages/test.ts +++ b/frontend/src/ts/pages/test.ts @@ -14,7 +14,7 @@ export const page = new Page({ blurInputElement(); }, afterHide: async (): Promise => { - TestLogic.restart({ + void TestLogic.restart({ noAnim: true, }); void Funbox.clear(); @@ -23,7 +23,7 @@ export const page = new Page({ beforeShow: async (): Promise => { updateFooterAndVerticalAds(false); resetIncompleteTests(); - TestLogic.restart({ + void TestLogic.restart({ noAnim: true, }); }, diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts index da49b8e4578b..4a02d10701c2 100644 --- a/frontend/src/ts/test/pace-caret.ts +++ b/frontend/src/ts/test/pace-caret.ts @@ -15,6 +15,7 @@ import { import { getActiveWordIndex, getCurrentQuote, + getResultVisible, isPaceRepeat, isTestActive, setPaceCaretWpm, @@ -122,7 +123,7 @@ export async function init(): Promise { export async function update(expectedStepEnd: number): Promise { const currentSettings = settings; - if (currentSettings === null || !isTestActive() || TestState.resultVisible) { + if (currentSettings === null || !isTestActive() || getResultVisible()) { return; } diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 138afb25c73b..58fa45356670 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -59,6 +59,7 @@ import { qs, qsa } from "../utils/dom"; import { getTheme } from "../states/theme"; import { getCurrentQuote, + getResultVisible, isTestInvalid, setResultCalculating, } from "../states/test"; @@ -81,7 +82,7 @@ let quoteId = ""; export function toggleSmoothedBurst(): void { useSmoothedBurst = !useSmoothedBurst; showSuccessNotification(useSmoothedBurst ? "on" : "off"); - if (TestState.resultVisible) { + if (getResultVisible()) { void updateChartData().then(() => { ChartController.result.update("resize"); }); @@ -91,7 +92,7 @@ export function toggleSmoothedBurst(): void { export function toggleUserFakeChartData(): void { useFakeChartData = !useFakeChartData; showSuccessNotification(useFakeChartData ? "on" : "off"); - if (TestState.resultVisible) { + if (getResultVisible()) { void updateChartData().then(() => { ChartController.result.update("resize"); }); @@ -1386,7 +1387,7 @@ qs(".pageTest #favoriteQuoteButton")?.on("click", async () => { configEvent.subscribe(async ({ key }) => { if ( ["typingSpeedUnit", "startGraphsAtZero"].includes(key) && - TestState.resultVisible + getResultVisible() ) { resultAnnotation = []; diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index f15cc03c6d87..ce7605357647 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -13,7 +13,6 @@ import * as CustomText from "./custom-text"; import * as PractiseWords from "./practise-words"; import * as Funbox from "./funbox/funbox"; import * as PaceCaret from "./pace-caret"; -import * as Caret from "./caret"; import * as TestTimer from "./test-timer"; import * as DB from "../db"; import * as Replay from "./replay-ui"; @@ -53,6 +52,7 @@ import { setWordsHaveNewline, setWordsHaveNumbers, setWordsHaveTab, + getResultVisible, } from "../states/test"; import { restartTestEvent } from "../events/test"; import * as TestWords from "./test-words"; @@ -64,7 +64,6 @@ import { timerEvent } from "../events/timer"; import objectHash from "object-hash"; import * as AnalyticsController from "../controllers/analytics-controller"; import { getAuthenticatedUser } from "../firebase"; -import * as ConnectionState from "../legacy-states/connection"; import { highlight } from "../events/keymap"; import * as LazyModeState from "../legacy-states/remember-lazy-mode"; import Format from "../singletons/format"; @@ -169,7 +168,7 @@ type RestartOptions = { isQuickRestart?: boolean; }; -export function restart(options = {} as RestartOptions): void { +export async function restart(options = {} as RestartOptions): Promise { const defaultOptions = { withSameWordset: false, practiseMissed: false, @@ -179,9 +178,8 @@ export function restart(options = {} as RestartOptions): void { }; options = { ...defaultOptions, ...options }; - Strings.clearWordDirectionCache(); - const animationTime = options.noAnim ? 0 : Misc.applyReducedMotion(125); + // guards const noQuit = isFunboxActive("no_quit"); if (isTestActive() && noQuit) { @@ -230,6 +228,8 @@ export function restart(options = {} as RestartOptions): void { } } + // close out the abandoned test + if (isRepeated()) { options.withSameWordset = true; } @@ -284,90 +284,62 @@ export function restart(options = {} as RestartOptions): void { PractiseWords.resetBefore(); } + // reset state + resetTestEvents(); TestTimer.clear(); setIsTestInvalid(false); resetModifierState(); - Caret.hide(); setTestActive(false); Replay.pauseReplay(); setBailedOut(false); - Caret.resetPosition(); PaceCaret.reset(); TestState.setKoreanStatus(false); clearQuoteStats(); CompositionState.setComposing(false); CompositionState.setData(""); + Strings.clearWordDirectionCache(); + testReinitCount = 0; + failReason = ""; - if (!ConnectionState.get()) { - ConnectionState.showOfflineBanner(); - } + const repeatWithPace = + (Config.repeatedPace && options.withSameWordset) ?? false; + setIsRepeated(options.withSameWordset ?? false); + setIsPaceRepeat(repeatWithPace); - // TestUI.beforeTestRestart(); + // restart - let source: "testPage" | "resultPage"; - let el: HTMLElement; - if (TestState.resultVisible) { - //results are being displayed - el = document.querySelector("#result") as HTMLElement; - source = "resultPage"; - } else { - //words are being displayed - el = document.querySelector("#typingTest") as HTMLElement; - source = "testPage"; - } + const source: "testPage" | "resultPage" = getResultVisible() + ? "resultPage" + : "testPage"; + const noAnim = options.noAnim ?? false; - TestState.setResultVisible(false); TestState.setTestRestarting(true); - animate(el, { - opacity: 0, - duration: animationTime, - onComplete: async () => { - setResultVisible(false); - setInputElementValue(""); + await TestUI.fadeOutForRestart(source, noAnim); - await Funbox.rememberSettings(); + setResultVisible(false); + setInputElementValue(""); - testReinitCount = 0; - failReason = ""; + await Funbox.rememberSettings(); - let repeatWithPace = false; - if (Config.repeatedPace && options.withSameWordset) { - repeatWithPace = true; - } + const initResult = await init(); - setIsRepeated(options.withSameWordset ?? false); - setIsPaceRepeat(repeatWithPace); - TestInitFailed.hide(); - const initResult = await init(); + if (!initResult) { + TestState.setTestRestarting(false); + return; + } - if (!initResult) { - TestState.setTestRestarting(false); - return; - } + await PaceCaret.init(); - await PaceCaret.init(); + for (const fb of getActiveFunboxesWithFunction("restart")) { + fb.functions.restart(); + } - for (const fb of getActiveFunboxesWithFunction("restart")) { - fb.functions.restart(); - } + TestUI.onTestRestart(source); - TestUI.onTestRestart(source); - - const typingTestEl = document.querySelector("#typingTest") as HTMLElement; - animate(typingTestEl, { - opacity: [0, 1], - onBegin: () => { - typingTestEl.classList.remove("hidden"); - }, - duration: animationTime, - onComplete: () => { - TestState.setTestRestarting(false); - }, - }); - }, - }); + await TestUI.fadeInAfterRestart(noAnim); + TestState.setTestRestarting(false); } let lastInitError: Error | null = null; @@ -629,7 +601,7 @@ export async function addWord(): Promise { "Error while getting section. Please try again later", ); toggleFunbox(sectionFunbox.name); - restart(); + void restart(); return; } @@ -859,7 +831,6 @@ export async function finish(difficultyFailed = false): Promise { forceReleaseAllKeys(); setResultVisible(true); - TestState.setResultVisible(true); setTestActive(false); cleanupData(); @@ -1177,7 +1148,7 @@ async function saveResult( if (data.xp !== undefined) { localDataToSave.xp = data.xp; - if (TestState.resultVisible) { + if (getResultVisible()) { localDataToSave.xpBreakdown = data.xpBreakdown; } } @@ -1281,7 +1252,7 @@ const debouncedZipfCheck = debounce(250, async () => { }); qs(".pageTest")?.onChild("click", "#testInitFailed button.restart", () => { - restart(); + void restart(); }); qs(".pageTest")?.onChild("click", "#restartTestButton", () => { @@ -1291,11 +1262,11 @@ qs(".pageTest")?.onChild("click", "#restartTestButton", () => { Config.repeatQuotes === "typing" && Config.mode === "quote" ) { - restart({ + void restart({ withSameWordset: true, }); } else { - restart(); + void restart(); } }); @@ -1306,7 +1277,7 @@ qs(".pageTest")?.onChild( ); qs(".pageTest")?.onChild("click", "#nextTestButton", () => { - restart(); + void restart(); }); qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => { @@ -1314,7 +1285,7 @@ qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => { showNoticeNotification("Repeat test disabled in zen mode"); return; } - restart({ + void restart({ withSameWordset: true, }); }); @@ -1323,10 +1294,10 @@ qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => { window.addEventListener("focus", () => { if ( !isTestActive() && - !TestState.resultVisible && + !getResultVisible() && (Config.mode === "time" || Config.mode === "words") ) { - restart({ + void restart({ noAnim: true, }); } @@ -1337,16 +1308,16 @@ document.addEventListener("visibilitychange", () => { if (document.visibilityState !== "visible") return; if ( !isTestActive() && - !TestState.resultVisible && + !getResultVisible() && (Config.mode === "time" || Config.mode === "words") ) { - restart({ + void restart({ noAnim: true, }); } }); -restartTestEvent.subscribe((event) => restart(event)); +restartTestEvent.subscribe((event) => void restart(event)); // =============================== @@ -1362,11 +1333,11 @@ configEvent.subscribe(({ key, newValue, nosave }) => { nosave: true, }); } - restart(); + void restart(); } - if (key === "difficulty" && !nosave) restart(); + if (key === "difficulty" && !nosave) void restart(); if (key === "customLayoutfluid" && Config.funbox.includes("layoutfluid")) { - restart(); + void restart(); } if (key === "keymapMode" && newValue === "next" && Config.mode !== "zen") { diff --git a/frontend/src/ts/test/test-screenshot.ts b/frontend/src/ts/test/test-screenshot.ts index fe457cea5e4e..e8623ee0483e 100644 --- a/frontend/src/ts/test/test-screenshot.ts +++ b/frontend/src/ts/test/test-screenshot.ts @@ -15,10 +15,10 @@ import { showSuccessNotification, } from "../states/notifications"; import { convertRemToPixels } from "../utils/numbers"; -import * as TestState from "./test-state"; import { qs, qsa } from "../utils/dom"; import { getTheme } from "../states/theme"; import { download as downloadFile } from "../utils/misc"; +import { getResultVisible } from "../states/test"; let revealReplay = false; @@ -343,7 +343,7 @@ qs(".pageTest")?.onChild("click", "#saveScreenshotButton", (event) => { }); document.addEventListener("keydown", (event) => { - if (!(TestState.resultVisible && getActivePage() === "test")) return; + if (!(getResultVisible() && getActivePage() === "test")) return; if (event.key !== "Shift") return; qs("#result #saveScreenshotButton i") ?.removeClass(["far", "fa-image"]) @@ -351,7 +351,7 @@ document.addEventListener("keydown", (event) => { }); document.addEventListener("keyup", (event) => { - if (!(TestState.resultVisible && getActivePage() === "test")) return; + if (!(getResultVisible() && getActivePage() === "test")) return; if (event.key !== "Shift") return; qs("#result #saveScreenshotButton i") ?.removeClass(["fas", "fa-download"]) diff --git a/frontend/src/ts/test/test-state.ts b/frontend/src/ts/test/test-state.ts index 5af9e48d7490..e3a77facfd07 100644 --- a/frontend/src/ts/test/test-state.ts +++ b/frontend/src/ts/test/test-state.ts @@ -6,7 +6,6 @@ export let selectedQuoteId = export let isLanguageRightToLeft = false; export let isDirectionReversed = false; export let testRestarting = false; -export let resultVisible = false; export let koreanStatus = false; export let lastEventLog: EventLog | null = null; @@ -47,7 +46,3 @@ export function setTestRestarting(val: boolean): void { restartingResolve(); } } - -export function setResultVisible(val: boolean): void { - resultVisible = val; -} diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 274edeb41915..d231b9ac2bae 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -64,6 +64,7 @@ import { wordsHaveNewline, setTestFocusState, showOutOfFocusWarning, + getResultVisible, } from "../states/test"; import { createEffect } from "solid-js"; import { @@ -72,6 +73,8 @@ import { getMissedWords, getWordBurstHistory, } from "./events/stats"; +import * as ConnectionState from "../legacy-states/connection"; +import * as TestInitFailed from "../elements/test-init-failed"; export const updateHintsPositionDebounced = Misc.debounceUntilResolved( updateHintsPosition, @@ -318,7 +321,7 @@ async function joinOverlappingHints( async function updateHintsPosition(): Promise { if ( getActivePage() !== "test" || - TestState.resultVisible || + getResultVisible() || (Config.indicateTypos !== "below" && Config.indicateTypos !== "both") ) { return; @@ -614,7 +617,7 @@ export async function centerActiveLine(): Promise { } export function updateWordsWrapperHeight(force = false): void { - if (getActivePage() !== "test" || TestState.resultVisible) return; + if (getActivePage() !== "test" || getResultVisible()) return; if (!force && Config.mode !== "custom") return; const activeWordEl = getActiveWordElement(); if (!activeWordEl) return; @@ -952,7 +955,7 @@ function getNlCharWidth( } export async function scrollTape(noAnimation = false): Promise { - if (getActivePage() !== "test" || TestState.resultVisible) return; + if (getActivePage() !== "test" || getResultVisible()) return; await centeringActiveLine; @@ -1400,7 +1403,7 @@ async function loadWordsHistory(): Promise { wordEl.addEventListener("mouseenter", (e) => { // if (noHover) return; - if (!TestState.resultVisible) return; + if (!getResultVisible()) return; const input = (e.currentTarget as HTMLElement).getAttribute("input") ?? ""; const burst = parseInt( @@ -1439,7 +1442,7 @@ async function loadWordsHistory(): Promise { } export async function toggleResultWords(noAnimation = false): Promise { - if (!TestState.resultVisible) return; + if (!getResultVisible()) return; ResultWordHighlight.updateToggleWordsHistoryTime(); if (resultWordsHistoryEl.isHidden()) { @@ -1818,6 +1821,32 @@ export function onTestStart(): void { }); } +function getRestartAnimationTime(noAnim: boolean): number { + return noAnim ? 0 : Misc.applyReducedMotion(125); +} + +export async function fadeOutForRestart( + source: "testPage" | "resultPage", + noAnim: boolean, +): Promise { + const selector = source === "resultPage" ? "#result" : "#typingTest"; + await qs(selector)?.promiseAnimate({ + opacity: 0, + duration: getRestartAnimationTime(noAnim), + }); +} + +export async function fadeInAfterRestart(noAnim: boolean): Promise { + const typingTestEl = qs("#typingTest"); + await typingTestEl?.promiseAnimate({ + opacity: [0, 1], + onBegin: () => { + typingTestEl.removeClass("hidden"); + }, + duration: getRestartAnimationTime(noAnim), + }); +} + export function onTestRestart(source: "testPage" | "resultPage"): void { qs("#result")?.hide(); qs("#typingTest")?.setStyle({ opacity: "0" }).show(); @@ -1836,6 +1865,13 @@ export function onTestRestart(source: "testPage" | "resultPage"): void { ResultWordHighlight.destroy(); MonkeyPower.reset(); MemoryFunboxTimer.reset(); + Caret.hide(); + Caret.resetPosition(); + TestInitFailed.hide(); + + if (!ConnectionState.get()) { + ConnectionState.showOfflineBanner(); + } if (source === "resultPage") { if (Config.randomTheme !== "off") { @@ -1922,7 +1958,7 @@ addEventListener("resize", () => { qs("#wordsInput")?.on("focus", (e) => { if (!isInputElementFocused()) return; - if (!TestState.resultVisible && Config.showOutOfFocusWarning) { + if (!getResultVisible() && Config.showOutOfFocusWarning) { setTestFocusState("focused"); } Caret.show(true); @@ -1970,7 +2006,7 @@ configEvent.subscribe(({ key, newValue }) => { ) { void updateHintsPositionDebounced(); } - if ((key === "theme" || key === "burstHeatmap") && TestState.resultVisible) { + if ((key === "theme" || key === "burstHeatmap") && getResultVisible()) { void applyBurstHeatmap(); } if (key === "highlightMode") { diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index e2322d56390f..e25d4146248e 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -1,7 +1,6 @@ import { Config } from "./config/store"; import * as Caret from "./test/caret"; import * as CustomText from "./test/custom-text"; -import * as TestState from "./test/test-state"; import { configEvent } from "./events/config"; import { debounce, throttle } from "throttle-debounce"; import * as TestUI from "./test/test-ui"; @@ -19,7 +18,7 @@ import fileStorage from "./utils/file-storage"; import { convertRemToPixels } from "./utils/numbers"; import { getLanguage } from "./utils/json-data"; import { replaceUnderscoresWithSpaces } from "./utils/strings"; -import { isTestActive } from "./states/test"; +import { getResultVisible, isTestActive } from "./states/test"; let isPreviewingFont = false; export function previewFontFamily(font: FontName): void { @@ -116,7 +115,7 @@ window.addEventListener("beforeunload", (event) => { }); const debouncedEvent = debounce(250, () => { - if (getActivePage() === "test" && !TestState.resultVisible) { + if (getActivePage() === "test" && !getResultVisible()) { if (Config.tapeMode !== "off") { void TestUI.scrollTape(); } else { From 8f9241d6c4e9a9e36319603c6c9d4ce467ce439d Mon Sep 17 00:00:00 2001 From: Miodec Date: Mon, 20 Jul 2026 11:24:45 +0200 Subject: [PATCH 2/2] fix: invisible caret --- frontend/src/ts/test/test-ui.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index d231b9ac2bae..7f93e6657028 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -501,7 +501,7 @@ function updateWordWrapperClasses(): void { updateWordsMargin(); updateWordsInputPosition(); void updateHintsPositionDebounced(); - Caret.updatePosition(); + Caret.updatePosition(true); if (!isInputElementFocused()) { setTestFocusState("unfocused"); @@ -1865,7 +1865,6 @@ export function onTestRestart(source: "testPage" | "resultPage"): void { ResultWordHighlight.destroy(); MonkeyPower.reset(); MemoryFunboxTimer.reset(); - Caret.hide(); Caret.resetPosition(); TestInitFailed.hide();