From ccf63dcea0bb6a445ff2976334e0518cf8fc1492 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sat, 26 Sep 2026 10:07:16 -0400 Subject: [PATCH] feat(themes): interrupteur global bande des themes + masquee sur watch - ThemesService: signal barVisible persiste (localStorage), toggle global - header: bouton oeil afficher/masquer la bande (aria-pressed, FR/EN) - app: affichage = page autorisee ET interrupteur; allowlist stricte (accueil + /t/... uniquement, chemin normalise) -> jamais sur /watch - i18n: cles themes.showBar/hideBar FR/EN --- src/app.component.ts | 14 ++++++++++---- src/components/header/header.component.html | 11 +++++++++++ src/components/header/header.component.ts | 6 ++++++ src/services/i18n.service.ts | 4 ++++ src/services/themes.service.ts | 19 +++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/app.component.ts b/src/app.component.ts index c4cc95a..55e88fa 100644 --- a/src/app.component.ts +++ b/src/app.component.ts @@ -6,6 +6,7 @@ import { SidebarComponent } from './components/sidebar/sidebar.component'; import { ThemesNavComponent } from './components/themes/themes-nav.component'; import { I18nService } from './services/i18n.service'; import { AuthService } from './services/auth.service'; +import { ThemesService } from './services/themes.service'; @Component({ selector: 'app-root', @@ -18,6 +19,7 @@ export class AppComponent { private i18n = inject(I18nService); private auth = inject(AuthService); private router = inject(Router); + private themesSvc = inject(ThemesService); sidebarOpen = signal(false); sidebarCollapsed = signal(false); private _showThemesBar = signal(false); @@ -59,9 +61,12 @@ export class AppComponent { private updateShowThemesBar() { try { - const url = this.router.url || '/'; - const isHome = url === '/' || url.startsWith('/?'); - const isTheme = url.startsWith('/t/') || /^\/p\/[^/]+\/t\//.test(url); + // Liste blanche stricte : accueil + pages de thèmes uniquement. + // /watch, /search, /shorts, /library, /channel... : jamais de bande. + const raw = this.router.url || '/'; + const pathOnly = raw.split('?')[0].split('#')[0] || '/'; + const isHome = pathOnly === '/' || pathOnly === ''; + const isTheme = pathOnly.startsWith('/t/') || /^\/p\/[^/]+\/t\//.test(pathOnly); this._showThemesBar.set(isHome || isTheme); } catch { this._showThemesBar.set(false); @@ -69,5 +74,6 @@ export class AppComponent { } // Template helpers - showThemesBar() { return this._showThemesBar(); } + // Visible = page autorisée ET interrupteur global du header (persisté). + showThemesBar() { return this._showThemesBar() && this.themesSvc.barVisible(); } } diff --git a/src/components/header/header.component.html b/src/components/header/header.component.html index 8a00b91..5b00cd6 100644 --- a/src/components/header/header.component.html +++ b/src/components/header/header.component.html @@ -21,6 +21,17 @@
+ +
((() => { diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts index 93427f6..9a998d8 100644 --- a/src/services/i18n.service.ts +++ b/src/services/i18n.service.ts @@ -74,6 +74,8 @@ const EN: Record = { 'themes.title': 'Themes', 'themes.show': 'Show', 'themes.hide': 'Hide', + 'themes.showBar': 'Show the themes bar', + 'themes.hideBar': 'Hide the themes bar', // Common 'common.error': 'An error occurred.', 'empty.noItems': 'No items.', @@ -210,6 +212,8 @@ const FR: Record = { 'themes.title': 'Thèmes', 'themes.show': 'Afficher', 'themes.hide': 'Masquer', + 'themes.showBar': 'Afficher la bande des thèmes', + 'themes.hideBar': 'Masquer la bande des thèmes', // Common 'common.error': 'Une erreur est survenue.', 'empty.noItems': 'Aucun élément.', diff --git a/src/services/themes.service.ts b/src/services/themes.service.ts index d3caba5..69a1b2f 100644 --- a/src/services/themes.service.ts +++ b/src/services/themes.service.ts @@ -16,6 +16,25 @@ export class ThemesService { // Controls whether the themes nav should show all themes or only a subset (e.g., 8) readonly showAll = signal(false); + /** + * Interrupteur global de la bande des thèmes (bouton du header). + * Persisté : '0' = masquée partout, toute autre valeur = visible + * (sous réserve des règles de route : jamais sur /watch, etc.). + */ + private static readonly BAR_VISIBLE_KEY = 'newtube.themesBarVisible'; + readonly barVisible = signal((() => { + try { return localStorage.getItem(ThemesService.BAR_VISIBLE_KEY) !== '0'; } catch { return true; } + })()); + + setBarVisible(v: boolean): void { + this.barVisible.set(!!v); + try { localStorage.setItem(ThemesService.BAR_VISIBLE_KEY, v ? '1' : '0'); } catch {} + } + + toggleBarVisible(): void { + this.setBarVisible(!this.barVisible()); + } + constructor(private i18n: I18nService) {} themes() { return this.list(); }