refactor: use ElementWithUtils in page class (@fehmer) (#7223)

This commit is contained in:
Christian Fehmer 2025-12-12 15:16:11 +01:00 committed by GitHub
parent 735740da98
commit 2b380bb931
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 50 additions and 25 deletions

View file

@ -64,6 +64,7 @@ vi.mock("../src/ts/utils/dom", () => {
getOffsetTop: vi.fn().mockReturnValue(0),
getOffsetLeft: vi.fn().mockReturnValue(0),
animate: vi.fn().mockResolvedValue(null),
promiseAnimate: vi.fn().mockResolvedValue(null),
native: document.createElement("div"),
};
};

View file

@ -59,14 +59,14 @@ async function showSyncLoading({
loadingOptions: LoadingOptions[];
totalDuration: number;
}): Promise<void> {
PageLoading.page.element.removeClass("hidden").css("opacity", 0);
PageLoading.page.element.show().setStyle({ opacity: "0" });
await PageLoading.page.beforeShow({});
const fillDivider = loadingOptions.length;
const fillOffset = 100 / fillDivider;
//void here to run the loading promise as soon as possible
void Misc.promiseAnimate(PageLoading.page.element[0] as HTMLElement, {
void PageLoading.page.element.promiseAnimate({
opacity: "1",
duration: totalDuration / 2,
});
@ -97,13 +97,13 @@ async function showSyncLoading({
}
}
await Misc.promiseAnimate(PageLoading.page.element[0] as HTMLElement, {
await PageLoading.page.element.promiseAnimate({
opacity: "0",
duration: totalDuration / 2,
});
await PageLoading.page.afterHide();
PageLoading.page.element.addClass("hidden");
PageLoading.page.element.hide();
}
// Global abort controller for keyframe promises
@ -206,12 +206,12 @@ export async function change(
//previous page
await previousPage?.beforeHide?.();
previousPage.element.removeClass("hidden").css("opacity", 1);
await Misc.promiseAnimate(previousPage.element[0] as HTMLElement, {
previousPage.element.show().setStyle({ opacity: "1" });
await previousPage.element.promiseAnimate({
opacity: "0",
duration: totalDuration / 2,
});
previousPage.element.addClass("hidden");
previousPage.element.hide();
await previousPage?.afterHide();
// we need to evaluate and store next page loading mode in case options.loadingOptions.loadingMode is sync
@ -281,8 +281,8 @@ export async function change(
});
}
nextPage.element.removeClass("hidden").css("opacity", 0);
await Misc.promiseAnimate(nextPage.element[0] as HTMLElement, {
nextPage.element.show().setStyle({ opacity: "0" });
await nextPage.element.promiseAnimate({
opacity: "1",
duration: totalDuration / 2,
});

View file

@ -1,9 +1,10 @@
import Page from "./page";
import * as Skeleton from "../utils/skeleton";
import { qsr } from "../utils/dom";
export const page = new Page({
id: "404",
element: $(".page.page404"),
element: qsr(".page.page404"),
path: "/404",
afterHide: async (): Promise<void> => {
Skeleton.remove("page404");

View file

@ -10,6 +10,7 @@ import * as Skeleton from "../utils/skeleton";
import { TypingStats, SpeedHistogram } from "@monkeytype/schemas/public";
import { getNumberWithMagnitude, numberWithSpaces } from "../utils/numbers";
import { tryCatch } from "@monkeytype/util/trycatch";
import { qsr } from "../utils/dom";
function reset(): void {
$(".pageAbout .contributors").empty();
@ -199,7 +200,7 @@ function getHistogramDataBucketed(data: Record<string, number>): {
export const page = new Page({
id: "about",
element: $(".page.pageAbout"),
element: qsr(".page.pageAbout"),
path: "/about",
afterHide: async (): Promise<void> => {
reset();

View file

@ -12,6 +12,7 @@ import * as BlockedUserTable from "../elements/account-settings/blocked-user-tab
import * as Notifications from "../elements/notifications";
import { z } from "zod";
import * as AuthEvent from "../observables/auth-event";
import { qsr } from "../utils/dom";
const pageElement = $(".page.pageAccountSettings");
@ -229,7 +230,7 @@ AuthEvent.subscribe((event) => {
export const page = new PageWithUrlParams({
id: "accountSettings",
display: "Account Settings",
element: pageElement,
element: qsr(".page.pageAccountSettings"),
path: "/account-settings",
urlParamsSchema: UrlParameterSchema,
afterHide: async (): Promise<void> => {

View file

@ -35,6 +35,7 @@ import { SnapshotResult } from "../constants/default-snapshot";
import Ape from "../ape";
import { AccountChart } from "@monkeytype/schemas/configs";
import { SortedTableWithLimit } from "../utils/sorted-table";
import { qsr } from "../utils/dom";
let filterDebug = false;
//toggle filterdebug
@ -1195,7 +1196,7 @@ ConfigEvent.subscribe(({ key }) => {
export const page = new Page<undefined>({
id: "account",
element: $(".page.pageAccount"),
element: qsr(".page.pageAccount"),
path: "/account",
loadingOptions: {
loadingMode: () => {

View file

@ -30,8 +30,7 @@ import { Friend, UserNameSchema } from "@monkeytype/schemas/users";
import * as Loader from "../elements/loader";
import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
import { remoteValidation } from "../utils/remote-validation";
const pageElement = $(".page.pageFriends");
import { qsr } from "../utils/dom";
let friendsTable: SortedTable<Friend> | undefined = undefined;
@ -499,7 +498,7 @@ function update(): void {
export const page = new Page<undefined>({
id: "friends",
display: "Friends",
element: pageElement,
element: qsr(".page.pageFriends"),
path: "/friends",
loadingOptions: {
loadingMode: () => {

View file

@ -44,6 +44,7 @@ import { isSafeNumber } from "@monkeytype/util/numbers";
import { Mode, Mode2, ModeSchema } from "@monkeytype/schemas/shared";
import * as ServerConfiguration from "../ape/server-configuration";
import { getAvatarElement } from "../utils/discord-avatar";
import { qsr } from "../utils/dom";
const LeaderboardTypeSchema = z.enum(["allTime", "weekly", "daily"]);
type LeaderboardType = z.infer<typeof LeaderboardTypeSchema>;
@ -1489,7 +1490,7 @@ $(".page.pageLeaderboards .buttonGroup.friendsOnlyButtons").on(
export const page = new PageWithUrlParams({
id: "leaderboards",
element: $(".page.pageLeaderboards"),
element: qsr(".page.pageLeaderboards"),
path: "/leaderboards",
urlParamsSchema: UrlParameterSchema,

View file

@ -1,6 +1,7 @@
import Page from "./page";
import * as Skeleton from "../utils/skeleton";
import { promiseAnimate } from "../utils/misc";
import { qsr } from "../utils/dom";
const pageEl = $(".page.pageLoading");
const barEl = pageEl.find(".bar");
@ -45,7 +46,7 @@ export async function showBar(): Promise<void> {
export const page = new Page({
id: "loading",
element: pageEl,
element: qsr(".page.pageLoading"),
path: "/",
afterHide: async (): Promise<void> => {
Skeleton.remove("pageLoading");

View file

@ -208,7 +208,7 @@ new ValidatedHtmlInputElement(passwordVerifyInputEl, {
export const page = new Page({
id: "login",
element: $(".page.pageLogin"),
element: qsr(".page.pageLogin"),
path: "/login",
afterHide: async (): Promise<void> => {
hidePreloader();

View file

@ -3,6 +3,7 @@ import {
safeParse as parseUrlSearchParams,
serialize as serializeUrlSearchParams,
} from "zod-urlsearchparams";
import { ElementWithUtils } from "../utils/dom";
export type PageName =
| "loading"
@ -69,7 +70,7 @@ export type LoadingOptions = {
type PageProperties<T> = {
id: PageName;
display?: string;
element: JQuery;
element: ElementWithUtils;
path: string;
loadingOptions?: LoadingOptions;
beforeHide?: () => Promise<void>;
@ -84,7 +85,7 @@ async function empty(): Promise<void> {
export default class Page<T> {
public id: PageName;
public display: string | undefined;
public element: JQuery;
public element: ElementWithUtils;
public pathname: string;
public loadingOptions: LoadingOptions | undefined;

View file

@ -20,7 +20,7 @@ function disableButton(): void {
export const page = new Page({
id: "profileSearch",
element: $(".page.pageProfileSearch"),
element: qsr(".page.pageProfileSearch"),
path: "/profile",
afterHide: async (): Promise<void> => {
Skeleton.remove("pageProfileSearch");

View file

@ -12,6 +12,7 @@ import * as TestActivity from "../elements/test-activity";
import { TestActivityCalendar } from "../elements/test-activity-calendar";
import { getFirstDayOfTheWeek } from "../utils/date-and-time";
import { addFriend } from "./friends";
import { qsr } from "../utils/dom";
const firstDayOfTheWeek = getFirstDayOfTheWeek();
@ -260,7 +261,7 @@ $(".page.pageProfile").on("click", ".profile .addFriendButton", async () => {
export const page = new Page<undefined | UserProfile>({
id: "profile",
element: $(".page.pageProfile"),
element: qsr(".page.pageProfile"),
path: "/profile",
afterHide: async (): Promise<void> => {
Skeleton.remove("pageProfile");

View file

@ -1011,7 +1011,7 @@ AuthEvent.subscribe((event) => {
export const page = new PageWithUrlParams({
id: "settings",
element: $(".page.pageSettings"),
element: qsr(".page.pageSettings"),
path: "/settings",
urlParamsSchema: StateSchema,
afterHide: async (): Promise<void> => {

View file

@ -9,10 +9,11 @@ import * as Keymap from "../elements/keymap";
import * as TestConfig from "../test/test-config";
import * as ScrollToTop from "../elements/scroll-to-top";
import { blurInputElement } from "../input/input-element";
import { qsr } from "../utils/dom";
export const page = new Page({
id: "test",
element: $(".page.pageTest"),
element: qsr(".page.pageTest"),
path: "/",
beforeHide: async (): Promise<void> => {
blurInputElement();

View file

@ -514,6 +514,22 @@ export class ElementWithUtils<T extends HTMLElement = HTMLElement> {
animate(animationParams: AnimationParams): JSAnimation {
return animejsAnimate(this.native, animationParams);
}
/**
* Animate the element using Anime.js
* @param animationParams The Anime.js animation parameters
*/
async promiseAnimate(animationParams: AnimationParams): Promise<void> {
return new Promise((resolve) => {
animejsAnimate(this.native, {
...animationParams,
onComplete: (self, e) => {
animationParams.onComplete?.(self, e);
resolve();
},
});
});
}
}
/**