diff --git a/frontend/__tests__/setup-tests.ts b/frontend/__tests__/setup-tests.ts index 21a133a1ff32..0456f9681161 100644 --- a/frontend/__tests__/setup-tests.ts +++ b/frontend/__tests__/setup-tests.ts @@ -65,6 +65,8 @@ vi.mock("../src/ts/utils/dom", async (importOriginal) => { getOffsetLeft: vi.fn().mockReturnValue(0), animate: vi.fn().mockResolvedValue(null), promiseAnimate: vi.fn().mockResolvedValue(null), + slideUp: vi.fn().mockResolvedValue(null), + slideDown: vi.fn().mockResolvedValue(null), native: document.createElement("div"), // @ts-expect-error - mocking private method hasValue: vi.fn().mockReturnValue(false), diff --git a/frontend/src/ts/commandline/lists/result-screen.ts b/frontend/src/ts/commandline/lists/result-screen.ts index 60b528594ca4..1abf3acdf436 100644 --- a/frontend/src/ts/commandline/lists/result-screen.ts +++ b/frontend/src/ts/commandline/lists/result-screen.ts @@ -97,7 +97,7 @@ const commands: Command[] = [ display: "Toggle word history", icon: "fa-align-left", exec: (): void => { - TestUI.toggleResultWords(); + void TestUI.toggleResultWords(); }, available: (): boolean => { return TestState.resultVisible; diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 125c18d18b73..afe9c1c34c68 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -753,30 +753,14 @@ function toggleSettingsGroup(groupName: string): void { const groupEl = qs(`.pageSettings .settingsGroup.${groupName}`); if (!groupEl?.hasClass("slideup")) { - groupEl?.setStyle({ overflow: "hidden" })?.animate({ - height: 0, - duration: 250, - onComplete: () => { - groupEl - ?.hide() - .setStyle({ height: "", overflow: "" }) - .addClass("slideup"); - }, - }); + void groupEl?.slideUp(250); + groupEl?.addClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).addClass( "rotateIcon", ); } else { - groupEl?.show(); - groupEl?.setStyle({ height: "", overflow: "hidden" }); - const height = groupEl.getOffsetHeight(); - groupEl?.animate({ - height: [0, height], - duration: 250, - onComplete: () => { - groupEl?.setStyle({ height: "", overflow: "" }).removeClass("slideup"); - }, - }); + void groupEl?.slideDown(250); + groupEl?.removeClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).removeClass( "rotateIcon", ); diff --git a/frontend/src/ts/test/replay.ts b/frontend/src/ts/test/replay.ts index 0e24c4c61047..81659e268c1d 100644 --- a/frontend/src/ts/test/replay.ts +++ b/frontend/src/ts/test/replay.ts @@ -2,6 +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"; type ReplayAction = | "correctLetter" @@ -30,6 +31,8 @@ let stopwatchList: NodeJS.Timeout[] = []; const toggleButton = document.getElementById("playpauseReplayButton") ?.children[0]; +const replayEl = qsr(".pageTest #resultReplay"); + function replayGetWordsList(wordsListFromScript: string[]): void { wordsList = wordsListFromScript; } @@ -187,24 +190,11 @@ function loadOldReplay(): number { } function toggleReplayDisplay(): void { - if ($("#resultReplay").stop(true, true).hasClass("hidden")) { + if (replayEl.isHidden()) { initializeReplayPrompt(); loadOldReplay(); //show - if (!$("#watchReplayButton").hasClass("loaded")) { - $("#words").html( - `
`, - ); - $("#resultReplay") - .removeClass("hidden") - .css("display", "none") - .slideDown(250); - } else { - $("#resultReplay") - .removeClass("hidden") - .css("display", "none") - .slideDown(250); - } + void replayEl.slideDown(250); } else { //hide if ( @@ -213,9 +203,7 @@ function toggleReplayDisplay(): void { ) { pauseReplay(); } - $("#resultReplay").slideUp(250, () => { - $("#resultReplay").addClass("hidden"); - }); + void replayEl.slideUp(250); } } diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index ec2b9f068179..21eef47ee016 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -1121,7 +1121,7 @@ export async function update( ); if (Config.alwaysShowWordsHistory && canQuickRestart && !GlarsesMode.get()) { - TestUI.toggleResultWords(true); + void TestUI.toggleResultWords(true); } AdController.updateFooterAndVerticalAds(true); void Funbox.clear(); diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index d10e6e6651a8..73c0110ff816 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -57,6 +57,7 @@ import * as XPBar from "../elements/xp-bar"; import * as ModesNotice from "../elements/modes-notice"; import * as Last10Average from "../elements/last-10-average"; import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer"; +import { qsr } from "../utils/dom"; export const updateHintsPositionDebounced = Misc.debounceUntilResolved( updateHintsPosition, @@ -67,6 +68,7 @@ const wordsEl = document.querySelector(".pageTest #words") as HTMLElement; const wordsWrapperEl = document.querySelector( ".pageTest #wordsWrapper", ) as HTMLElement; +const resultWordsHistoryEl = qsr(".pageTest #resultWordsHistory"); export let activeWordTop = 0; export let activeWordHeight = 0; @@ -1391,42 +1393,18 @@ async function loadWordsHistory(): Promise { return true; } -export function toggleResultWords(noAnimation = false): void { - if (TestState.resultVisible) { - ResultWordHighlight.updateToggleWordsHistoryTime(); - if ($("#resultWordsHistory").stop(true, true).hasClass("hidden")) { - //show - - if ($("#resultWordsHistory .words .word").length === 0) { - void loadWordsHistory().then(() => { - if (Config.burstHeatmap) { - void applyBurstHeatmap(); - } - $("#resultWordsHistory") - .removeClass("hidden") - .css("display", "none") - .slideDown(noAnimation ? 0 : 250, () => { - if (Config.burstHeatmap) { - void applyBurstHeatmap(); - } - }); - }); - } else { - if (Config.burstHeatmap) { - void applyBurstHeatmap(); - } - $("#resultWordsHistory") - .removeClass("hidden") - .css("display", "none") - .slideDown(noAnimation ? 0 : 250); - } - } else { - //hide +export async function toggleResultWords(noAnimation = false): Promise { + if (!TestState.resultVisible) return; + ResultWordHighlight.updateToggleWordsHistoryTime(); - $("#resultWordsHistory").slideUp(250, () => { - $("#resultWordsHistory").addClass("hidden"); - }); + if (resultWordsHistoryEl.isHidden()) { + if (resultWordsHistoryEl.qsa(".words .word").length === 0) { + await loadWordsHistory(); } + void resultWordsHistoryEl.slideDown(noAnimation ? 0 : 250); + void applyBurstHeatmap(); + } else { + void resultWordsHistoryEl.slideUp(noAnimation ? 0 : 250); } } @@ -2005,7 +1983,7 @@ $("#wordsInput").on("focusout", () => { }); $(".pageTest").on("click", "#showWordHistoryButton", () => { - toggleResultWords(); + void toggleResultWords(); }); $("#wordsWrapper").on("click", () => { diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index d0525086bb93..f37cff4abe09 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -209,6 +209,13 @@ export class ElementWithUtils { return this; } + /** + * Check if the element has the "hidden" class + */ + isHidden(): boolean { + return this.hasClass("hidden"); + } + /** * Check if element is visible */ @@ -669,6 +676,69 @@ export class ElementWithUtils { }); } + /** + * Animate the element sliding down (expanding height from 0 to full height) + * @param duration The duration of the animation in milliseconds (default: 250ms) + */ + async slideDown(duration = 250): Promise { + this.show().setStyle({ + height: "", + overflow: "hidden", + marginTop: "", + marginBottom: "", + }); + const height = this.getOffsetHeight(); + const computed = getComputedStyle(this.native); + const marginTop = computed.marginTop; + const marginBottom = computed.marginBottom; + this.setStyle({ height: "0px", marginTop: "0px", marginBottom: "0px" }); + await this.promiseAnimate({ + height: [0, height], + marginTop: [0, marginTop], + marginBottom: [0, marginBottom], + duration, + onComplete: () => { + this.setStyle({ + height: "", + overflow: "", + marginTop: "", + marginBottom: "", + }); + }, + }); + } + + /** + * Animate the element sliding up (collapsing height from full height to 0) + * @param duration The duration of the animation in milliseconds (default: 250ms) + */ + async slideUp(duration = 250): Promise { + this.show().setStyle({ + overflow: "hidden", + height: "", + marginTop: "", + marginBottom: "", + }); + const height = this.getOffsetHeight(); + const computed = getComputedStyle(this.native); + const marginTop = computed.marginTop; + const marginBottom = computed.marginBottom; + await this.promiseAnimate({ + height: [height, 0], + marginTop: [marginTop, 0], + marginBottom: [marginBottom, 0], + duration, + onComplete: () => { + this.setStyle({ + height: "", + overflow: "", + marginTop: "", + marginBottom: "", + }).hide(); + }, + }); + } + /** * Focus the element */