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/test/focus.ts b/frontend/src/ts/test/focus.ts index 7b9842e2f124..dd5973fbd63c 100644 --- a/frontend/src/ts/test/focus.ts +++ b/frontend/src/ts/test/focus.ts @@ -88,14 +88,13 @@ export function set(value: boolean, withCursor = false): void { }); } -$(document).on("mousemove", function (event) { +document.addEventListener("mousemove", function (event) { if (PageTransition.get()) return; if (!state) return; if ( - event.originalEvent && // To avoid mouse/desk vibration from creating a flashy effect, we'll unfocus @ >5px instead of >0px - (event.originalEvent.movementX > unfocusPx || - event.originalEvent.movementY > unfocusPx) + event.movementX > unfocusPx || + event.movementY > unfocusPx ) { set(false); } 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..2329a70732aa 100644 --- a/frontend/src/ts/test/layout-emulator.ts +++ b/frontend/src/ts/test/layout-emulator.ts @@ -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 04def513b736..a8940bc6af1f 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -48,6 +48,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; @@ -326,41 +327,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`, ); @@ -378,11 +379,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 @@ -392,16 +393,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, @@ -409,7 +410,7 @@ function updateConsistency(): void { }), ); } else { - $("#result .stats .consistency .bottom").attr( + qs("#result .stats .consistency .bottom")?.setAttribute( "aria-label", `${result.consistency}% (${result.keyConsistency}% key)`, ); @@ -420,11 +421,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}%`, ); @@ -434,14 +437,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 @@ -451,11 +454,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] + @@ -472,8 +477,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" : "", ); @@ -655,21 +660,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; @@ -684,7 +694,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); @@ -709,13 +719,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(); @@ -803,7 +810,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( @@ -857,11 +864,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); } } @@ -877,32 +884,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; } @@ -917,18 +924,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(); } } @@ -945,29 +952,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()) { @@ -997,17 +1004,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) { @@ -1089,10 +1096,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, }); @@ -1102,10 +1109,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(); } @@ -1209,7 +1216,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( @@ -1217,7 +1224,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; diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index 62af3478227f..9b8a234e595e 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -226,8 +226,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; } @@ -651,6 +651,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 @@ -761,8 +772,8 @@ export class ElementWithUtils { /** * Focus the element */ - focus(): void { - this.native.focus(); + focus(options?: FocusOptions): void { + this.native.focus(options ?? {}); } /**