diff --git a/frontend/src/ts/controllers/page-controller.ts b/frontend/src/ts/controllers/page-controller.ts index db91c32e4..e22ca05e6 100644 --- a/frontend/src/ts/controllers/page-controller.ts +++ b/frontend/src/ts/controllers/page-controller.ts @@ -15,7 +15,7 @@ import * as PageAccountSettings from "../pages/account-settings"; import * as PageTransition from "../states/page-transition"; import * as AdController from "../controllers/ad-controller"; import * as Focus from "../test/focus"; -import { PageName } from "../pages/page"; +import { PageName, PageWithUrlParams } from "../pages/page"; type ChangeOptions = { force?: boolean; @@ -110,6 +110,10 @@ export async function change( ActivePage.set(nextPage.id); await previousPage?.afterHide(); + + if (nextPage instanceof PageWithUrlParams) { + nextPage.readUrlParams(); + } await nextPage?.beforeShow({ params: options.params, // @ts-expect-error for the future (i think) diff --git a/frontend/src/ts/pages/leaderboards.ts b/frontend/src/ts/pages/leaderboards.ts index b51ca86f9..ffe8408b2 100644 --- a/frontend/src/ts/pages/leaderboards.ts +++ b/frontend/src/ts/pages/leaderboards.ts @@ -1,4 +1,4 @@ -import Page from "./page"; +import { PageWithUrlParams } from "./page"; import * as Skeleton from "../utils/skeleton"; import Config from "../config"; import { @@ -34,10 +34,6 @@ import { abbreviateNumber } from "../utils/numbers"; import { formatDistanceToNow } from "date-fns/formatDistanceToNow"; import { z } from "zod"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; -import { - safeParse as parseUrlSearchParams, - serialize as serializeUrlSearchParams, -} from "zod-urlsearchparams"; import { UTCDateMini } from "@date-fns/utc"; import * as ConfigEvent from "../observables/config-event"; import * as ActivePage from "../states/active-page"; @@ -1135,34 +1131,17 @@ function updateGetParameters(): void { params.page = state.page + 1; - const urlParams = serializeUrlSearchParams({ - schema: UrlParameterSchema, - data: params, - }); - - const newUrl = `${window.location.pathname}?${urlParams.toString()}`; - window.history.replaceState({}, "", newUrl); + page.setUrlParams(params); selectorLS.set(state); } -function readGetParameters(): void { - const urlParams = new URLSearchParams(window.location.search); - - if (urlParams.size === 0) { +function readGetParameters(params: UrlParameter | null): void { + if (params === null) { Object.assign(state, selectorLS.get()); return; } - const parsed = parseUrlSearchParams({ - schema: UrlParameterSchema, - input: urlParams, - }); - if (!parsed.success) { - return; - } - const params = parsed.data; - if (params.type !== undefined) { state.type = params.type; } @@ -1288,10 +1267,14 @@ $(".page.pageLeaderboards .buttonGroup.secondary").on( } ); -export const page = new Page({ +export const page = new PageWithUrlParams({ id: "leaderboards", element: $(".page.pageLeaderboards"), path: "/leaderboards", + urlParams: { + schema: UrlParameterSchema, + onUrlParamUpdate: readGetParameters, + }, afterHide: async (): Promise => { Skeleton.remove("pageLeaderboards"); stopTimer(); @@ -1299,7 +1282,6 @@ export const page = new Page({ beforeShow: async (): Promise => { Skeleton.append("pageLeaderboards", "main"); // await appendLanguageButtons(); //todo figure out this race condition - readGetParameters(); startTimer(); updateTypeButtons(); updateTitle(); diff --git a/frontend/src/ts/pages/page.ts b/frontend/src/ts/pages/page.ts index f16706458..b2c6a28c5 100644 --- a/frontend/src/ts/pages/page.ts +++ b/frontend/src/ts/pages/page.ts @@ -1,3 +1,9 @@ +import { z } from "zod"; +import { + safeParse as parseUrlSearchParams, + serialize as serializeUrlSearchParams, +} from "zod-urlsearchparams"; + export type PageName = | "loading" | "test" @@ -35,6 +41,7 @@ export default class Page { public display: string | undefined; public element: JQuery; public pathname: string; + public beforeHide: () => Promise; public afterHide: () => Promise; public beforeShow: (options: Options) => Promise; @@ -51,3 +58,52 @@ export default class Page { this.afterShow = props.afterShow ?? empty; } } + +type UrlParamsSchema = z.ZodObject>; +type PagePropertiesWithUrlParams< + T, + U extends UrlParamsSchema +> = PageProperties & { + urlParams: { + schema: U; + onUrlParamUpdate?: (params: z.infer | null) => void; + }; +}; + +export class PageWithUrlParams extends Page { + private urlSchema: U; + private onUrlParamUpdate?: (params: z.infer | null) => void; + + constructor(props: PagePropertiesWithUrlParams) { + super(props); + this.urlSchema = props.urlParams.schema; + this.onUrlParamUpdate = props.urlParams.onUrlParamUpdate; + } + + public readUrlParams(): void { + if (this.onUrlParamUpdate === undefined) { + return; + } + const urlParams = new URLSearchParams(window.location.search); + + const parsed = parseUrlSearchParams({ + schema: this.urlSchema, + input: urlParams, + }); + + if (!parsed.success) { + this.onUrlParamUpdate?.(null); + return; + } + + this.onUrlParamUpdate?.(parsed.data); + } + public setUrlParams(params: z.infer): void { + const urlParams = serializeUrlSearchParams({ + schema: this.urlSchema, + data: params, + }); + const newUrl = `${window.location.pathname}?${urlParams.toString()}`; + window.history.replaceState({}, "", newUrl); + } +}