diff --git a/src/components/search/search.component.html b/src/components/search/search.component.html index 4ecc1b5..2665008 100644 --- a/src/components/search/search.component.html +++ b/src/components/search/search.component.html @@ -1,28 +1,31 @@
- +
+
-

{{ pageHeading() }}

+

+ {{ pageHeading() }} +

@if (hasQuery()) { - - {{ filteredResults().length }} résultat{{ filteredResults().length === 1 ? '' : 's' }} + + {{ countLabel(filteredResults().length) }} résultat{{ filteredResults().length === 1 ? '' : 's' }} } @if (hasQuery()) {
@if (notice()) { -
- {{ notice() }} +
+ + {{ notice() }}
} @if (!hasQuery()) { -

{{ 'search.hint' | t }}

+

{{ 'search.hint' | t }}

} @if (hasQuery()) { - -
- - - - - -
+ +
+ +
+
+ + + + + +
+
- -
- Sources -
- @for (chip of providerChips(); track chip.id) { - - } - @if (!allProvidersVisible()) { - - } - @if (!allSourcesActive()) { - - } +
+ + +
+ Sources +
+
+ @for (chip of providerChips(); track chip.id) { + + } + @if (!allProvidersVisible()) { + + } + @if (!allSourcesActive()) { + + } +
+
@if (filtersOpen()) { -
+
@if (activeFilterChips().length > 0) { -
- Actifs +
+ Actifs @for (chip of activeFilterChips(); track chip.key) { } - - {{ filteredResults().length }} / {{ scopedVideos().length }} résultats + + {{ countLabel(filteredResults().length) }} / {{ countLabel(scopedVideos().length) }}
}
- - +
+ + + +
- - +
+ + + +
- - +
+ + + +
- - +
+ + + +
- - - - - {{ 'filters.serverHint' | t: 'Type, période et durée sont appliqués par les sources ; la langue filtre les résultats affichés.' }} - +
+ + + +
+ +

+ {{ 'filters.serverHint' | t: 'Type, période et durée sont appliqués par les sources ; la langue filtre les résultats affichés.' }} +

} - @if (error()) { -
- {{ error() }} - -
- } + @if (error()) { +
+ + + {{ error() }} + + +
+ } diff --git a/src/components/search/search.component.ts b/src/components/search/search.component.ts index 482bc2c..19aec8a 100644 --- a/src/components/search/search.component.ts +++ b/src/components/search/search.component.ts @@ -30,6 +30,29 @@ const LOADING_TIMEOUT_MS = 20_000; const STORAGE_HIDDEN_PROVIDERS = 'newtube:search.hiddenProviders'; const STORAGE_FILTERS_OPEN = 'newtube:search.filtersOpen'; +/** + * Couleurs de marque, portées par un simple point dans la pastille. Le fond et + * le texte du bouton restent neutres : l'en-tête suit alors le thème (clair + * compris) au lieu d'imposer des teintes qui ne contrastent plus. + */ +const PROVIDER_DOTS: Record = { + yt: '#ef4444', + dm: '#3b82f6', + tw: '#a855f7', + pt: '#22c55e', + od: '#eab308', + ru: '#f97316', +}; + +/** 1 284 -> « 1.3K » : garde les pastilles étroites sans perdre l'info. */ +function formatCount(n: number): string { + const v = Number(n); + if (!Number.isFinite(v) || v <= 0) return '0'; + if (v < 1000) return String(v); + if (v < 1_000_000) return `${(v / 1000).toFixed(v < 10_000 ? 1 : 0).replace('.0', '')}K`; + return `${(v / 1_000_000).toFixed(1).replace('.0', '')}M`; +} + @Component({ selector: 'app-search', standalone: true, @@ -221,16 +244,24 @@ export class SearchComponent { const hidden = this.hiddenProviders(); return this.activeProviders().map(id => { const spec = PROVIDERS.find(p => p.id === id); + const count = counts.get(id) || 0; return { id, shortLabel: spec?.shortLabel || id.toUpperCase(), displayName: spec?.displayName || id, - count: counts.get(id) || 0, + // Couleur de marque portée par le point, pas par le fond du bouton : + // les fonds neutres restent lisibles dans tous les thèmes (clair inclus). + dot: PROVIDER_DOTS[id] || 'rgb(148, 163, 184)', + count, + countLabel: formatCount(count), visible: !hidden.includes(id), }; }); }); + /** Nombre de sources masquées (alimente le libellé « Tout afficher »). */ + hiddenProviderCount = computed(() => this.hiddenProviders().length); + /** Vrai quand toutes les sources connues sont déjà interrogées. */ allSourcesActive = computed(() => this.activeProviders().length >= PROVIDERS.length); @@ -246,37 +277,15 @@ export class SearchComponent { }); } - /** Style de pastille : couleurs de la marque quand visible, neutre quand masquée. */ - providerChipStyle(id: ProviderId, visible: boolean): { [key: string]: string } { - if (!visible) { - return { - backgroundColor: 'transparent', - color: 'rgb(113, 113, 122)', - borderColor: 'rgb(63, 63, 70)', - }; - } - return this.getProviderColors(this.providerIdMap[id]); - } - - // Brand colors for provider badge (inline styles to match History badges) - getProviderColors(providerId: string | null | undefined): { [key: string]: string } { - const id = (providerId || '').toLowerCase(); - switch (id) { - case 'youtube': - return { backgroundColor: 'rgba(220, 38, 38, 0.15)', color: 'rgb(248, 113, 113)', borderColor: 'rgba(239, 68, 68, 0.3)' }; - case 'dailymotion': - return { backgroundColor: 'rgba(37, 99, 235, 0.15)', color: 'rgb(147, 197, 253)', borderColor: 'rgba(59, 130, 246, 0.3)' }; - case 'peertube': - return { backgroundColor: 'rgba(245, 158, 11, 0.15)', color: 'rgb(252, 211, 77)', borderColor: 'rgba(245, 158, 11, 0.3)' }; - case 'rumble': - return { backgroundColor: 'rgba(22, 163, 74, 0.15)', color: 'rgb(134, 239, 172)', borderColor: 'rgba(34, 197, 94, 0.3)' }; - case 'twitch': - return { backgroundColor: 'rgba(168, 85, 247, 0.15)', color: 'rgb(216, 180, 254)', borderColor: 'rgba(168, 85, 247, 0.3)' }; - case 'odysee': - return { backgroundColor: 'rgba(236, 72, 153, 0.15)', color: 'rgb(251, 207, 232)', borderColor: 'rgba(236, 72, 153, 0.3)' }; - default: - return { backgroundColor: 'rgba(30, 41, 59, 0.8)', color: 'rgb(203, 213, 225)', borderColor: 'rgb(51, 65, 85)' }; - } + /** + * Point de la pastille source : plein quand la source est affichée, creux + * (anneau) quand elle est masquée. L'identité de la source reste lisible + * dans les deux états, et le fond du bouton reste neutre (donc thémable). + */ + providerDotStyle(chip: { dot: string; visible: boolean }): { [key: string]: string } { + return chip.visible + ? { backgroundColor: chip.dot } + : { backgroundColor: 'transparent', boxShadow: `inset 0 0 0 1.5px ${chip.dot}` }; } // Merge groups with dedup by id/videoId/url @@ -621,6 +630,11 @@ export class SearchComponent { return { live, video, clip, channel, all: (list || []).length }; }); + /** Nombre compact pour les compteurs de l'en-tête (1 284 -> « 1,3 k »). */ + countLabel(n: number): string { + return formatCount(n); + } + /** Pills Tous / Directs / Vidéos / Clips / Chaînes synchronisées avec typeFilter. */ typePill = computed<'all' | 'live' | 'video' | 'shorts' | 'channel'>(() => { const t = this.filters().type; @@ -821,13 +835,6 @@ export class SearchComponent { } - formatViews(views: number): string { - if (views >= 1_000_000_000) return (views / 1_000_000_000).toFixed(1) + 'B'; - if (views >= 1_000_000) return (views / 1_000_000).toFixed(1) + 'M'; - if (views >= 1_000) return (views / 1_000).toFixed(1) + 'K'; - return views.toString(); - } - formatRelative(dateIso?: string): string { if (!dateIso) return ''; return formatRelativeFr(dateIso);