diff --git a/frontend/__tests__/test/layout-emulator.spec.ts b/frontend/__tests__/test/layout-emulator.spec.ts index 5dc0b902f5b9..e507ba663f8a 100644 --- a/frontend/__tests__/test/layout-emulator.spec.ts +++ b/frontend/__tests__/test/layout-emulator.spec.ts @@ -13,14 +13,11 @@ describe("LayoutEmulator", () => { updateAltGrState(event); }); - const createEvent = ( - code: string, - type: string, - ): JQuery.KeyboardEventBase => + const createEvent = (code: string, type: string): KeyboardEvent => ({ code, type, - }) as JQuery.KeyboardEventBase; + }) as KeyboardEvent; it("should set isAltGrPressed to true on AltRight keydown", () => { const event = createEvent("AltRight", "keydown"); diff --git a/frontend/src/ts/observables/connection-event.ts b/frontend/src/ts/observables/connection-event.ts index ef15553467f3..b072a59b5a96 100644 --- a/frontend/src/ts/observables/connection-event.ts +++ b/frontend/src/ts/observables/connection-event.ts @@ -7,6 +7,7 @@ export function subscribe(fn: SubscribeFunction): void { } window.addEventListener("load", () => { + console.warn("LOAD"); window.addEventListener("online", () => { dispatch(true); }); diff --git a/frontend/src/ts/states/connection.ts b/frontend/src/ts/states/connection.ts index d5318df92700..b121f7330c95 100644 --- a/frontend/src/ts/states/connection.ts +++ b/frontend/src/ts/states/connection.ts @@ -48,9 +48,18 @@ ConnectionEvent.subscribe((newState) => { throttledHandleState(); }); +window.addEventListener("load", () => { + console.warn("SECOND_LOAD"); +}); + onDOMReady(() => { + console.warn("DOM"); state = navigator.onLine; if (!state) { showOfflineBanner(); } }); + +document.addEventListener("DOMContentLoaded", () => { + console.warn("FIRST_DOM"); +}); diff --git a/frontend/src/ts/test/focus.ts b/frontend/src/ts/test/focus.ts index 7c9ee58cccbf..41cf2de3257b 100644 --- a/frontend/src/ts/test/focus.ts +++ b/frontend/src/ts/test/focus.ts @@ -6,13 +6,14 @@ import * as TimerProgress from "./timer-progress"; import * as PageTransition from "../states/page-transition"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; import { getFocus, setFocus } from "../signals/core"; +import { qsa, ElementsWithUtils } from "../utils/dom"; const unfocusPx = 3; let cacheReady = false; let cache: { - focus?: HTMLElement[]; - cursor?: HTMLElement[]; + focus?: ElementsWithUtils; + cursor?: ElementsWithUtils; } = {}; function initializeCache(): void { @@ -33,8 +34,8 @@ function initializeCache(): void { "#ad-footer-small-wrapper", ].join(","); - cache.cursor = [...document.querySelectorAll(cursorSelector)]; - cache.focus = [...document.querySelectorAll(elementsSelector)]; + cache.cursor = qsa(cursorSelector); + cache.focus = qsa(elementsSelector); cacheReady = true; } @@ -50,14 +51,10 @@ export function set(value: boolean, withCursor = false): void { // batch DOM operations for better performance if (cache.focus) { - for (const el of cache.focus) { - el.classList.add("focus"); - } + cache.focus.addClass("focus"); } if (!withCursor && cache.cursor) { - for (const el of cache.cursor) { - el.style.cursor = "none"; - } + cache.cursor.setStyle({ cursor: "none" }); } Caret.stopAnimation(); @@ -69,14 +66,10 @@ export function set(value: boolean, withCursor = false): void { setFocus(false); if (cache.focus) { - for (const el of cache.focus) { - el.classList.remove("focus"); - } + cache.focus.removeClass("focus"); } if (cache.cursor) { - for (const el of cache.cursor) { - el.style.cursor = ""; - } + cache.cursor.setStyle({ cursor: "" }); } Caret.startAnimation(); diff --git a/frontend/src/ts/test/funbox/funbox-functions.ts b/frontend/src/ts/test/funbox/funbox-functions.ts index 200720604cc0..564881d3644d 100644 --- a/frontend/src/ts/test/funbox/funbox-functions.ts +++ b/frontend/src/ts/test/funbox/funbox-functions.ts @@ -23,6 +23,7 @@ import * as TestState from "../test-state"; import { WordGenError } from "../../utils/word-gen-error"; import { FunboxName, KeymapLayout, Layout } from "@monkeytype/schemas/configs"; import { Language, LanguageObject } from "@monkeytype/schemas/languages"; +import { qs } from "../../utils/dom"; export type FunboxFunctions = { getWord?: (wordset?: Wordset, wordIndex?: number) => string; @@ -61,9 +62,9 @@ async function readAheadHandleKeydown(event: KeyboardEvent): Promise { TestWords.words.get(TestState.activeWordIndex - 1) || Config.freedomMode) ) { - $("#words").addClass("read_ahead_disabled"); + qs("#words")?.addClass("read_ahead_disabled"); } else if (event.key === " ") { - $("#words").removeClass("read_ahead_disabled"); + qs("#words")?.removeClass("read_ahead_disabled"); } } @@ -510,7 +511,7 @@ const list: Partial> = { }, memory: { applyConfig(): void { - $("#wordsWrapper").addClass("hidden"); + qs("#wordsWrapper")?.hide(); setConfig("showAllLines", true, { nosave: true, }); @@ -529,11 +530,11 @@ const list: Partial> = { }, start(): void { MemoryTimer.reset(); - $("#words").addClass("hidden"); + qs("#words")?.hide(); }, restart(): void { MemoryTimer.start(Math.round(Math.pow(TestWords.words.length, 1.2))); - $("#words").removeClass("hidden"); + qs("#words")?.show(); if (Config.keymapMode === "next") { setConfig("keymapMode", "react"); } @@ -672,14 +673,14 @@ const list: Partial> = { return; } } - $("body").append('
'); - $("body").addClass("crtmode"); - $("#globalFunBoxTheme").attr("href", `funbox/crt.css`); + qs("body")?.appendHtml('
'); + qs("body")?.addClass("crtmode"); + qs("#globalFunBoxTheme")?.setAttribute("href", `funbox/crt.css`); }, clearGlobal(): void { - $("#scanline").remove(); - $("body").removeClass("crtmode"); - $("#globalFunBoxTheme").attr("href", ``); + qs("#scanline")?.remove(); + qs("body")?.removeClass("crtmode"); + qs("#globalFunBoxTheme")?.setAttribute("href", ``); }, }, ALL_CAPS: { diff --git a/frontend/src/ts/test/funbox/funbox.ts b/frontend/src/ts/test/funbox/funbox.ts index f3e20af3a687..ce33aa9a2116 100644 --- a/frontend/src/ts/test/funbox/funbox.ts +++ b/frontend/src/ts/test/funbox/funbox.ts @@ -22,6 +22,7 @@ import { } from "./list"; import { checkForcedConfig } from "./funbox-validation"; import { tryCatch } from "@monkeytype/util/trycatch"; +import { qs } from "../../utils/dom"; export function toggleScript(...params: string[]): void { if (Config.funbox.length === 0) return; @@ -66,18 +67,18 @@ export function toggleFunbox(funbox: FunboxName): void { } export async function clear(): Promise { - $("body").attr( + qs("body")?.setAttribute( "class", - $("body") - ?.attr("class") + qs("body") + ?.getAttribute("class") ?.split(/\s+/) ?.filter((it) => !it.startsWith("fb-")) ?.join(" ") ?? "", ); - $(".funBoxTheme").remove(); + qs(".funBoxTheme")?.remove(); - $("#wordsWrapper").removeClass("hidden"); + qs("#wordsWrapper")?.show(); MemoryTimer.reset(); ManualRestart.set(); return true; @@ -115,7 +116,7 @@ export async function activate( await setFunboxBodyClasses(); await applyFunboxCSS(); - $("#wordsWrapper").removeClass("hidden"); + qs("#wordsWrapper")?.show(); const { data: language, error } = await tryCatch( JSONData.getCurrentLanguage(Config.language), @@ -216,7 +217,7 @@ export async function rememberSettings(): Promise { } async function setFunboxBodyClasses(): Promise { - const $body = $("body"); + const $body = qs("body"); const activeFbClasses = getActiveFunboxNames().map( (name) => "fb-" + name.replaceAll("_", "-"), @@ -224,7 +225,7 @@ async function setFunboxBodyClasses(): Promise { const currentClasses = $body - ?.attr("class") + ?.getAttribute("class") ?.split(/\s+/) .filter((it) => !it.startsWith("fb-")) ?? []; @@ -232,7 +233,7 @@ async function setFunboxBodyClasses(): Promise { currentClasses.push("ignore-reduced-motion"); } - $body.attr( + $body?.setAttribute( "class", [...new Set([...currentClasses, ...activeFbClasses]).keys()].join(" "), ); @@ -241,7 +242,7 @@ async function setFunboxBodyClasses(): Promise { } async function applyFunboxCSS(): Promise { - $(".funBoxTheme").remove(); + qs(".funBoxTheme")?.remove(); for (const funbox of getActiveFunboxesWithProperty("hasCssFile")) { const css = document.createElement("link"); css.classList.add("funBoxTheme"); diff --git a/frontend/src/ts/test/funbox/layoutfluid-funbox-timer.ts b/frontend/src/ts/test/funbox/layoutfluid-funbox-timer.ts index f80d95a025a1..aa3fce6c22ea 100644 --- a/frontend/src/ts/test/funbox/layoutfluid-funbox-timer.ts +++ b/frontend/src/ts/test/funbox/layoutfluid-funbox-timer.ts @@ -1,31 +1,29 @@ -import { animate } from "animejs"; import { capitalizeFirstLetter } from "../../utils/strings"; import { applyReducedMotion } from "../../utils/misc"; +import { qs } from "../../utils/dom"; -const timerEl = document.querySelector( - "#typingTest #layoutfluidTimer", -) as HTMLElement; +const timerEl = qs("#typingTest #layoutfluidTimer"); export function show(): void { - animate(timerEl, { + timerEl?.animate({ opacity: 1, duration: applyReducedMotion(125), }); } export function hide(): void { - animate(timerEl, { + timerEl?.animate({ opacity: 0, duration: applyReducedMotion(125), }); } export function instantHide(): void { - timerEl.style.opacity = "0"; + timerEl?.setStyle({ opacity: "0" }); } export function updateTime(sec: number, layout: string): void { - timerEl.textContent = `${capitalizeFirstLetter(layout)} in: ${sec}s`; + timerEl?.setText(`${capitalizeFirstLetter(layout)} in: ${sec}s`); } export function updateWords(words: number, layout: string): void { @@ -34,5 +32,5 @@ export function updateWords(words: number, layout: string): void { if (words === 1) { str = `${layoutName} starting next word`; } - timerEl.textContent = str; + timerEl?.setText(str); } diff --git a/frontend/src/ts/test/funbox/memory-funbox-timer.ts b/frontend/src/ts/test/funbox/memory-funbox-timer.ts index e5dba6f4a65e..9cca426fc038 100644 --- a/frontend/src/ts/test/funbox/memory-funbox-timer.ts +++ b/frontend/src/ts/test/funbox/memory-funbox-timer.ts @@ -1,22 +1,20 @@ -import { animate } from "animejs"; import { applyReducedMotion } from "../../utils/misc"; +import { qs } from "../../utils/dom"; let memoryTimer: number | null = null; let memoryInterval: NodeJS.Timeout | null = null; -const timerEl = document.querySelector( - "#typingTest #memoryTimer", -) as HTMLElement; +const timerEl = qs("#typingTest #memoryTimer"); export function show(): void { - animate(timerEl, { + timerEl?.animate({ opacity: 1, duration: applyReducedMotion(125), }); } export function hide(): void { - animate(timerEl, { + timerEl?.animate({ opacity: 0, duration: applyReducedMotion(125), }); @@ -42,11 +40,11 @@ export function start(time: number): void { memoryTimer === 0 ? hide() : update(memoryTimer); if (memoryTimer <= 0) { reset(); - $("#wordsWrapper").addClass("hidden"); + qs("#wordsWrapper")?.hide(); } }, 1000); } export function update(sec: number): void { - timerEl.textContent = `Timer left to memorise all words: ${sec}s`; + timerEl?.setText(`Timer left to memorise all words: ${sec}s`); } diff --git a/frontend/src/ts/test/layout-emulator.ts b/frontend/src/ts/test/layout-emulator.ts index 998da28717f2..bb8f421f15c6 100644 --- a/frontend/src/ts/test/layout-emulator.ts +++ b/frontend/src/ts/test/layout-emulator.ts @@ -11,10 +11,10 @@ let isAltGrPressed = false; const isPunctuationPattern = /\p{P}/u; export async function getCharFromEvent( - event: JQuery.KeyDownEvent | JQuery.KeyUpEvent | KeyboardEvent, + event: KeyboardEvent, ): Promise { function emulatedLayoutGetVariant( - event: JQuery.KeyDownEvent | JQuery.KeyUpEvent | KeyboardEvent, + event: KeyboardEvent, keyVariants: string[], ): string | undefined { let isCapitalized = event.shiftKey; @@ -238,7 +238,7 @@ export async function getCharFromEvent( } } -export function updateAltGrState(event: JQuery.KeyboardEventBase): void { +export function updateAltGrState(event: KeyboardEvent): void { const shouldHandleLeftAlt = event.code === "AltLeft" && navigator.userAgent.includes("Mac"); if (event.code !== "AltRight" && !shouldHandleLeftAlt) return; @@ -250,5 +250,5 @@ export function getIsAltGrPressed(): boolean { return isAltGrPressed; } -$(document).on("keydown", updateAltGrState); -$(document).on("keyup", updateAltGrState); +document.addEventListener("keydown", updateAltGrState); +document.addEventListener("keyup", updateAltGrState); diff --git a/frontend/src/ts/test/live-acc.ts b/frontend/src/ts/test/live-acc.ts index 3158d0e36928..e780c94f5a76 100644 --- a/frontend/src/ts/test/live-acc.ts +++ b/frontend/src/ts/test/live-acc.ts @@ -2,13 +2,11 @@ import Config from "../config"; import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; import { applyReducedMotion } from "../utils/misc"; -import { animate } from "animejs"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; +import { qs } from "../utils/dom"; -const textEl = document.querySelector( - "#liveStatsTextBottom .liveAcc", -) as HTMLElement; -const miniEl = document.querySelector("#liveStatsMini .acc") as HTMLElement; +const textEl = qs("#liveStatsTextBottom .liveAcc"); +const miniEl = qs("#liveStatsMini .acc"); export function update(acc: number): void { requestDebouncedAnimationFrame("live-acc.update", () => { @@ -16,15 +14,15 @@ export function update(acc: number): void { if (Config.blindMode) { number = 100; } - miniEl.innerHTML = number + "%"; - textEl.innerHTML = number + "%"; + miniEl?.setHtml(number + "%"); + textEl?.setHtml(number + "%"); }); } export function reset(): void { requestDebouncedAnimationFrame("live-acc.reset", () => { - miniEl.innerHTML = "100%"; - textEl.innerHTML = "100%"; + miniEl?.setHtml("100%"); + textEl?.setHtml("100%"); }); } @@ -36,14 +34,14 @@ export function show(): void { if (state) return; requestDebouncedAnimationFrame("live-acc.show", () => { if (Config.liveAccStyle === "mini") { - miniEl.classList.remove("hidden"); - animate(miniEl, { + miniEl?.show(); + miniEl?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); } else { - textEl.classList.remove("hidden"); - animate(textEl, { + textEl?.show(); + textEl?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); @@ -55,18 +53,18 @@ export function show(): void { export function hide(): void { if (!state) return; requestDebouncedAnimationFrame("live-acc.hide", () => { - animate(textEl, { + textEl?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - textEl.classList.add("hidden"); + textEl?.hide(); }, }); - animate(miniEl, { + miniEl?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - miniEl.classList.add("hidden"); + miniEl?.hide(); }, }); state = false; @@ -76,10 +74,10 @@ export function hide(): void { export function instantHide(): void { if (!state) return; - textEl.classList.add("hidden"); - textEl.style.opacity = "0"; - miniEl.classList.add("hidden"); - miniEl.style.opacity = "0"; + textEl?.hide(); + textEl?.setStyle({ opacity: "0" }); + miniEl?.hide(); + miniEl?.setStyle({ opacity: "0" }); state = false; } diff --git a/frontend/src/ts/test/live-burst.ts b/frontend/src/ts/test/live-burst.ts index b37537d5de30..be9d9190d04e 100644 --- a/frontend/src/ts/test/live-burst.ts +++ b/frontend/src/ts/test/live-burst.ts @@ -3,26 +3,24 @@ import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; import Format from "../utils/format"; import { applyReducedMotion } from "../utils/misc"; -import { animate } from "animejs"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; +import { qs } from "../utils/dom"; -const textEl = document.querySelector( - "#liveStatsTextBottom .liveBurst", -) as HTMLElement; -const miniEl = document.querySelector("#liveStatsMini .burst") as HTMLElement; +const textEl = qs("#liveStatsTextBottom .liveBurst"); +const miniEl = qs("#liveStatsMini .burst"); export function reset(): void { requestDebouncedAnimationFrame("live-burst.reset", () => { - textEl.innerHTML = "0"; - miniEl.innerHTML = "0"; + textEl?.setHtml("0"); + miniEl?.setHtml("0"); }); } export async function update(burst: number): Promise { requestDebouncedAnimationFrame("live-burst.update", () => { const burstText = Format.typingSpeed(burst, { showDecimalPlaces: false }); - miniEl.innerHTML = burstText; - textEl.innerHTML = burstText; + miniEl?.setHtml(burstText); + textEl?.setHtml(burstText); }); } @@ -34,14 +32,14 @@ export function show(): void { if (state) return; requestDebouncedAnimationFrame("live-burst.show", () => { if (Config.liveBurstStyle === "mini") { - miniEl.classList.remove("hidden"); - animate(miniEl, { + miniEl?.show(); + miniEl?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); } else { - textEl.classList.remove("hidden"); - animate(textEl, { + textEl?.show(); + textEl?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); @@ -53,18 +51,18 @@ export function show(): void { export function hide(): void { if (!state) return; requestDebouncedAnimationFrame("live-burst.hide", () => { - animate(textEl, { + textEl?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - textEl.classList.add("hidden"); + textEl?.hide(); }, }); - animate(miniEl, { + miniEl?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - miniEl.classList.add("hidden"); + miniEl?.hide(); }, }); state = false; @@ -74,10 +72,10 @@ export function hide(): void { export function instantHide(): void { if (!state) return; - textEl.classList.add("hidden"); - textEl.style.opacity = "0"; - miniEl.classList.add("hidden"); - miniEl.style.opacity = "0"; + textEl?.hide(); + textEl?.setStyle({ opacity: "0" }); + miniEl?.hide(); + miniEl?.setStyle({ opacity: "0" }); state = false; } diff --git a/frontend/src/ts/test/live-speed.ts b/frontend/src/ts/test/live-speed.ts index cef8273e4c04..a5a929bd6034 100644 --- a/frontend/src/ts/test/live-speed.ts +++ b/frontend/src/ts/test/live-speed.ts @@ -4,19 +4,15 @@ import * as ConfigEvent from "../observables/config-event"; import Format from "../utils/format"; import { applyReducedMotion } from "../utils/misc"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; -import { animate } from "animejs"; +import { qs } from "../utils/dom"; -const textElement = document.querySelector( - "#liveStatsTextBottom .liveSpeed", -) as HTMLElement; -const miniElement = document.querySelector( - "#liveStatsMini .speed", -) as HTMLElement; +const textElement = qs("#liveStatsTextBottom .liveSpeed"); +const miniElement = qs("#liveStatsMini .speed"); export function reset(): void { requestDebouncedAnimationFrame("live-speed.reset", () => { - textElement.innerHTML = "0"; - miniElement.innerHTML = "0"; + textElement?.setHtml("0"); + miniElement?.setHtml("0"); }); } @@ -27,8 +23,8 @@ export function update(wpm: number, raw: number): void { number = raw; } const numberText = Format.typingSpeed(number, { showDecimalPlaces: false }); - textElement.innerHTML = numberText; - miniElement.innerHTML = numberText; + textElement?.setHtml(numberText); + miniElement?.setHtml(numberText); }); } @@ -40,14 +36,14 @@ export function show(): void { if (state) return; requestDebouncedAnimationFrame("live-speed.show", () => { if (Config.liveSpeedStyle === "mini") { - miniElement.classList.remove("hidden"); - animate(miniElement, { + miniElement?.show(); + miniElement?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); } else { - textElement.classList.remove("hidden"); - animate(textElement, { + textElement?.show(); + textElement?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), }); @@ -59,18 +55,18 @@ export function show(): void { export function hide(): void { if (!state) return; requestDebouncedAnimationFrame("live-speed.hide", () => { - animate(miniElement, { + miniElement?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - miniElement.classList.add("hidden"); + miniElement?.hide(); }, }); - animate(textElement, { + textElement?.animate({ opacity: [1, 0], duration: applyReducedMotion(125), onComplete: () => { - textElement.classList.add("hidden"); + textElement?.hide(); }, }); state = false; @@ -79,10 +75,10 @@ export function hide(): void { export function instantHide(): void { if (!state) return; - miniElement.classList.add("hidden"); - miniElement.style.opacity = "0"; - textElement.classList.add("hidden"); - textElement.style.opacity = "0"; + miniElement?.hide(); + miniElement?.setStyle({ opacity: "0" }); + textElement?.hide(); + textElement?.setStyle({ opacity: "0" }); state = false; } diff --git a/frontend/src/ts/test/monkey.ts b/frontend/src/ts/test/monkey.ts index b1966c3e4c86..17a19ba8adab 100644 --- a/frontend/src/ts/test/monkey.ts +++ b/frontend/src/ts/test/monkey.ts @@ -3,17 +3,17 @@ import Config from "../config"; import * as ConfigEvent from "../observables/config-event"; import * as TestState from "../test/test-state"; import * as KeyConverter from "../utils/key-converter"; -import { animate } from "animejs"; +import { qs } from "../utils/dom"; -const monkeyEl = document.querySelector("#monkey") as HTMLElement; -const monkeyFastEl = document.querySelector("#monkey .fast") as HTMLElement; +const monkeyEl = qs("#monkey"); +const monkeyFastEl = qs("#monkey .fast"); ConfigEvent.subscribe(({ key }) => { if (key === "monkey" && TestState.isActive) { if (Config.monkey) { - monkeyEl.classList.remove("hidden"); + monkeyEl?.show(); } else { - monkeyEl.classList.add("hidden"); + monkeyEl?.hide(); } } }); @@ -22,62 +22,44 @@ let left = false; let right = false; const middleKeysState = { left: false, right: false, last: "right" }; -// 0 hand up -// 1 hand down - -// 00 both hands up -// 01 right hand down -// 10 left hand down -// 11 both hands down - -const elements = { - "00": monkeyEl.querySelector(".up"), - "01": monkeyEl.querySelector(".right"), - "10": monkeyEl.querySelector(".left"), - "11": monkeyEl.querySelector(".both"), -}; - -const elementsFast = { - "00": monkeyFastEl.querySelector(".up"), - "01": monkeyFastEl.querySelector(".right"), - "10": monkeyFastEl.querySelector(".left"), - "11": monkeyFastEl.querySelector(".both"), -}; - -function toBit(b: boolean): "1" | "0" { - return b ? "1" : "0"; -} +const upEls = monkeyEl?.qsa(".up"); +const rightEls = monkeyEl?.qsa(".right"); +const leftEls = monkeyEl?.qsa(".left"); +const bothEls = monkeyEl?.qsa(".both"); function update(): void { if (!Config.monkey) return; - if (!monkeyEl?.classList.contains("hidden")) { - (Object.keys(elements) as (keyof typeof elements)[]).forEach((key) => { - elements[key]?.classList.add("hidden"); - }); - (Object.keys(elementsFast) as (keyof typeof elements)[]).forEach((key) => { - elementsFast[key]?.classList.add("hidden"); - }); - - const id: keyof typeof elements = `${toBit(left)}${toBit(right)}`; - - elements[id]?.classList.remove("hidden"); - elementsFast[id]?.classList.remove("hidden"); + if (!monkeyEl?.hasClass("hidden")) { + upEls?.hide(); + rightEls?.hide(); + leftEls?.hide(); + bothEls?.hide(); + + if (left && right) { + bothEls?.show(); + } else if (right) { + rightEls?.show(); + } else if (left) { + leftEls?.show(); + } else { + upEls?.show(); + } } } export function updateFastOpacity(num: number): void { if (!Config.monkey) return; const opacity = mapRange(num, 130, 180, 0, 1); - animate(monkeyFastEl, { + monkeyFastEl?.animate({ opacity: opacity, duration: 1000, }); let animDuration = mapRange(num, 130, 180, 0.25, 0.01); if (animDuration === 0.25) animDuration = 0; - monkeyEl.style.animationDuration = animDuration + "s"; + monkeyEl?.setStyle({ animationDuration: animDuration + "s" }); } -export function type(event: JQuery.KeyDownEvent | KeyboardEvent): void { +export function type(event: KeyboardEvent): void { if (!Config.monkey) return; const { leftSide, rightSide } = KeyConverter.keycodeToKeyboardSide( @@ -115,7 +97,7 @@ export function type(event: JQuery.KeyDownEvent | KeyboardEvent): void { update(); } -export function stop(event: JQuery.KeyUpEvent | KeyboardEvent): void { +export function stop(event: KeyboardEvent): void { if (!Config.monkey) return; const { leftSide, rightSide } = KeyConverter.keycodeToKeyboardSide( @@ -144,28 +126,28 @@ export function stop(event: JQuery.KeyUpEvent | KeyboardEvent): void { export function show(): void { if (!Config.monkey) return; - monkeyEl.classList.remove("hidden"); - animate(monkeyEl, { + monkeyEl?.show(); + monkeyEl?.animate({ opacity: [0, 1], duration: 125, }); } export function hide(): void { - animate(monkeyEl, { + monkeyEl?.animate({ opacity: [1, 0], duration: 125, onComplete: () => { - monkeyEl.classList.add("hidden"); - monkeyEl.style.animationDuration = "0s"; - monkeyFastEl.style.opacity = "0"; + monkeyEl?.hide(); + monkeyEl?.setStyle({ animationDuration: "0s" }); + monkeyFastEl?.setStyle({ opacity: "0" }); }, }); } export function instantHide(): void { - monkeyEl.classList.add("hidden"); - monkeyEl.style.opacity = "0"; - monkeyEl.style.animationDuration = "0s"; - monkeyFastEl.style.opacity = "0"; + monkeyEl?.hide(); + monkeyEl?.setStyle({ opacity: "0" }); + monkeyEl?.setStyle({ animationDuration: "0s" }); + monkeyFastEl?.setStyle({ opacity: "0" }); } diff --git a/frontend/src/ts/test/out-of-focus.ts b/frontend/src/ts/test/out-of-focus.ts index 5a9050583954..05d4ee05db5b 100644 --- a/frontend/src/ts/test/out-of-focus.ts +++ b/frontend/src/ts/test/out-of-focus.ts @@ -1,13 +1,14 @@ import * as Misc from "../utils/misc"; import Config from "../config"; +import { qs, qsa } from "../utils/dom"; const outOfFocusTimeouts: (number | NodeJS.Timeout)[] = []; export function hide(): void { - $("#words, #compositionDisplay") - .css("transition", "none") - .removeClass("blurred"); - $(".outOfFocusWarning").addClass("hidden"); + qsa("#words, #compositionDisplay") + ?.setStyle({ transition: "none" }) + ?.removeClass("blurred"); + qs(".outOfFocusWarning")?.hide(); Misc.clearTimeouts(outOfFocusTimeouts); } @@ -15,10 +16,10 @@ export function show(): void { if (!Config.showOutOfFocusWarning) return; outOfFocusTimeouts.push( setTimeout(() => { - $("#words, #compositionDisplay") - .css("transition", "0.25s") - .addClass("blurred"); - $(".outOfFocusWarning").removeClass("hidden"); + qsa("#words, #compositionDisplay") + ?.setStyle({ transition: "0.25s" }) + ?.addClass("blurred"); + qs(".outOfFocusWarning")?.show(); }, 1000), ); } diff --git a/frontend/src/ts/test/pb-crown.ts b/frontend/src/ts/test/pb-crown.ts index 5eefc560bde1..d64c2f9218cc 100644 --- a/frontend/src/ts/test/pb-crown.ts +++ b/frontend/src/ts/test/pb-crown.ts @@ -1,9 +1,9 @@ -import { animate } from "animejs"; import { applyReducedMotion } from "../utils/misc"; +import { qs } from "../utils/dom"; export function hide(): void { visible = false; - $("#result .stats .wpm .crown").css("opacity", 0).addClass("hidden"); + qs("#result .stats .wpm .crown")?.setStyle({ opacity: "0" })?.hide(); } export type CrownType = @@ -23,25 +23,23 @@ export function getCurrentType(): CrownType { export function show(): void { if (visible) return; visible = true; - const el = document.querySelector( - "#result .stats .wpm .crown", - ) as HTMLElement; + const el = qs("#result .stats .wpm .crown"); - animate(el, { + el?.animate({ opacity: [0, 1], duration: applyReducedMotion(125), onBegin: () => { - el.classList.remove("hidden"); + el?.show(); }, }); } export function update(type: CrownType): void { currentType = type; - const el = $("#result .stats .wpm .crown"); - el.removeClass("ineligible"); - el.removeClass("pending"); - el.removeClass("error"); - el.removeClass("warning"); - el.addClass(type); + qs("#result .stats .wpm .crown") + ?.removeClass("ineligible") + ?.removeClass("pending") + ?.removeClass("error") + ?.removeClass("warning") + ?.addClass(type); } diff --git a/frontend/src/ts/test/replay.ts b/frontend/src/ts/test/replay.ts index 81659e268c1d..b6dee822a5dc 100644 --- a/frontend/src/ts/test/replay.ts +++ b/frontend/src/ts/test/replay.ts @@ -2,7 +2,7 @@ import config from "../config"; import * as Sound from "../controllers/sound-controller"; import * as TestInput from "./test-input"; import * as Arrays from "../utils/arrays"; -import { qsr } from "../utils/dom"; +import { qs, qsr } from "../utils/dom"; type ReplayAction = | "correctLetter" @@ -231,7 +231,7 @@ function addReplayEvent(action: ReplayAction, value?: number | string): void { function updateStatsString(time: number): void { const wpm = TestInput.wpmHistory[time - 1] ?? 0; const statsString = `${wpm}wpm\t${time}s`; - $("#replayStats").text(statsString); + qs("#replayStats")?.setText(statsString); } function playReplay(): void { @@ -301,7 +301,7 @@ function getReplayExport(): string { }); } -$(".pageTest #playpauseReplayButton").on("click", () => { +qs(".pageTest #playpauseReplayButton")?.on("click", () => { if (toggleButton?.className === "fas fa-play") { playReplay(); } else if (toggleButton?.className === "fas fa-pause") { @@ -309,23 +309,25 @@ $(".pageTest #playpauseReplayButton").on("click", () => { } }); -$("#replayWords").on("click", "letter", (event) => { +qs("#replayWords")?.onChild("click", "letter", (event) => { //allows user to click on the place they want to start their replay at pauseReplay(); - const replayWords = document.querySelector("#replayWords"); + const replayWords = qs("#replayWords"); + + const words = [...(replayWords?.native?.children ?? [])]; + targetWordPos = + words?.indexOf( + (event.childTarget as HTMLElement).parentNode as HTMLElement, + ) ?? 0; - const words = [...(replayWords?.children ?? [])]; - targetWordPos = words.indexOf( - (event.target as HTMLElement).parentNode as HTMLElement, - ); const letters = [...(words[targetWordPos] as HTMLElement).children]; - targetCurPos = letters.indexOf(event.target as HTMLElement); + targetCurPos = letters?.indexOf(event.childTarget as HTMLElement) ?? 0; initializeReplayPrompt(); loadOldReplay(); }); -$(".pageTest").on("click", "#watchReplayButton", () => { +qs(".pageTest")?.onChild("click", "#watchReplayButton", () => { toggleReplayDisplay(); }); diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 451887f7d296..4658cee5315a 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -49,6 +49,7 @@ import * as TestState from "./test-state"; import { blurInputElement } from "../input/input-element"; import * as ConnectionState from "../states/connection"; import { currentQuote } from "./test-words"; +import { qs, qsa } from "../utils/dom"; let result: CompletedEvent; let minChartVal: number; @@ -333,41 +334,41 @@ function updateWpmAndAcc(): void { inf = true; } - $("#result .stats .wpm .top .text").text(Config.typingSpeedUnit); + qs("#result .stats .wpm .top .text")?.setText(Config.typingSpeedUnit); if (inf) { - $("#result .stats .wpm .bottom").text("Infinite"); + qs("#result .stats .wpm .bottom")?.setText("Infinite"); } else { - $("#result .stats .wpm .bottom").text(Format.typingSpeed(result.wpm)); + qs("#result .stats .wpm .bottom")?.setText(Format.typingSpeed(result.wpm)); } - $("#result .stats .raw .bottom").text(Format.typingSpeed(result.rawWpm)); - $("#result .stats .acc .bottom").text( + qs("#result .stats .raw .bottom")?.setText(Format.typingSpeed(result.rawWpm)); + qs("#result .stats .acc .bottom")?.setText( result.acc === 100 ? "100%" : Format.accuracy(result.acc), ); if (Config.alwaysShowDecimalPlaces) { if (Config.typingSpeedUnit !== "wpm") { - $("#result .stats .wpm .bottom").attr( + qs("#result .stats .wpm .bottom")?.setAttribute( "aria-label", result.wpm.toFixed(2) + " wpm", ); - $("#result .stats .raw .bottom").attr( + qs("#result .stats .raw .bottom")?.setAttribute( "aria-label", result.rawWpm.toFixed(2) + " wpm", ); } else { - $("#result .stats .wpm .bottom").removeAttr("aria-label"); - $("#result .stats .raw .bottom").removeAttr("aria-label"); + qs("#result .stats .wpm .bottom")?.removeAttribute("aria-label"); + qs("#result .stats .raw .bottom")?.removeAttribute("aria-label"); } let time = Numbers.roundTo2(result.testDuration).toFixed(2) + "s"; if (result.testDuration > 61) { time = DateTime.secondsToString(Numbers.roundTo2(result.testDuration)); } - $("#result .stats .time .bottom .text").text(time); - // $("#result .stats .acc .bottom").removeAttr("aria-label"); + qs("#result .stats .time .bottom .text")?.setText(time); + // qs("#result .stats .acc .bottom")?.removeAttribute("aria-label"); - $("#result .stats .acc .bottom").attr( + qs("#result .stats .acc .bottom")?.setAttribute( "aria-label", `${TestInput.accuracy.correct} correct\n${TestInput.accuracy.incorrect} incorrect`, ); @@ -385,11 +386,11 @@ function updateWpmAndAcc(): void { rawWpmHover += " (" + result.rawWpm.toFixed(2) + " wpm)"; } - $("#result .stats .wpm .bottom").attr("aria-label", wpmHover); - $("#result .stats .raw .bottom").attr("aria-label", rawWpmHover); + qs("#result .stats .wpm .bottom")?.setAttribute("aria-label", wpmHover); + qs("#result .stats .raw .bottom")?.setAttribute("aria-label", rawWpmHover); - $("#result .stats .acc .bottom") - .attr( + qs("#result .stats .acc .bottom") + ?.setAttribute( "aria-label", `${ result.acc === 100 @@ -399,16 +400,16 @@ function updateWpmAndAcc(): void { TestInput.accuracy.incorrect } incorrect`, ) - .attr("data-balloon-break", ""); + ?.setAttribute("data-balloon-break", ""); } } function updateConsistency(): void { - $("#result .stats .consistency .bottom").text( + qs("#result .stats .consistency .bottom")?.setText( Format.percentage(result.consistency), ); if (Config.alwaysShowDecimalPlaces) { - $("#result .stats .consistency .bottom").attr( + qs("#result .stats .consistency .bottom")?.setAttribute( "aria-label", Format.percentage(result.keyConsistency, { showDecimalPlaces: true, @@ -416,7 +417,7 @@ function updateConsistency(): void { }), ); } else { - $("#result .stats .consistency .bottom").attr( + qs("#result .stats .consistency .bottom")?.setAttribute( "aria-label", `${result.consistency}% (${result.keyConsistency}% key)`, ); @@ -427,11 +428,13 @@ function updateTime(): void { const afkSecondsPercent = Numbers.roundTo2( (result.afkDuration / result.testDuration) * 100, ); - $("#result .stats .time .bottom .afk").text(""); + qs("#result .stats .time .bottom .afk")?.setText(""); if (afkSecondsPercent > 0) { - $("#result .stats .time .bottom .afk").text(afkSecondsPercent + "% afk"); + qs("#result .stats .time .bottom .afk")?.setText( + afkSecondsPercent + "% afk", + ); } - $("#result .stats .time .bottom").attr( + qs("#result .stats .time .bottom")?.setAttribute( "aria-label", `${result.afkDuration}s afk ${afkSecondsPercent}%`, ); @@ -441,14 +444,14 @@ function updateTime(): void { if (result.testDuration > 61) { time = DateTime.secondsToString(Numbers.roundTo2(result.testDuration)); } - $("#result .stats .time .bottom .text").text(time); + qs("#result .stats .time .bottom .text")?.setText(time); } else { let time = Math.round(result.testDuration) + "s"; if (result.testDuration > 61) { time = DateTime.secondsToString(Math.round(result.testDuration)); } - $("#result .stats .time .bottom .text").text(time); - $("#result .stats .time .bottom").attr( + qs("#result .stats .time .bottom .text")?.setText(time); + qs("#result .stats .time .bottom")?.setAttribute( "aria-label", `${Numbers.roundTo2(result.testDuration)}s (${ result.afkDuration @@ -458,11 +461,13 @@ function updateTime(): void { } export function updateTodayTracker(): void { - $("#result .stats .time .bottom .timeToday").text(TodayTracker.getString()); + qs("#result .stats .time .bottom .timeToday")?.setText( + TodayTracker.getString(), + ); } function updateKey(): void { - $("#result .stats .key .bottom").text( + qs("#result .stats .key .bottom")?.setText( result.charStats[0] + "/" + result.charStats[1] + @@ -479,8 +484,8 @@ export function showCrown(type: PbCrown.CrownType): void { } export function updateCrownText(text: string, wide = false): void { - $("#result .stats .wpm .crown").attr("aria-label", text); - $("#result .stats .wpm .crown").attr( + qs("#result .stats .wpm .crown")?.setAttribute("aria-label", text); + qs("#result .stats .wpm .crown")?.setAttribute( "data-balloon-length", wide ? "medium" : "", ); @@ -662,21 +667,26 @@ async function updateTags(dontSave: boolean): Promise { } catch (e) {} if (userTagsCount === 0) { - $("#result .stats .tags").addClass("hidden"); + qs("#result .stats .tags")?.hide(); } else { - $("#result .stats .tags").removeClass("hidden"); + qs("#result .stats .tags")?.show(); } if (activeTags.length === 0) { - $("#result .stats .tags .bottom").html("
no tags
"); + qs("#result .stats .tags .bottom")?.setHtml( + "
no tags
", + ); } else { - $("#result .stats .tags .bottom").text(""); + qs("#result .stats .tags .bottom")?.setText(""); } - $("#result .stats .tags .editTagsButton").attr("data-result-id", ""); - $("#result .stats .tags .editTagsButton").attr( + qs("#result .stats .tags .editTagsButton")?.setAttribute( + "data-result-id", + "", + ); + qs("#result .stats .tags .editTagsButton")?.setAttribute( "data-active-tag-ids", activeTags.map((t) => t._id).join(","), ); - $("#result .stats .tags .editTagsButton").addClass("invisible"); + qs("#result .stats .tags .editTagsButton")?.addClass("invisible"); let annotationSide: LabelPosition = "start"; let labelAdjust = 15; @@ -691,7 +701,7 @@ async function updateTags(dontSave: boolean): Promise { Config.difficulty, Config.lazyMode, ); - $("#result .stats .tags .bottom").append(` + qs("#result .stats .tags .bottom")?.appendHtml(`
${tag.display}
`); const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); @@ -716,13 +726,10 @@ async function updateTags(dontSave: boolean): Promise { result.rawWpm, result.consistency, ); - $( - `#result .stats .tags .bottom div[tagid="${tag._id}"] .fas`, - ).removeClass("hidden"); - $(`#result .stats .tags .bottom div[tagid="${tag._id}"]`).attr( - "aria-label", - "+" + Numbers.roundTo2(result.wpm - tpb), - ); + qs(`#result .stats .tags .bottom div[tagid="${tag._id}"] .fas`)?.show(); + qs( + `#result .stats .tags .bottom div[tagid="${tag._id}"]`, + )?.setAttribute("aria-label", "+" + Numbers.roundTo2(result.wpm - tpb)); // console.log("new pb for tag " + tag.display); } else { const themecolors = await ThemeColors.getAll(); @@ -810,7 +817,7 @@ function updateTestType(randomQuote: Quote | null): void { testType += `
stop on ${Config.stopOnError}`; } - $("#result .stats .testType .bottom").html(testType); + qs("#result .stats .testType .bottom")?.setHtml(testType); } function updateOther( @@ -864,11 +871,11 @@ function updateOther( } if (otherText === "") { - $("#result .stats .info").addClass("hidden"); + qs("#result .stats .info")?.hide(); } else { - $("#result .stats .info").removeClass("hidden"); + qs("#result .stats .info")?.show(); otherText = otherText.substring(4); - $("#result .stats .info .bottom").html(otherText); + qs("#result .stats .info .bottom")?.setHtml(otherText); } } @@ -884,32 +891,32 @@ export function updateRateQuote(randomQuote: Quote | null): void { const userqr = DB.getSnapshot()?.quoteRatings?.[randomQuote.language]?.[randomQuote.id]; if (Numbers.isSafeNumber(userqr)) { - $(".pageTest #result #rateQuoteButton .icon") - .removeClass("far") - .addClass("fas"); + qs(".pageTest #result #rateQuoteButton .icon") + ?.removeClass("far") + ?.addClass("fas"); } quoteRateModal .getQuoteStats(randomQuote) .then((quoteStats) => { - $(".pageTest #result #rateQuoteButton .rating").text( + qs(".pageTest #result #rateQuoteButton .rating")?.setText( quoteStats?.average?.toFixed(1) ?? "", ); }) .catch((_e: unknown) => { - $(".pageTest #result #rateQuoteButton .rating").text("?"); + qs(".pageTest #result #rateQuoteButton .rating")?.setText("?"); }); - $(".pageTest #result #rateQuoteButton") - .css({ opacity: 0 }) - .removeClass("hidden") - .css({ opacity: 1 }); + qs(".pageTest #result #rateQuoteButton") + ?.setStyle({ opacity: "0" }) + ?.show() + ?.setStyle({ opacity: "1" }); } } function updateQuoteFavorite(randomQuote: Quote | null): void { - const icon = $(".pageTest #result #favoriteQuoteButton .icon"); + const icon = qs(".pageTest #result #favoriteQuoteButton .icon"); if (Config.mode !== "quote" || !isAuthenticated()) { - icon.parent().addClass("hidden"); + icon?.getParent()?.hide(); return; } @@ -924,18 +931,18 @@ function updateQuoteFavorite(randomQuote: Quote | null): void { quoteId = Config.mode === "quote" ? randomQuote.id.toString() : ""; const userFav = QuotesController.isQuoteFavorite(randomQuote); - icon.removeClass(userFav ? "far" : "fas").addClass(userFav ? "fas" : "far"); - icon.parent().removeClass("hidden"); + icon?.removeClass(userFav ? "far" : "fas")?.addClass(userFav ? "fas" : "far"); + icon?.getParent()?.show(); } function updateQuoteSource(randomQuote: Quote | null): void { if (Config.mode === "quote") { - $("#result .stats .source").removeClass("hidden"); - $("#result .stats .source .bottom").html( + qs("#result .stats .source")?.show(); + qs("#result .stats .source .bottom")?.setHtml( randomQuote?.source ?? "Error: Source unknown", ); } else { - $("#result .stats .source").addClass("hidden"); + qs("#result .stats .source")?.hide(); } } @@ -952,29 +959,29 @@ export async function update( resultAnnotation = []; result = structuredClone(res); hideCrown(); - $("#resultWordsHistory .words").empty(); - $("#result #resultWordsHistory").addClass("hidden"); - $("#result #replayStats").text(""); - $("#result #resultReplay").addClass("hidden"); - $("#result #replayWords").empty(); - $("#retrySavingResultButton").addClass("hidden"); - $(".pageTest #result #rateQuoteButton .icon") - .removeClass("fas") - .addClass("far"); - $(".pageTest #result #rateQuoteButton .rating").text(""); - $(".pageTest #result #rateQuoteButton").addClass("hidden"); - $("#words").removeClass("blurred"); + qs("#resultWordsHistory .words")?.empty(); + qs("#result #resultWordsHistory")?.hide(); + qs("#result #replayStats")?.setText(""); + qs("#result #resultReplay")?.hide(); + qs("#result #replayWords")?.empty(); + qs("#retrySavingResultButton")?.hide(); + qs(".pageTest #result #rateQuoteButton .icon") + ?.removeClass("fas") + ?.addClass("far"); + qs(".pageTest #result #rateQuoteButton .rating")?.setText(""); + qs(".pageTest #result #rateQuoteButton")?.hide(); + qs("#words")?.removeClass("blurred"); blurInputElement(); - $("#result .stats .time .bottom .afk").text(""); + qs("#result .stats .time .bottom .afk")?.setText(""); if (isAuthenticated()) { - $("#result .loginTip").addClass("hidden"); + qs("#result .loginTip")?.hide(); } else { - $("#result .loginTip").removeClass("hidden"); + qs("#result .loginTip")?.show(); } if (Config.ads === "off" || Config.ads === "result") { - $("#result #watchVideoAdButton").addClass("hidden"); + qs("#result #watchVideoAdButton")?.hide(); } else { - $("#result #watchVideoAdButton").removeClass("hidden"); + qs("#result #watchVideoAdButton")?.show(); } if (!ConnectionState.get()) { @@ -1004,17 +1011,17 @@ export async function update( ChartController.result.resize(); if ( - $("#result .stats .tags").hasClass("hidden") && - $("#result .stats .info").hasClass("hidden") + qs("#result .stats .tags")?.hasClass("hidden") && + qs("#result .stats .info")?.hasClass("hidden") ) { - $("#result .stats .infoAndTags").addClass("hidden"); + qs("#result .stats .infoAndTags")?.hide(); } else { - $("#result .stats .infoAndTags").removeClass("hidden"); + qs("#result .stats .infoAndTags")?.show(); } if (GlarsesMode.get()) { - $("main #result .noStressMessage").remove(); - $("main #result").prepend(` + qs("main #result .noStressMessage")?.remove(); + qs("main #result")?.prependHtml(`
= 5) { @@ -1096,10 +1103,10 @@ export async function update( AdController.updateFooterAndVerticalAds(true); void Funbox.clear(); - $(".pageTest .loading").addClass("hidden"); - $("#result").removeClass("hidden"); + qs(".pageTest .loading")?.hide(); + qs("#result")?.show(); - const resultEl = document.querySelector("#result"); + const resultEl = qs("#result"); resultEl?.focus({ preventScroll: true, }); @@ -1109,10 +1116,10 @@ export async function update( duration: Misc.applyReducedMotion(125), }); - Misc.scrollToCenterOrTop(resultEl); + Misc.scrollToCenterOrTop(resultEl?.native ?? null); void AdController.renderResult(); TestUI.setResultCalculating(false); - $("#words").empty(); + qs("#words")?.empty(); ChartController.result.resize(); } @@ -1216,7 +1223,7 @@ function updateResultChartDataVisibility(): void { } } - const buttons = $(".pageTest #result .chart .chartLegend button"); + const buttons = qsa(".pageTest #result .chart .chartLegend button"); // Check if there are any tag PB annotations const hasTagPbAnnotations = resultAnnotation.some( @@ -1224,7 +1231,7 @@ function updateResultChartDataVisibility(): void { ); for (const button of buttons) { - const id = $(button).data("id") as string; + const id = button?.getAttribute("data-id") as string; if (id === "scale") { continue; @@ -1240,15 +1247,12 @@ function updateResultChartDataVisibility(): void { continue; } - $(button).toggleClass("active", vis[id]); + button.toggleClass("active", vis[id]); if (id === "pbLine") { - $(button).toggleClass("hidden", !isAuthenticated()); + button.toggleClass("hidden", !isAuthenticated()); } else if (id === "tagPbLine") { - $(button).toggleClass( - "hidden", - !isAuthenticated() || !hasTagPbAnnotations, - ); + button.toggleClass("hidden", !isAuthenticated() || !hasTagPbAnnotations); } } } @@ -1270,18 +1274,18 @@ export function updateTagsAfterEdit( } if (tagIds.length === 0) { - $(`.pageTest #result .tags .bottom`).html( + qs(`.pageTest #result .tags .bottom`)?.setHtml( "
no tags
", ); } else { - $(`.pageTest #result .tags .bottom div.noTags`).remove(); - const currentElements = $(`.pageTest #result .tags .bottom div[tagid]`); + qs(`.pageTest #result .tags .bottom div.noTags`)?.remove(); + const currentElements = qsa(`.pageTest #result .tags .bottom div[tagid]`); const checked: string[] = []; - currentElements.each((_, element) => { - const tagId = $(element).attr("tagid") as string; + currentElements.forEach((element) => { + const tagId = element.getAttribute("tagid") ?? ""; if (!tagIds.includes(tagId)) { - $(element).remove(); + element?.remove(); } else { checked.push(tagId); } @@ -1298,56 +1302,59 @@ export function updateTagsAfterEdit( } }); - // $(`.pageTest #result .tags .bottom`).html(tagNames.join("
")); - $(`.pageTest #result .tags .bottom`).append(html); + // qs(`.pageTest #result .tags .bottom`)?.setHtml(tagNames.join("
")); + qs(`.pageTest #result .tags .bottom`)?.appendHtml(html); } - $(`.pageTest #result .tags .top .editTagsButton`).attr( + qs(`.pageTest #result .tags .top .editTagsButton`)?.setAttribute( "data-active-tag-ids", tagIds.join(","), ); } -$(".pageTest #result .chart .chartLegend button").on("click", async (event) => { - const $target = $(event.target); - const id = $target.data("id") as string; +qs(".pageTest #result .chart .chartLegend button")?.on( + "click", + async (event) => { + const $target = event.target as HTMLElement; + const id = $target.getAttribute("data-id"); - if (id === "scale") { - setConfig("startGraphsAtZero", !Config.startGraphsAtZero); - return; - } + if (id === "scale") { + setConfig("startGraphsAtZero", !Config.startGraphsAtZero); + return; + } - if ( - id !== "raw" && - id !== "burst" && - id !== "errors" && - id !== "pbLine" && - id !== "tagPbLine" - ) { - return; - } - const vis = resultChartDataVisibility.get(); - vis[id] = !vis[id]; - resultChartDataVisibility.set(vis); + if ( + id !== "raw" && + id !== "burst" && + id !== "errors" && + id !== "pbLine" && + id !== "tagPbLine" + ) { + return; + } + const vis = resultChartDataVisibility.get(); + vis[id] = !vis[id]; + resultChartDataVisibility.set(vis); - updateResultChartDataVisibility(); - updateMinMaxChartValues(); - applyMinMaxChartValues(); - void ChartController.result.updateColors(); - ChartController.result.update(); -}); + updateResultChartDataVisibility(); + updateMinMaxChartValues(); + applyMinMaxChartValues(); + void ChartController.result.updateColors(); + ChartController.result.update(); + }, +); -$(".pageTest #favoriteQuoteButton").on("click", async () => { +qs(".pageTest #favoriteQuoteButton")?.on("click", async () => { if (quoteLang === undefined || quoteId === "") { Notifications.add("Could not get quote stats!", -1); return; } - const $button = $(".pageTest #favoriteQuoteButton .icon"); + const $button = qs(".pageTest #favoriteQuoteButton .icon"); const dbSnapshot = DB.getSnapshot(); if (!dbSnapshot) return; - if ($button.hasClass("fas")) { + if ($button?.hasClass("fas")) { // Remove from showLoaderBar(); const response = await Ape.users.removeQuoteFromFavorites({ @@ -1361,7 +1368,7 @@ $(".pageTest #favoriteQuoteButton").on("click", async () => { Notifications.add(response.body.message, response.status === 200 ? 1 : -1); if (response.status === 200) { - $button.removeClass("fas").addClass("far"); + $button?.removeClass("fas")?.addClass("far"); const quoteIndex = dbSnapshot.favoriteQuotes?.[quoteLang]?.indexOf( quoteId, ) as number; @@ -1378,7 +1385,7 @@ $(".pageTest #favoriteQuoteButton").on("click", async () => { Notifications.add(response.body.message, response.status === 200 ? 1 : -1); if (response.status === 200) { - $button.removeClass("far").addClass("fas"); + $button?.removeClass("far")?.addClass("fas"); dbSnapshot.favoriteQuotes ??= {}; dbSnapshot.favoriteQuotes[quoteLang] ??= []; dbSnapshot.favoriteQuotes[quoteLang]?.push(quoteId); diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index 64d82659b815..d3c7a7c99bf5 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -3,68 +3,68 @@ import { Mode } from "@monkeytype/schemas/shared"; import Config from "../config"; import * as ConfigEvent from "../observables/config-event"; import { getActivePage } from "../signals/core"; -import { applyReducedMotion, promiseAnimate } from "../utils/misc"; +import { applyReducedMotion } from "../utils/misc"; import { areUnsortedArraysEqual } from "../utils/arrays"; import * as AuthEvent from "../observables/auth-event"; -import { animate } from "animejs"; +import { qs } from "../utils/dom"; export function show(): void { - $("#testConfig").removeClass("invisible"); - $("#mobileTestConfigButton").removeClass("invisible"); + qs("#testConfig")?.removeClass("invisible"); + qs("#mobileTestConfigButton")?.removeClass("invisible"); } export function hide(): void { - $("#testConfig").addClass("invisible"); - $("#mobileTestConfigButton").addClass("invisible"); + qs("#testConfig")?.addClass("invisible"); + qs("#mobileTestConfigButton")?.addClass("invisible"); } export async function instantUpdate(): Promise { - $("#testConfig .mode .textButton").removeClass("active"); - $("#testConfig .mode .textButton[mode='" + Config.mode + "']").addClass( + qs("#testConfig .mode .textButton")?.removeClass("active"); + qs("#testConfig .mode .textButton[mode='" + Config.mode + "']")?.addClass( "active", ); - $("#testConfig .puncAndNum").addClass("hidden"); - $("#testConfig .spacer").addClass("hidden"); - $("#testConfig .time").addClass("hidden"); - $("#testConfig .wordCount").addClass("hidden"); - $("#testConfig .customText").addClass("hidden"); - $("#testConfig .quoteLength").addClass("hidden"); - $("#testConfig .zen").addClass("hidden"); + qs("#testConfig .puncAndNum")?.hide(); + qs("#testConfig .spacer")?.hide(); + qs("#testConfig .time")?.hide(); + qs("#testConfig .wordCount")?.hide(); + qs("#testConfig .customText")?.hide(); + qs("#testConfig .quoteLength")?.hide(); + qs("#testConfig .zen")?.hide(); if (Config.mode === "time") { - $("#testConfig .puncAndNum").removeClass("hidden").css({ + qs("#testConfig .puncAndNum")?.show()?.setStyle({ width: "", opacity: "", }); - $("#testConfig .leftSpacer").removeClass("hidden"); - $("#testConfig .rightSpacer").removeClass("hidden"); - $("#testConfig .time").removeClass("hidden"); + qs("#testConfig .leftSpacer")?.show(); + qs("#testConfig .rightSpacer")?.show(); + qs("#testConfig .time")?.show(); updateActiveExtraButtons("time", Config.time); } else if (Config.mode === "words") { - $("#testConfig .puncAndNum").removeClass("hidden").css({ + qs("#testConfig .puncAndNum")?.show()?.setStyle({ width: "", opacity: "", }); - $("#testConfig .leftSpacer").removeClass("hidden"); - $("#testConfig .rightSpacer").removeClass("hidden"); - $("#testConfig .wordCount").removeClass("hidden"); + qs("#testConfig .leftSpacer")?.show(); + qs("#testConfig .rightSpacer")?.show(); + qs("#testConfig .wordCount")?.show(); updateActiveExtraButtons("words", Config.words); } else if (Config.mode === "quote") { - $("#testConfig .rightSpacer").removeClass("hidden"); - $("#testConfig .quoteLength").removeClass("hidden"); + qs("#testConfig .rightSpacer")?.show(); + qs("#testConfig .quoteLength")?.show(); updateActiveExtraButtons("quoteLength", Config.quoteLength); } else if (Config.mode === "custom") { - $("#testConfig .puncAndNum").removeClass("hidden").css({ + qs("#testConfig .puncAndNum")?.show()?.setStyle({ width: "", opacity: "", }); - $("#testConfig .leftSpacer").removeClass("hidden"); - $("#testConfig .rightSpacer").removeClass("hidden"); - $("#testConfig .customText").removeClass("hidden"); + qs("#testConfig .leftSpacer")?.show(); + qs("#testConfig .rightSpacer")?.show(); + qs("#testConfig .customText")?.show(); } updateActiveExtraButtons("quoteLength", Config.quoteLength); @@ -113,21 +113,21 @@ async function update(previous: Mode, current: Mode): Promise { zen: false, }; - const puncAndNumEl = $("#testConfig .puncAndNum"); + const puncAndNumEl = qs("#testConfig .puncAndNum"); if (puncAndNumVisible[current] !== puncAndNumVisible[previous]) { puncAndNumEl - .css({ + ?.setStyle({ width: "unset", - opacity: 1, + opacity: "1", }) - .removeClass("hidden"); + ?.show(); const width = Math.round( - puncAndNumEl[0]?.getBoundingClientRect().width ?? 0, + puncAndNumEl?.native.getBoundingClientRect().width ?? 0, ); - animate(puncAndNumEl[0] as HTMLElement, { + puncAndNumEl?.animate({ width: [ (puncAndNumVisible[previous] ? width : 0) + "px", (puncAndNumVisible[current] ? width : 0) + "px", @@ -142,22 +142,20 @@ async function update(previous: Mode, current: Mode): Promise { ease: easing.both, onComplete: () => { if (puncAndNumVisible[current]) { - puncAndNumEl.css("width", "unset"); + puncAndNumEl?.setStyle({ width: "unset" }); } else { - puncAndNumEl.addClass("hidden"); + puncAndNumEl?.hide(); } }, }); - const leftSpacerEl = document.querySelector( - "#testConfig .leftSpacer", - ) as HTMLElement; + const leftSpacerEl = qs("#testConfig .leftSpacer"); - leftSpacerEl.style.width = "0.5em"; - leftSpacerEl.style.opacity = "1"; - leftSpacerEl.classList.remove("hidden"); + leftSpacerEl?.setStyle({ width: "0.5em" }); + leftSpacerEl?.setStyle({ opacity: "1" }); + leftSpacerEl?.show(); - animate(leftSpacerEl, { + leftSpacerEl?.animate({ width: [ puncAndNumVisible[previous] ? "0.5em" : 0, puncAndNumVisible[current] ? "0.5em" : 0, @@ -172,23 +170,21 @@ async function update(previous: Mode, current: Mode): Promise { ease: easing.both, onComplete: () => { if (puncAndNumVisible[current]) { - leftSpacerEl.style.width = ""; + leftSpacerEl?.setStyle({ width: "" }); } else { - leftSpacerEl.classList.add("hidden"); + leftSpacerEl?.hide(); } }, }); } - const rightSpacerEl = document.querySelector( - "#testConfig .rightSpacer", - ) as HTMLElement; + const rightSpacerEl = qs("#testConfig .rightSpacer"); - rightSpacerEl.style.width = "0.5em"; - rightSpacerEl.style.opacity = "1"; - rightSpacerEl.classList.remove("hidden"); + rightSpacerEl?.setStyle({ width: "0.5em" }); + rightSpacerEl?.setStyle({ opacity: "1" }); + rightSpacerEl?.show(); - animate(rightSpacerEl, { + rightSpacerEl?.animate({ width: [ previous === "zen" ? "0px" : "0.5em", current === "zen" ? "0px" : "0.5em", @@ -202,34 +198,34 @@ async function update(previous: Mode, current: Mode): Promise { ease: easing.both, onComplete: () => { if (current === "zen") { - rightSpacerEl.classList.add("hidden"); + rightSpacerEl?.hide(); } else { - rightSpacerEl.style.width = ""; + rightSpacerEl?.setStyle({ width: "" }); } }, }); - const currentEl = $(`#testConfig .${submenu[current]}`); - const previousEl = $(`#testConfig .${submenu[previous]}`); + const currentEl = qs(`#testConfig .${submenu[current]}`); + const previousEl = qs(`#testConfig .${submenu[previous]}`); const previousWidth = Math.round( - previousEl[0]?.getBoundingClientRect().width ?? 0, + previousEl?.native.getBoundingClientRect().width ?? 0, ); - previousEl.addClass("hidden"); - currentEl.removeClass("hidden"); + previousEl?.hide(); + currentEl?.show(); const currentWidth = Math.round( - currentEl[0]?.getBoundingClientRect().width ?? 0, + currentEl?.native.getBoundingClientRect().width ?? 0, ); - previousEl.removeClass("hidden"); - currentEl.addClass("hidden"); + previousEl?.show(); + currentEl?.hide(); const widthDifference = currentWidth - previousWidth; const widthStep = widthDifference / 2; - await promiseAnimate(previousEl[0] as HTMLElement, { + await previousEl?.promiseAnimate({ opacity: [1, 0], width: [previousWidth + "px", previousWidth + widthStep + "px"], duration: animTime / 2, @@ -237,19 +233,19 @@ async function update(previous: Mode, current: Mode): Promise { }); previousEl - .css({ - opacity: 1, + ?.setStyle({ + opacity: "1", width: "unset", }) - .addClass("hidden"); + ?.hide(); currentEl - .css({ - opacity: 0, + ?.setStyle({ + opacity: "0", width: previousWidth + widthStep + "px", }) - .removeClass("hidden"); + ?.show(); - await promiseAnimate(currentEl[0] as HTMLElement, { + await currentEl?.promiseAnimate({ opacity: [0, 1], width: [previousWidth + widthStep + "px", currentWidth + "px"], duration: animTime / 2, @@ -258,64 +254,64 @@ async function update(previous: Mode, current: Mode): Promise { } function updateActiveModeButtons(mode: Mode): void { - $("#testConfig .mode .textButton").removeClass("active"); - $("#testConfig .mode .textButton[mode='" + mode + "']").addClass("active"); + qs("#testConfig .mode .textButton")?.removeClass("active"); + qs("#testConfig .mode .textButton[mode='" + mode + "']")?.addClass("active"); } function updateActiveExtraButtons(key: string, value: ConfigValue): void { if (key === "time") { - $("#testConfig .time .textButton").removeClass("active"); + qs("#testConfig .time .textButton")?.removeClass("active"); const timeCustom = ![15, 30, 60, 120].includes(value as number) ? "custom" : (value as number); - $( + qs( "#testConfig .time .textButton[timeConfig='" + timeCustom + "']", - ).addClass("active"); + )?.addClass("active"); } else if (key === "words") { - $("#testConfig .wordCount .textButton").removeClass("active"); + qs("#testConfig .wordCount .textButton")?.removeClass("active"); const wordCustom = ![10, 25, 50, 100, 200].includes(value as number) ? "custom" : (value as number); - $( + qs( "#testConfig .wordCount .textButton[wordCount='" + wordCustom + "']", - ).addClass("active"); + )?.addClass("active"); } else if (key === "quoteLength") { - $("#testConfig .quoteLength .textButton").removeClass("active"); + qs("#testConfig .quoteLength .textButton")?.removeClass("active"); if (areUnsortedArraysEqual(value as QuoteLength[], [0, 1, 2, 3])) { - $("#testConfig .quoteLength .textButton[quotelength='all']").addClass( + qs("#testConfig .quoteLength .textButton[quotelength='all']")?.addClass( "active", ); } else { (value as QuoteLength[]).forEach((ql) => { - $( + qs( "#testConfig .quoteLength .textButton[quoteLength='" + ql + "']", - ).addClass("active"); + )?.addClass("active"); }); } } else if (key === "numbers") { if (value === false) { - $("#testConfig .numbersMode.textButton").removeClass("active"); + qs("#testConfig .numbersMode.textButton")?.removeClass("active"); } else { - $("#testConfig .numbersMode.textButton").addClass("active"); + qs("#testConfig .numbersMode.textButton")?.addClass("active"); } } else if (key === "punctuation") { if (value === false) { - $("#testConfig .punctuationMode.textButton").removeClass("active"); + qs("#testConfig .punctuationMode.textButton")?.removeClass("active"); } else { - $("#testConfig .punctuationMode.textButton").addClass("active"); + qs("#testConfig .punctuationMode.textButton")?.addClass("active"); } } } export function showFavoriteQuoteLength(): void { - $("#testConfig .quoteLength .favorite").removeClass("hidden"); + qs("#testConfig .quoteLength .favorite")?.show(); } export function hideFavoriteQuoteLength(): void { - $("#testConfig .quoteLength .favorite").addClass("hidden"); + qs("#testConfig .quoteLength .favorite")?.hide(); } let ignoreConfigEvent = false; diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index d8c5be184470..5feea760eeca 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -68,6 +68,7 @@ import { animate } from "animejs"; import { setInputElementValue } from "../input/input-element"; import { debounce } from "throttle-debounce"; import * as Time from "../states/time"; +import { qs } from "../utils/dom"; let failReason = ""; @@ -707,7 +708,7 @@ export async function retrySavingResult(): Promise { } retrySaving.canRetry = false; - $("#retrySavingResultButton").addClass("hidden"); + qs("#retrySavingResultButton")?.hide(); Notifications.add("Retrying to save..."); @@ -866,8 +867,8 @@ export async function finish(difficultyFailed = false): Promise { opacity: 0, duration: Misc.applyReducedMotion(125), }); - $(".pageTest #typingTest").addClass("hidden"); - $(".pageTest .loading").removeClass("hidden"); + qs(".pageTest #typingTest")?.hide(); + qs(".pageTest .loading")?.show(); await Misc.sleep(0); //allow ui update TestUI.onTestFinish(); @@ -1215,7 +1216,7 @@ async function saveResult( }); AccountButton.loading(false); retrySaving.canRetry = true; - $("#retrySavingResultButton").removeClass("hidden"); + qs("#retrySavingResultButton")?.show(); if (!isRetrying) { retrySaving.completedEvent = completedEvent; } @@ -1243,7 +1244,7 @@ async function saveResult( //only allow retry if status is not in this list if (![460, 461, 463, 464, 465, 466].includes(response.status)) { retrySaving.canRetry = true; - $("#retrySavingResultButton").removeClass("hidden"); + qs("#retrySavingResultButton")?.show(); if (!isRetrying) { retrySaving.completedEvent = result; } @@ -1264,11 +1265,11 @@ async function saveResult( } const data = response.body.data; - $("#result .stats .tags .editTagsButton").attr( + qs("#result .stats .tags .editTagsButton")?.setAttribute( "data-result-id", data.insertedId, ); - $("#result .stats .tags .editTagsButton").removeClass("invisible"); + qs("#result .stats .tags .editTagsButton")?.removeClass("invisible"); const localDataToSave: DB.SaveLocalResultData = {}; @@ -1339,12 +1340,12 @@ async function saveResult( duration: Misc.applyReducedMotion(250), }); - $("#result .stats .dailyLeaderboard .bottom").html( + qs("#result .stats .dailyLeaderboard .bottom")?.setHtml( Format.rank(data.dailyLeaderboardRank, { fallback: "" }), ); } - $("#retrySavingResultButton").addClass("hidden"); + qs("#retrySavingResultButton")?.hide(); if (isRetrying) { Notifications.add("Result saved", 1, { important: true }); } @@ -1397,15 +1398,19 @@ const debouncedZipfCheck = debounce(250, async () => { } }); -$(".pageTest").on("click", "#testModesNotice .textButton.restart", () => { - restart(); -}); +qs(".pageTest")?.onChild( + "click", + "#testModesNotice .textButton.restart", + () => { + restart(); + }, +); -$(".pageTest").on("click", "#testInitFailed button.restart", () => { +qs(".pageTest")?.onChild("click", "#testInitFailed button.restart", () => { restart(); }); -$(".pageTest").on("click", "#restartTestButton", () => { +qs(".pageTest")?.onChild("click", "#restartTestButton", () => { ManualRestart.set(); if (TestUI.resultCalculating) return; if ( @@ -1421,14 +1426,18 @@ $(".pageTest").on("click", "#restartTestButton", () => { } }); -$(".pageTest").on("click", "#retrySavingResultButton", retrySavingResult); +qs(".pageTest")?.onChild( + "click", + "#retrySavingResultButton", + retrySavingResult, +); -$(".pageTest").on("click", "#nextTestButton", () => { +qs(".pageTest")?.onChild("click", "#nextTestButton", () => { ManualRestart.set(); restart(); }); -$(".pageTest").on("click", "#restartTestButtonWithSameWordset", () => { +qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => { if (Config.mode === "zen") { Notifications.add("Repeat test disabled in zen mode"); return; @@ -1439,10 +1448,11 @@ $(".pageTest").on("click", "#restartTestButtonWithSameWordset", () => { }); }); -$(".pageTest").on("click", "#testConfig .mode .textButton", (e) => { +qs(".pageTest")?.onChild("click", "#testConfig .mode .textButton", (e) => { if (TestState.testRestarting) return; - if ($(e.currentTarget).hasClass("active")) return; - const mode = ($(e.currentTarget).attr("mode") ?? "time") as Mode; + if ((e.childTarget as HTMLElement).classList.contains("active")) return; + const mode = ((e.childTarget as HTMLElement)?.getAttribute("mode") ?? + "time") as Mode; if (mode === undefined) return; if (setConfig("mode", mode)) { ManualRestart.set(); @@ -1450,9 +1460,9 @@ $(".pageTest").on("click", "#testConfig .mode .textButton", (e) => { } }); -$(".pageTest").on("click", "#testConfig .wordCount .textButton", (e) => { +qs(".pageTest")?.onChild("click", "#testConfig .wordCount .textButton", (e) => { if (TestState.testRestarting) return; - const wrd = $(e.currentTarget).attr("wordCount") ?? "15"; + const wrd = (e.childTarget as HTMLElement)?.getAttribute("wordCount") ?? "15"; if (wrd !== "custom") { if (setConfig("words", parseInt(wrd))) { ManualRestart.set(); @@ -1461,9 +1471,10 @@ $(".pageTest").on("click", "#testConfig .wordCount .textButton", (e) => { } }); -$(".pageTest").on("click", "#testConfig .time .textButton", (e) => { +qs(".pageTest")?.onChild("click", "#testConfig .time .textButton", (e) => { if (TestState.testRestarting) return; - const mode = $(e.currentTarget).attr("timeConfig") ?? "10"; + const mode = + (e.childTarget as HTMLElement)?.getAttribute("timeConfig") ?? "10"; if (mode !== "custom") { if (setConfig("time", parseInt(mode))) { ManualRestart.set(); @@ -1472,43 +1483,51 @@ $(".pageTest").on("click", "#testConfig .time .textButton", (e) => { } }); -$(".pageTest").on("click", "#testConfig .quoteLength .textButton", (e) => { - if (TestState.testRestarting) return; - const lenAttr = $(e.currentTarget).attr("quoteLength"); - if (lenAttr === "all") { - if (setQuoteLengthAll()) { - ManualRestart.set(); - restart(); - } - } else { - const len = parseInt(lenAttr ?? "1") as QuoteLength; +qs(".pageTest")?.onChild( + "click", + "#testConfig .quoteLength .textButton", + (e) => { + if (TestState.testRestarting) return; + const lenAttr = (e.childTarget as HTMLElement)?.getAttribute("quoteLength"); + if (lenAttr === "all") { + if (setQuoteLengthAll()) { + ManualRestart.set(); + restart(); + } + } else { + const len = parseInt(lenAttr ?? "1") as QuoteLength; - if (len !== -2) { - let arr: QuoteLengthConfig = []; + if (len !== -2) { + let arr: QuoteLengthConfig = []; - if (e.shiftKey) { - arr = [...Config.quoteLength, len]; - } else { - arr = [len]; - } + if (e.shiftKey) { + arr = [...Config.quoteLength, len]; + } else { + arr = [len]; + } - if (setConfig("quoteLength", arr)) { - ManualRestart.set(); - restart(); + if (setConfig("quoteLength", arr)) { + ManualRestart.set(); + restart(); + } } } - } -}); - -$(".pageTest").on("click", "#testConfig .punctuationMode.textButton", () => { - if (TestState.testRestarting) return; - if (setConfig("punctuation", !Config.punctuation)) { - ManualRestart.set(); - restart(); - } -}); + }, +); + +qs(".pageTest")?.onChild( + "click", + "#testConfig .punctuationMode.textButton", + () => { + if (TestState.testRestarting) return; + if (setConfig("punctuation", !Config.punctuation)) { + ManualRestart.set(); + restart(); + } + }, +); -$(".pageTest").on("click", "#testConfig .numbersMode.textButton", () => { +qs(".pageTest")?.onChild("click", "#testConfig .numbersMode.textButton", () => { if (TestState.testRestarting) return; if (setConfig("numbers", !Config.numbers)) { ManualRestart.set(); @@ -1516,7 +1535,7 @@ $(".pageTest").on("click", "#testConfig .numbersMode.textButton", () => { } }); -$("header").on("click", "nav #startTestButton, #logo", () => { +qs("header")?.onChild("click", "nav #startTestButton, #logo", () => { if (getActivePage() === "test") restart(); // Result.showConfetti(); }); diff --git a/frontend/src/ts/test/test-screenshot.ts b/frontend/src/ts/test/test-screenshot.ts index eec120b2a2eb..2f1d7c1e8152 100644 --- a/frontend/src/ts/test/test-screenshot.ts +++ b/frontend/src/ts/test/test-screenshot.ts @@ -11,34 +11,34 @@ import { getHtmlByUserFlags } from "../controllers/user-flag-controller"; import * as Notifications from "../elements/notifications"; import { convertRemToPixels } from "../utils/numbers"; import * as TestState from "./test-state"; +import { qs } from "../utils/dom"; let revealReplay = false; let revertCookie = false; function revert(): void { hideLoaderBar(); - $("#ad-result-wrapper").removeClass("hidden"); - $("#ad-result-small-wrapper").removeClass("hidden"); - $("#testConfig").removeClass("hidden"); - $(".pageTest .screenshotSpacer").remove(); - $("#notificationCenter").removeClass("hidden"); - $("#commandLineMobileButton").removeClass("hidden"); - $(".pageTest .ssWatermark").addClass("hidden"); - $(".pageTest .ssWatermark").text("monkeytype.com"); // Reset watermark text - $(".pageTest .buttons").removeClass("hidden"); - $("noscript").removeClass("hidden"); - $("#nocss").removeClass("hidden"); - $("header, footer").removeClass("invisible"); - $("#result").removeClass("noBalloons"); - $(".wordInputHighlight").removeClass("hidden"); - $(".highlightContainer").removeClass("hidden"); - if (revertCookie) $("#cookiesModal").removeClass("hidden"); - if (revealReplay) $("#resultReplay").removeClass("hidden"); + qs("#ad-result-wrapper")?.show(); + qs("#ad-result-small-wrapper")?.show(); + qs("#testConfig")?.show(); + qs(".pageTest .screenshotSpacer")?.remove(); + qs("#notificationCenter")?.show(); + qs("#commandLineMobileButton")?.show(); + qs(".pageTest .ssWatermark")?.hide(); + qs(".pageTest .ssWatermark")?.setText("monkeytype.com"); // Reset watermark text + qs(".pageTest .buttons")?.show(); + qs("noscript")?.show(); + qs("#nocss")?.show(); + qs("header, footer")?.show(); + qs("#result")?.removeClass("noBalloons"); + qs(".wordInputHighlight")?.show(); + qs(".highlightContainer")?.show(); + if (revertCookie) qs("#cookiesModal")?.show(); + if (revealReplay) qs("#resultReplay")?.show(); if (!isAuthenticated()) { - $(".pageTest .loginTip").removeClass("hidden"); + qs(".pageTest .loginTip")?.removeClass("hidden"); } - (document.querySelector("html") as HTMLElement).style.scrollBehavior = - "smooth"; + qs("html")?.setStyle({ scrollBehavior: "smooth" }); for (const fb of getActiveFunboxesWithFunction("applyGlobalCSS")) { fb.functions.applyGlobalCSS(); } @@ -55,7 +55,7 @@ async function generateCanvas(): Promise { const { domToCanvas } = await import("modern-screenshot"); showLoaderBar(true); - if (!$("#resultReplay").hasClass("hidden")) { + if (!qs("#resultReplay")?.hasClass("hidden")) { revealReplay = true; Replay.pauseReplay(); } @@ -68,8 +68,8 @@ async function generateCanvas(): Promise { // --- UI Preparation --- const dateNow = new Date(Date.now()); - $("#resultReplay").addClass("hidden"); - $(".pageTest .ssWatermark").removeClass("hidden"); + qs("#resultReplay")?.hide(); + qs(".pageTest .ssWatermark")?.show(); const snapshot = DB.getSnapshot(); const ssWatermark = [format(dateNow, "dd MMM yyyy HH:mm"), "monkeytype.com"]; @@ -79,28 +79,28 @@ async function generateCanvas(): Promise { })}`; ssWatermark.unshift(userText); } - $(".pageTest .ssWatermark").html( + qs(".pageTest .ssWatermark")?.setHtml( ssWatermark .map((el) => `${el}`) .join("|"), ); - $(".pageTest .buttons").addClass("hidden"); - $("#notificationCenter").addClass("hidden"); - $("#commandLineMobileButton").addClass("hidden"); - $(".pageTest .loginTip").addClass("hidden"); - $("noscript").addClass("hidden"); - $("#nocss").addClass("hidden"); - $("#ad-result-wrapper").addClass("hidden"); - $("#ad-result-small-wrapper").addClass("hidden"); - $("#testConfig").addClass("hidden"); + qs(".pageTest .buttons")?.hide(); + qs("#notificationCenter")?.hide(); + qs("#commandLineMobileButton")?.hide(); + qs(".pageTest .loginTip")?.hide(); + qs("noscript")?.hide(); + qs("#nocss")?.hide(); + qs("#ad-result-wrapper")?.hide(); + qs("#ad-result-small-wrapper")?.hide(); + qs("#testConfig")?.hide(); // Ensure spacer is removed before adding a new one if function is called rapidly - $(".pageTest .screenshotSpacer").remove(); - $(".page.pageTest").prepend("
"); - $("header, footer").addClass("invisible"); - $("#result").addClass("noBalloons"); - $(".wordInputHighlight").addClass("hidden"); - $(".highlightContainer").addClass("hidden"); - if (revertCookie) $("#cookiesModal").addClass("hidden"); + qs(".pageTest .screenshotSpacer")?.remove(); + qs(".page.pageTest")?.prependHtml("
"); + qs("header, footer")?.addClass("invisible"); + qs("#result")?.addClass("noBalloons"); + qs(".wordInputHighlight")?.hide(); + qs(".highlightContainer")?.hide(); + if (revertCookie) qs("#cookiesModal")?.hide(); for (const fb of getActiveFunboxesWithFunction("clearGlobal")) { fb.functions.clearGlobal(); @@ -110,8 +110,8 @@ async function generateCanvas(): Promise { window.scrollTo({ top: 0, behavior: "auto" }); // --- Target Element Calculation --- - const src = $("#result .wrapper"); - if (!src.length) { + const src = qs("#result .wrapper"); + if (src === null) { console.error("Result wrapper not found for screenshot"); Notifications.add("Screenshot target element not found", -1); revert(); @@ -119,10 +119,10 @@ async function generateCanvas(): Promise { } await Misc.sleep(50); // Small delay for render updates - const sourceX = src.offset()?.left ?? 0; - const sourceY = src.offset()?.top ?? 0; - const sourceWidth = src.outerWidth(true) as number; - const sourceHeight = src.outerHeight(true) as number; + const sourceX = src.getOffsetLeft() ?? 0; + const sourceY = src.getOffsetTop() ?? 0; + const sourceWidth = src.getOuterWidth(); + const sourceHeight = src.getOuterHeight(); const paddingX = convertRemToPixels(2); const paddingY = convertRemToPixels(2); @@ -350,7 +350,7 @@ export async function download(): Promise { } } -$(".pageTest").on("click", "#saveScreenshotButton", (event) => { +qs(".pageTest")?.onChild("click", "#saveScreenshotButton", (event) => { if (event.shiftKey) { void download(); } else { @@ -358,23 +358,23 @@ $(".pageTest").on("click", "#saveScreenshotButton", (event) => { } // reset save screenshot button icon - $("#saveScreenshotButton i") - .removeClass("fas fa-download") - .addClass("far fa-image"); + qs("#saveScreenshotButton i") + ?.removeClass("fas fa-download") + ?.addClass("far fa-image"); }); -$(document).on("keydown", (event) => { +document.addEventListener("keydown", (event) => { if (!(TestState.resultVisible && getActivePage() === "test")) return; if (event.key !== "Shift") return; - $("#result #saveScreenshotButton i") - .removeClass("far fa-image") - .addClass("fas fa-download"); + qs("#result #saveScreenshotButton i") + ?.removeClass("far fa-image") + ?.addClass("fas fa-download"); }); -$(document).on("keyup", (event) => { +document.addEventListener("keyup", (event) => { if (!(TestState.resultVisible && getActivePage() === "test")) return; if (event.key !== "Shift") return; - $("#result #saveScreenshotButton i") - .removeClass("fas fa-download") - .addClass("far fa-image"); + qs("#result #saveScreenshotButton i") + ?.removeClass("fas fa-download") + ?.addClass("far fa-image"); }); diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index f9774b6e69c5..41f439b3b1ba 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -271,8 +271,8 @@ export class ElementWithUtils { /** * Make element visible by scrolling the element's ancestor containers */ - scrollIntoView(options: ScrollIntoViewOptions): this { - this.native.scrollIntoView(options); + scrollIntoView(options?: ScrollIntoViewOptions): this { + this.native.scrollIntoView(options ?? {}); return this; } @@ -668,6 +668,34 @@ export class ElementWithUtils { return this; } + /** + * Get the element's height + margin + */ + + getOuterHeight(): number { + const style = getComputedStyle(this.native); + + return ( + this.native.getBoundingClientRect().height + + parseFloat(style.marginTop) + + parseFloat(style.marginBottom) + ); + } + + /** + * Get The element's width + margin + */ + + getOuterWidth(): number { + const style = getComputedStyle(this.native); + + return ( + this.native.getBoundingClientRect().width + + parseFloat(style.marginLeft) + + parseFloat(style.marginRight) + ); + } + /** * Get the element's width */ @@ -696,6 +724,17 @@ export class ElementWithUtils { return this.native.offsetLeft; } + /** + * Get the element's children + */ + getChildren(): ElementsWithUtils { + const children = Array.from(this.native.children); + const convertedChildren = new ElementsWithUtils( + ...children.map((child) => new ElementWithUtils(child as HTMLElement)), + ); + return convertedChildren; + } + /** * Animate the element using Anime.js * @param animationParams The Anime.js animation parameters @@ -806,8 +845,8 @@ export class ElementWithUtils { /** * Focus the element */ - focus(): void { - this.native.focus(); + focus(options?: FocusOptions): void { + this.native.focus(options ?? {}); } /**