diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index 271105df5..eef25f4bd 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -79,7 +79,7 @@ export function saveFullConfigToLocalStorage(noDbCheck = false): void { } function isConfigChangeBlocked(): boolean { - if (TestState.isActive && config.funbox.includes("no_quit")) { + if (TestState.isActive() && config.funbox.includes("no_quit")) { Notifications.add("No quit funbox is active. Please finish the test.", 0, { important: true, }); @@ -112,7 +112,7 @@ export function setConfig( if ( metadata.changeRequiresRestart && - TestState.isActive && + TestState.isActive() && config.funbox.includes("no_quit") ) { Notifications.add("No quit funbox is active. Please finish the test.", 0, { diff --git a/frontend/src/ts/controllers/ad-controller.ts b/frontend/src/ts/controllers/ad-controller.ts index 31bab3ca7..d9c98545e 100644 --- a/frontend/src/ts/controllers/ad-controller.ts +++ b/frontend/src/ts/controllers/ad-controller.ts @@ -42,7 +42,7 @@ function init(): void { } setInterval(() => { - if (TestState.isActive) { + if (TestState.isActive()) { return; } if (choice === "eg") { diff --git a/frontend/src/ts/controllers/route-controller.ts b/frontend/src/ts/controllers/route-controller.ts index d9d429525..5949cc683 100644 --- a/frontend/src/ts/controllers/route-controller.ts +++ b/frontend/src/ts/controllers/route-controller.ts @@ -176,7 +176,7 @@ export async function navigate( } const noQuit = isFunboxActive("no_quit"); - if (TestState.isActive && noQuit) { + if (TestState.isActive() && noQuit) { Notifications.add("No quit funbox is active. Please finish the test.", 0, { important: true, }); diff --git a/frontend/src/ts/input/handlers/before-delete.ts b/frontend/src/ts/input/handlers/before-delete.ts index 79fb28d77..c42d36517 100644 --- a/frontend/src/ts/input/handlers/before-delete.ts +++ b/frontend/src/ts/input/handlers/before-delete.ts @@ -7,7 +7,7 @@ import * as TestUI from "../../test/test-ui"; import { isAwaitingNextWord } from "../state"; export function onBeforeDelete(event: InputEvent): void { - if (!TestState.isActive) { + if (!TestState.isActive()) { event.preventDefault(); return; } diff --git a/frontend/src/ts/input/handlers/insert-text.ts b/frontend/src/ts/input/handlers/insert-text.ts index 00a0f7258..6ecc39886 100644 --- a/frontend/src/ts/input/handlers/insert-text.ts +++ b/frontend/src/ts/input/handlers/insert-text.ts @@ -113,7 +113,7 @@ export async function onInsertText(options: OnInsertTextParams): Promise { const data = normalizedData ?? options.data; // start if needed - if (!TestState.isActive) { + if (!TestState.isActive()) { TestLogic.startTest(now); } diff --git a/frontend/src/ts/input/listeners/composition.ts b/frontend/src/ts/input/listeners/composition.ts index e3388a557..4904651c1 100644 --- a/frontend/src/ts/input/listeners/composition.ts +++ b/frontend/src/ts/input/listeners/composition.ts @@ -19,7 +19,7 @@ inputEl.addEventListener("compositionstart", (event) => { CompositionState.setComposing(true); CompositionState.setData(""); setLastInsertCompositionTextData(""); - if (!TestState.isActive) { + if (!TestState.isActive()) { TestLogic.startTest(performance.now()); } }); diff --git a/frontend/src/ts/signals/live-states.ts b/frontend/src/ts/signals/live-states.ts index 1ac3bdbbd..7440f8bbd 100644 --- a/frontend/src/ts/signals/live-states.ts +++ b/frontend/src/ts/signals/live-states.ts @@ -2,5 +2,3 @@ import { createSignal } from "solid-js"; export const [getWpm, setWpm] = createSignal("0"); export const [getAcc, setAcc] = createSignal("100%"); export const [getBurst, setBurst] = createSignal("0"); -export const [getFocus, setFocus] = createSignal(false); -export const [getTestRunning, setTestRunning] = createSignal(false); diff --git a/frontend/src/ts/states/connection.ts b/frontend/src/ts/states/connection.ts index efaeebd8f..76bd47cfa 100644 --- a/frontend/src/ts/states/connection.ts +++ b/frontend/src/ts/states/connection.ts @@ -39,7 +39,7 @@ const throttledHandleState = debounce(5000, () => { )?.dispatch("click"); } bannerAlreadyClosed = false; - } else if (!TestState.isActive) { + } else if (!TestState.isActive()) { showOfflineBanner(); } }); diff --git a/frontend/src/ts/test/focus.ts b/frontend/src/ts/test/focus.ts index dd2bad158..4ce820e55 100644 --- a/frontend/src/ts/test/focus.ts +++ b/frontend/src/ts/test/focus.ts @@ -2,10 +2,10 @@ import * as Caret from "./caret"; import * as TimerProgress from "./timer-progress"; import * as PageTransition from "../states/page-transition"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; -import { setFocus } from "../signals/live-states"; +import { createSignal } from "solid-js"; const unfocusPx = 3; -let state = false; +let [state, setState] = createSignal(false); let cacheReady = false; let cache: { @@ -13,6 +13,10 @@ let cache: { cursor?: HTMLElement[]; } = {}; +export function isFocused(): boolean { + return state(); +} + function initializeCache(): void { if (cacheReady) return; @@ -43,8 +47,8 @@ export function set(value: boolean, withCursor = false): void { requestDebouncedAnimationFrame("focus.set", () => { initializeCache(); - if (value && !state) { - state = true; + if (value && !state()) { + setState(true); // batch DOM operations for better performance if (cache.focus) { @@ -60,8 +64,8 @@ export function set(value: boolean, withCursor = false): void { Caret.stopAnimation(); TimerProgress.show(); - } else if (!value && state) { - state = false; + } else if (!value && state()) { + setState(false); if (cache.focus) { for (const el of cache.focus) { @@ -77,14 +81,12 @@ export function set(value: boolean, withCursor = false): void { Caret.startAnimation(); TimerProgress.hide(); } - - setFocus(state); }); } $(document).on("mousemove", function (event) { if (PageTransition.get()) return; - if (!state) return; + if (!state()) return; if ( event.originalEvent && // To avoid mouse/desk vibration from creating a flashy effect, we'll unfocus @ >5px instead of >0px diff --git a/frontend/src/ts/test/live-states.tsx b/frontend/src/ts/test/live-states.tsx index bb7dce51c..c5cbad303 100644 --- a/frontend/src/ts/test/live-states.tsx +++ b/frontend/src/ts/test/live-states.tsx @@ -2,20 +2,16 @@ import { createMemo } from "solid-js"; import { qsr } from "../utils/dom"; import { LiveCounter } from "./live-counter"; import { render } from "solid-js/web"; -import { - getAcc, - getBurst, - getFocus, - getTestRunning, - getWpm, -} from "../signals/live-states"; +import { getAcc, getBurst, getWpm } from "../signals/live-states"; import { getLiveAccStyle, getLiveBurstStyle, getLiveSpeedStyle, } from "../signals/config"; +import { isActive } from "./test-state"; +import { isFocused } from "./focus"; -const isTestRunning = createMemo(() => getTestRunning() && getFocus()); +const isTestRunning = createMemo(() => isActive() && isFocused()); const liveWpmText = createMemo(() => isTestRunning() && getLiveSpeedStyle() === "text" ? getWpm() : "", diff --git a/frontend/src/ts/test/monkey.ts b/frontend/src/ts/test/monkey.ts index b1966c3e4..f05a6f0e2 100644 --- a/frontend/src/ts/test/monkey.ts +++ b/frontend/src/ts/test/monkey.ts @@ -9,7 +9,7 @@ const monkeyEl = document.querySelector("#monkey") as HTMLElement; const monkeyFastEl = document.querySelector("#monkey .fast") as HTMLElement; ConfigEvent.subscribe(({ key }) => { - if (key === "monkey" && TestState.isActive) { + if (key === "monkey" && TestState.isActive()) { if (Config.monkey) { monkeyEl.classList.remove("hidden"); } else { diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts index da0b4d01c..830849589 100644 --- a/frontend/src/ts/test/pace-caret.ts +++ b/frontend/src/ts/test/pace-caret.ts @@ -133,7 +133,7 @@ export async function update(expectedStepEnd: number): Promise { const currentSettings = settings; if ( currentSettings === null || - !TestState.isActive || + !TestState.isActive() || TestState.resultVisible ) { return; diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 90138cefa..a01ef91b5 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -151,7 +151,7 @@ export function restart(options = {} as RestartOptions): void { const animationTime = options.noAnim ? 0 : Misc.applyReducedMotion(125); const noQuit = isFunboxActive("no_quit"); - if (TestState.isActive && noQuit) { + if (TestState.isActive() && noQuit) { Notifications.add("No quit funbox is active. Please finish the test.", 0, { important: true, }); @@ -196,7 +196,7 @@ export function restart(options = {} as RestartOptions): void { } } - if (TestState.isActive) { + if (TestState.isActive()) { if (TestState.isRepeated) { options.withSameWordset = true; } @@ -221,7 +221,7 @@ export function restart(options = {} as RestartOptions): void { TestWords.currentQuote !== null && Config.language.startsWith(TestWords.currentQuote.language) && Config.repeatQuotes === "typing" && - (TestState.isActive || failReason !== "") + (TestState.isActive() || failReason !== "") ) { options.withSameWordset = true; } @@ -854,7 +854,7 @@ function buildCompletedEvent( } export async function finish(difficultyFailed = false): Promise { - if (!TestState.isActive) return; + if (!TestState.isActive()) return; TestUI.setResultCalculating(true); const now = performance.now(); TestTimer.clear(); @@ -1404,7 +1404,7 @@ $(".pageTest").on("click", "#restartTestButton", () => { ManualRestart.set(); if (TestUI.resultCalculating) return; if ( - TestState.isActive && + TestState.isActive() && Config.repeatQuotes === "typing" && Config.mode === "quote" ) { diff --git a/frontend/src/ts/test/test-state.ts b/frontend/src/ts/test/test-state.ts index 1b5079d9d..a47220c75 100644 --- a/frontend/src/ts/test/test-state.ts +++ b/frontend/src/ts/test/test-state.ts @@ -1,9 +1,10 @@ import { Challenge } from "@monkeytype/schemas/challenges"; import { promiseWithResolvers } from "../utils/misc"; +import { createSignal } from "solid-js"; export let isRepeated = false; export let isPaceRepeat = false; -export let isActive = false; +export let [isActive, setActive] = createSignal(false); export let activeChallenge: null | Challenge = null; export let savingEnabled = true; export let bailedOut = false; @@ -23,10 +24,6 @@ export function setPaceRepeat(tf: boolean): void { isPaceRepeat = tf; } -export function setActive(tf: boolean): void { - isActive = tf; -} - export function setActiveChallenge(val: null | Challenge): void { activeChallenge = val; } diff --git a/frontend/src/ts/test/test-stats.ts b/frontend/src/ts/test/test-stats.ts index 2644e4ee6..9d27941de 100644 --- a/frontend/src/ts/test/test-stats.ts +++ b/frontend/src/ts/test/test-stats.ts @@ -160,7 +160,7 @@ export function calculateWpmAndRaw( raw: number; } { const testSeconds = calculateTestSeconds( - TestState.isActive ? performance.now() : end, + TestState.isActive() ? performance.now() : end, ); const chars = countChars(final); const wpm = Numbers.roundTo2( @@ -256,7 +256,7 @@ function getInputWords(): string[] { let inputWords = [...TestInput.input.getHistory()]; - if (TestState.isActive) { + if (TestState.isActive()) { inputWords.push(TestInput.input.current); } @@ -276,7 +276,7 @@ function getTargetWords(): string[] { : TestWords.words.list), ]; - if (TestState.isActive) { + if (TestState.isActive()) { targetWords.push( Config.mode === "zen" ? TestInput.input.current diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index fa18c2ece..4c67aa4dd 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -308,7 +308,7 @@ async function _startOld(): Promise { const drift = Math.abs(interval - delay); checkIfTimerIsSlow(drift); timer = setTimeout(function () { - if (!TestState.isActive) { + if (!TestState.isActive()) { if (timer !== null) clearTimeout(timer); SlowTimer.clear(); slowTimerCount = 0; diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index a388ff763..ee5d9a2c7 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -55,12 +55,7 @@ import * as ModesNotice from "../elements/modes-notice"; import * as Last10Average from "../elements/last-10-average"; import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer"; import { qsr } from "../utils/dom"; -import { - setAcc, - setBurst, - setTestRunning, - setWpm, -} from "../signals/live-states"; +import { setAcc, setBurst, setWpm } from "../signals/live-states"; export const updateHintsPositionDebounced = Misc.debounceUntilResolved( updateHintsPosition, @@ -88,7 +83,7 @@ export function focusWords(force = false): void { blurInputElement(); } focusInputElement(true); - if (TestState.isActive) { + if (TestState.isActive()) { keepWordsInputInTheCenter(true); } else { const typingTest = document.querySelector("#typingTest"); @@ -1818,7 +1813,6 @@ export function onTestStart(): void { Focus.set(true); Monkey.show(); TimerProgress.show(); - setTestRunning(true); TimerProgress.update(); } @@ -1875,7 +1869,6 @@ export function onTestRestart(source: "testPage" | "resultPage"): void { export function onTestFinish(): void { Caret.hide(); - setTestRunning(false); TimerProgress.hide(); OutOfFocus.hide(); Monkey.hide(); diff --git a/frontend/src/ts/test/timer-progress.ts b/frontend/src/ts/test/timer-progress.ts index a1fe882ca..8b3967d60 100644 --- a/frontend/src/ts/test/timer-progress.ts +++ b/frontend/src/ts/test/timer-progress.ts @@ -20,7 +20,7 @@ const textEl = document.querySelector( const miniEl = document.querySelector("#liveStatsMini .time") as HTMLElement; export function show(): void { - if (!TestState.isActive) return; + if (!TestState.isActive()) return; requestDebouncedAnimationFrame("timer-progress.show", () => { if (Config.mode !== "zen" && Config.timerStyle === "bar") { animate(barOpacityEl, { @@ -255,7 +255,7 @@ export function update(): void { } export function updateStyle(): void { - if (!TestState.isActive) return; + if (!TestState.isActive()) return; hide(); update(); if (Config.timerStyle === "off") return; diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index 568a21cb1..39d2ed126 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -81,7 +81,7 @@ window.addEventListener("beforeunload", (event) => { ) { //ignore } else { - if (TestState.isActive) { + if (TestState.isActive()) { event.preventDefault(); // Included for legacy support, e.g. Chrome/Edge < 119 // oxlint-disable-next-line no-deprecated