diff --git a/src/client/ClanModal.ts b/src/client/ClanModal.ts index 975367c2b7..e4b1b0886f 100644 --- a/src/client/ClanModal.ts +++ b/src/client/ClanModal.ts @@ -16,6 +16,7 @@ import "./components/clan/ClanTransferView"; import "./components/ConfirmDialog"; import "./components/CopyButton"; import { modalHeader } from "./components/ui/ModalHeader"; +import { modalRouter } from "./ModalRouter"; import { translateText } from "./Utils"; type View = @@ -58,6 +59,7 @@ export class ClanModal extends BaseModal { pendingRequestCount: number; stats: ClanStats | null; } | null = null; + private openGeneration = 0; private get onListView(): boolean { return this.view === "list" && !this.selectedClanTag; @@ -152,13 +154,19 @@ export class ClanModal extends BaseModal { this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; + modalRouter.syncArgs("clan", { clan: null }); }, ariaLabel, rightContent: clan ? this.tagPill(clan.tag) : undefined, }); } - protected onOpen(): void { + protected onOpen(args?: Record): void { + const clanTag = typeof args?.clan === "string" ? args.clan.trim() : ""; + if (clanTag) { + void this.openInitialView(clanTag); + return; + } this.loadMyClans(); } @@ -170,6 +178,14 @@ export class ClanModal extends BaseModal { this.myRole = null; this.browseCache = null; this.detailCache = null; + this.openGeneration++; + } + + private async openInitialView(clanTag: string) { + const generation = ++this.openGeneration; + await this.loadMyClans(); + if (generation !== this.openGeneration || !this.isModalOpen) return; + this.openDetail(clanTag); } private async loadMyClans() { @@ -385,6 +401,7 @@ export class ClanModal extends BaseModal { private openDetail(tag: string) { this.selectedClanTag = tag; this.view = "detail"; + modalRouter.syncArgs("clan", { clan: tag }); } private renderMyClans() { diff --git a/src/client/ModalRouter.ts b/src/client/ModalRouter.ts index a2224f86e4..0162240572 100644 --- a/src/client/ModalRouter.ts +++ b/src/client/ModalRouter.ts @@ -123,6 +123,24 @@ class ModalRouter { this.replaceHash("#" + params.toString()); } + /** Called when a router-managed modal changes non-tab route state. */ + syncArgs(name: string, args: Record): void { + if (this.routingFromUrl) return; + if (this.currentName !== name) return; + const params = this.currentHashParams(); + params.set("modal", name); + for (const [key, value] of Object.entries(args)) { + if (key === "modal") continue; + if (value === undefined || value === null || value === "") { + params.delete(key); + continue; + } + if (typeof value === "object") continue; + params.set(key, String(value)); + } + this.replaceHash("#" + params.toString()); + } + /** True if the current hash is `#modal=...`. */ isHashRouted(): boolean { const hash = window.location.hash; @@ -142,7 +160,7 @@ class ModalRouter { if (args) { for (const [key, value] of Object.entries(args)) { if (key === "modal") continue; - if (value === undefined || value === null) continue; + if (value === undefined || value === null || value === "") continue; if (typeof value === "object") continue; params.set(key, String(value)); }