From 2e83910a77a0aef3bdd6be8f847892a35c2d0551 Mon Sep 17 00:00:00 2001 From: candela97 <54083835+candela97@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:21:39 +0800 Subject: [PATCH] Rework achievement sort to better support all locales --- .../ProfileStats/FAchievementSort.ts | 136 +++++++----------- 1 file changed, 50 insertions(+), 86 deletions(-) diff --git a/src/js/Content/Features/Community/ProfileStats/FAchievementSort.ts b/src/js/Content/Features/Community/ProfileStats/FAchievementSort.ts index 365016786..05a88ca95 100644 --- a/src/js/Content/Features/Community/ProfileStats/FAchievementSort.ts +++ b/src/js/Content/Features/Community/ProfileStats/FAchievementSort.ts @@ -1,49 +1,32 @@ -import {Localization} from "../../../../modulesCore"; -import {Feature, Sortbox} from "../../../modulesContent"; +import {HTML, Localization} from "../../../../modulesCore"; +import {Feature, RequestData, Sortbox} from "../../../modulesContent"; import type {CProfileStats} from "./CProfileStats"; -import {DateTime, type DateTimeOptions} from "luxon"; -import {Page} from "../../Page"; +import {DateTime} from "luxon"; export default class FAchievementSort extends Feature { - private container: Element|null = null; private bookmark: Element|null = null; + private unlockedAchievements: Element[] = []; + private achievementsFetched: boolean = false; - private defaultSort: Element[] = []; - private unlockedMap: Map = new Map(); - - private dateFormat: string|null = null; - private dateOptions: DateTimeOptions|undefined = undefined; - - override async checkPrerequisites(): Promise { - - await this.loadDateFormat(); - - // Check if we support current locale - if (!this.dateFormat) { - return false; - } + override checkPrerequisites(): boolean { // Check if the user has unlocked more than 1 achievement return document.querySelectorAll("#personalAchieve .achieveUnlockTime").length > 1; } - override async apply(): Promise { - if (!this.dateFormat) { - return; - } + override apply(): void { - this.container = document.getElementById("personalAchieve"); - if (this.container === null) { - throw new Error("Did not find #personalAchieve node"); - } + this.unlockedAchievements = Array.from(document.querySelectorAll(".achieveRow")) + .filter(el => !!el.querySelector(".achieveUnlockTime")); - const tabs = document.getElementById("tabs"); - if (tabs === null) { - throw new Error("Did not find #tabs"); - } + this.unlockedAchievements.forEach((ach, i) => { ach.dataset.esSortdefault = `${i}`; }); + + // Insert an empty div before the first unlocked achievement as an anchor for sorted nodes + this.bookmark = document.createElement("div"); + (document.querySelector(".achieveRow") as Element).before(this.bookmark); - const sortbox = Sortbox.get( + (document.getElementById("tabs") as Element).before(Sortbox.get( "achievements", [ ["default", Localization.str.theworddefault], @@ -51,74 +34,55 @@ export default class FAchievementSort extends Feature { ], "default_ASC", (sortBy: "time"|"default", reversed: boolean) => { this._sortRows(sortBy, reversed); }, - ); - if (sortbox === null) { - throw new Error("Failed to create Sortbox"); - } + )); + } - tabs.insertAdjacentElement("beforebegin", sortbox); + private async _sortRows(sortBy: "time"|"default", reversed: boolean) { - this.bookmark = document.createElement("div"); - const achieveRow = document.querySelector(".achieveRow"); - if (!achieveRow) { - throw new Error("Could not create bookmark"); - } - achieveRow.insertAdjacentElement("beforebegin", this.bookmark); - - const nodes = this.container.querySelectorAll(".achieveUnlockTime"); - for (let node of nodes) { - if (!node.firstChild?.textContent) { continue; } - const achieveRow = node.closest(".achieveRow"); - - if (!achieveRow) { continue; } - const unlockedTime = DateTime.fromFormat( - node.firstChild.textContent.trim(), - this.dateFormat, - this.dateOptions - ); - - this.defaultSort.push(achieveRow); - this.unlockedMap.set(achieveRow, unlockedTime.toUnixInteger()); - } - } + const property = `esSort${sortBy}`; - private async loadDateFormat(): Promise { - let language = await Page.runInPageContext(() => window.g_strLanguage, [], true); + if (sortBy === "time" && !this.achievementsFetched) { - switch(language) { - case "english": - this.dateFormat = "'Unlocked' d LLL, yyyy '@' h:mma"; - return; + this.achievementsFetched = true; - case "czech": - this.dateFormat = "'OdemĨeno' d. LLL. yyyy v H.mm"; - this.dateOptions = {locale: "cs"}; - return; + const url = new URL(window.location.origin + window.location.pathname); + url.searchParams.set("l", "english"); - // TODO add support for more locales - } - } + const data = await RequestData.getHttp(url.toString()); + const dom = HTML.toDom(data); + const nodes = dom.querySelectorAll(".achieveRow"); - private async _sortRows(sortBy: "time"|"default", reversed: boolean) { - if (!this.container || !this.bookmark) { - return; - } + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + const unlockedText = node.querySelector(".achieveUnlockTime")?.firstChild?.textContent?.trim(); + if (!unlockedText) { continue; } - let sortedNodes: Element[] = []; + const unlockedTime = DateTime.fromFormat( + unlockedText, + "'Unlocked' d LLL, yyyy '@' h:mma", + {"locale": "en-US"} + ); - if (sortBy === "default") { - sortedNodes = [...this.defaultSort]; - } else if (sortBy === "time") { - sortedNodes = [...this.unlockedMap.keys()] - .sort((a, b) => (this.unlockedMap.get(a) ?? 0) - (this.unlockedMap.get(b) ?? 0)); + (document.querySelector(`.achieveRow[data-es-sortdefault="${i}"]`) as Element) + .dataset[property] = `${unlockedTime.toUnixInteger()}`; + } } + this.unlockedAchievements.sort(this._getSortFunc(sortBy, property)); + if (reversed) { - sortedNodes.reverse(); + this.unlockedAchievements.reverse(); } - for (let node of sortedNodes) { - this.bookmark.insertAdjacentElement("beforebegin", node); + this.unlockedAchievements.forEach(ach => this.bookmark.before(ach)); + } + + private _getSortFunc(sortBy: "time"|"default", property) { + switch (sortBy) { + case "default": + return (a, b) => Number(a.dataset[property] ?? 0) - Number(b.dataset[property] ?? 0); + case "time": + return (a, b) => Number(b.dataset[property] ?? 0) - Number(a.dataset[property] ?? 0); } } }