diff --git a/resources/lang/en.json b/resources/lang/en.json index 83160eb11..1e671caa2 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -42,7 +42,6 @@ "play": "Play", "news": "News", "store": "Store", - "store_new_badge": "NEW", "settings": "Settings", "leaderboard": "Leaderboard", "account": "Account", diff --git a/src/client/components/DesktopNavBar.ts b/src/client/components/DesktopNavBar.ts index 77bd1e0e6..45e1d0ea0 100644 --- a/src/client/components/DesktopNavBar.ts +++ b/src/client/components/DesktopNavBar.ts @@ -1,15 +1,10 @@ import { LitElement, html } from "lit"; -import { customElement, state } from "lit/decorators.js"; -import { getCosmeticsHash } from "../Cosmetics"; -import { getGamesPlayed } from "../Utils"; - -const HELP_SEEN_KEY = "helpSeen"; -const STORE_SEEN_HASH_KEY = "storeSeenHash"; +import { customElement } from "lit/decorators.js"; +import { NavNotificationsController } from "./NavNotificationsController"; @customElement("desktop-nav-bar") export class DesktopNavBar extends LitElement { - @state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true"; - @state() private _hasNewCosmetics = false; + private _notifications = new NavNotificationsController(this); createRenderRoot() { return this; @@ -26,12 +21,6 @@ export class DesktopNavBar extends LitElement { this._updateActiveState(current); }); } - - // Check if cosmetics have changed - getCosmeticsHash().then((hash: string | null) => { - const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY); - this._hasNewCosmetics = hash !== null && hash !== seenHash; - }); } disconnectedCallback() { @@ -54,30 +43,6 @@ export class DesktopNavBar extends LitElement { }); } - private showHelpDot(): boolean { - // Only show one dot at a time to prevent - // overwhelming users. - return getGamesPlayed() < 10 && !this._helpSeen; - } - - private showStoreDot(): boolean { - return this._hasNewCosmetics && !this.showHelpDot(); - } - - private onHelpClick = () => { - localStorage.setItem(HELP_SEEN_KEY, "true"); - this._helpSeen = true; - }; - - private onStoreClick = () => { - this._hasNewCosmetics = false; - getCosmeticsHash().then((hash: string | null) => { - if (hash !== null) { - localStorage.setItem(STORE_SEEN_HASH_KEY, hash); - } - }); - }; - render() { window.currentPageId ??= "page-play"; const currentPage = window.currentPageId; @@ -142,13 +107,26 @@ export class DesktopNavBar extends LitElement { data-i18n="main.play" > - +
+ + ${this._notifications.showNewsDot() + ? html` + + + ` + : ""} +
- ${this.showStoreDot() + ${this._notifications.showStoreDot() ? html` - ${this.showHelpDot() + ${this._notifications.showHelpDot() ? html` { + const inner = el.querySelector("button"); if ((el as HTMLElement).dataset.page === pageId) { el.classList.add("active"); + inner?.classList.add("active"); } else { el.classList.remove("active"); + inner?.classList.remove("active"); } }); } + private _renderDot(color: string): TemplateResult { + return html` + + + `; + } + render() { window.currentPageId ??= "page-play"; const currentPage = window.currentPageId; @@ -120,26 +133,36 @@ export class MobileNavBar extends LitElement { data-page="page-play" data-i18n="main.play" > - + @click=${this._notifications.onNewsClick} + > + + ${this._notifications.showNewsDot() + ? this._renderDot("bg-red-500") + : ""} +
-
+ - + @click=${this._notifications.onHelpClick} + > + + ${this._notifications.showHelpDot() + ? this._renderDot("bg-yellow-400") + : ""} +
diff --git a/src/client/components/NavNotificationsController.ts b/src/client/components/NavNotificationsController.ts new file mode 100644 index 000000000..1e5937a07 --- /dev/null +++ b/src/client/components/NavNotificationsController.ts @@ -0,0 +1,91 @@ +import { ReactiveController, ReactiveControllerHost } from "lit"; +import version from "resources/version.txt?raw"; +import { getCosmeticsHash } from "../Cosmetics"; +import { getGamesPlayed } from "../Utils"; + +const HELP_SEEN_KEY = "helpSeen"; +const STORE_SEEN_HASH_KEY = "storeSeenHash"; +const NEWS_SEEN_VERSION_KEY = "newsSeenVersion"; + +export class NavNotificationsController implements ReactiveController { + private host: ReactiveControllerHost; + + private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true"; + private _hasNewCosmetics = false; + private _hasNewVersion = false; + + private get normalizedVersion(): string { + const trimmed = version.trim(); + return trimmed.startsWith("v") ? trimmed : `v${trimmed}`; + } + + constructor(host: ReactiveControllerHost) { + this.host = host; + host.addController(this); + } + + hostConnected(): void { + // Check if cosmetics have changed + getCosmeticsHash() + .then((hash: string | null) => { + const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY); + this._hasNewCosmetics = hash !== null && hash !== seenHash; + this.host.requestUpdate(); + }) + .catch(() => {}); + + // Check if version has changed + const currentVersion = this.normalizedVersion; + const seenVersion = localStorage.getItem(NEWS_SEEN_VERSION_KEY); + this._hasNewVersion = + seenVersion !== null && seenVersion !== currentVersion; + if (seenVersion === null) { + localStorage.setItem(NEWS_SEEN_VERSION_KEY, currentVersion); + } + } + + hostDisconnected(): void {} + + // Only show one dot at a time to prevent + // overwhelming users. Priority: News > Store > Help. + showNewsDot(): boolean { + return this._hasNewVersion; + } + + showStoreDot(): boolean { + return this._hasNewCosmetics && !this.showNewsDot(); + } + + showHelpDot(): boolean { + return ( + getGamesPlayed() < 10 && + !this._helpSeen && + !this.showNewsDot() && + !this.showStoreDot() + ); + } + + onNewsClick = (): void => { + this._hasNewVersion = false; + localStorage.setItem(NEWS_SEEN_VERSION_KEY, this.normalizedVersion); + this.host.requestUpdate(); + }; + + onStoreClick = (): void => { + this._hasNewCosmetics = false; + getCosmeticsHash() + .then((hash: string | null) => { + if (hash !== null) { + localStorage.setItem(STORE_SEEN_HASH_KEY, hash); + } + }) + .catch(() => {}); + this.host.requestUpdate(); + }; + + onHelpClick = (): void => { + localStorage.setItem(HELP_SEEN_KEY, "true"); + this._helpSeen = true; + this.host.requestUpdate(); + }; +}