forked from IsThereAnyDeal/AugmentedSteam
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rework achievement sort to better support all locales
- Loading branch information
Showing
1 changed file
with
50 additions
and
86 deletions.
There are no files selected for viewing
136 changes: 50 additions & 86 deletions
136
src/js/Content/Features/Community/ProfileStats/FAchievementSort.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,124 +1,88 @@ | ||
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<CProfileStats> { | ||
|
||
private container: Element|null = null; | ||
private bookmark: Element|null = null; | ||
private unlockedAchievements: Element[] = []; | ||
private achievementsFetched: boolean = false; | ||
|
||
private defaultSort: Element[] = []; | ||
private unlockedMap: Map<Element, number> = new Map(); | ||
|
||
private dateFormat: string|null = null; | ||
private dateOptions: DateTimeOptions|undefined = undefined; | ||
|
||
override async checkPrerequisites(): Promise<boolean> { | ||
|
||
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<void> { | ||
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], | ||
["time", Localization.str.date_unlocked], | ||
], | ||
"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<void> { | ||
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); | ||
} | ||
} | ||
} |