feat(preferences): section langues a telecharger (defaut fr+en), transcripts filtres cote serveur et watch
CI / build-and-test (push) Successful in 13m50s
CI / build-and-test (push) Successful in 13m50s
This commit is contained in:
@@ -42,15 +42,88 @@
|
||||
}
|
||||
|
||||
@if (hasQuery()) {
|
||||
<div class="my-4 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-slate-300" for="sortSelect">Trier par</label>
|
||||
<select id="sortSelect" class="bg-slate-900 text-slate-100 border border-slate-700 rounded px-2 py-1 text-sm"
|
||||
[value]="sortParam()" (change)="onSortChange($event)">
|
||||
<option value="relevance">Pertinence</option>
|
||||
<option value="date">Date</option>
|
||||
<option value="views">Vues</option>
|
||||
</select>
|
||||
<!-- Pills de type façon directory Twitch : Directs / Vidéos / Clips / Chaînes -->
|
||||
<div class="flex items-center gap-2 overflow-x-auto pb-1" role="tablist" aria-label="Filtrer par type de contenu">
|
||||
<button class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'all' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'all'" (click)="setTypePill('all')">
|
||||
Tout ({{ typeCounts().all }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().live > 0 || typePill() === 'live'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'live' ? 'bg-purple-600 text-white border-purple-500' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'live'" (click)="setTypePill('live')">
|
||||
Directs ({{ typeCounts().live }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().video > 0 || typePill() === 'video'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'video' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'video'" (click)="setTypePill('video')">
|
||||
Vidéos ({{ typeCounts().video }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().clip > 0 || typePill() === 'short'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'short' ? 'bg-red-600 text-white border-red-500' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'short'" (click)="setTypePill('short')">
|
||||
Clips ({{ typeCounts().clip }})
|
||||
</button>
|
||||
<button *ngIf="typeCounts().channel > 0 || typePill() === 'channel'" class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
||||
[ngClass]="typePill() === 'channel' ? 'bg-slate-200 text-slate-900 border-slate-200' : 'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700'"
|
||||
[attr.aria-pressed]="typePill() === 'channel'" (click)="setTypePill('channel')">
|
||||
Chaînes ({{ typeCounts().channel }})
|
||||
</button>
|
||||
</div>
|
||||
<!-- Barre de filtrage (même UX que la page thème / fournisseur) -->
|
||||
<div class="bg-slate-800/50 p-4 rounded-xl border border-slate-700 my-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div>
|
||||
<label for="sortSelect" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.sort' | t }}</label>
|
||||
<select id="sortSelect" class="w-full bg-slate-700 text-white rounded-md px-3 py-2 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
[value]="sortParam()" (change)="onSortChange($event)">
|
||||
<option value="relevance">{{ 'filter.sort.relevance' | t }}</option>
|
||||
<option value="date">{{ 'filter.sort.date' | t }}</option>
|
||||
<option value="views">{{ 'filter.sort.views' | t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="duration-filter" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.duration' | t }}</label>
|
||||
<select id="duration-filter" class="w-full bg-slate-700 text-white rounded-md px-3 py-2 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
[value]="durationFilter()" (change)="onDurationChange($event)">
|
||||
<option value="any">{{ 'filter.duration.any' | t }}</option>
|
||||
<option value="short">{{ 'filter.duration.short' | t }}</option>
|
||||
<option value="medium">{{ 'filter.duration.medium' | t }}</option>
|
||||
<option value="long">{{ 'filter.duration.long' | t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="type-filter" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.type' | t }}</label>
|
||||
<select id="type-filter" class="w-full bg-slate-700 text-white rounded-md px-3 py-2 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
[value]="typeFilter()" (change)="onTypeChange($event)">
|
||||
<option value="any">{{ 'filter.type.any' | t }}</option>
|
||||
<option value="video">{{ 'filter.type.video' | t }}</option>
|
||||
<option value="live">{{ 'filter.type.live' | t }}</option>
|
||||
<option value="recorded">{{ 'filter.type.recorded' | t }}</option>
|
||||
<option value="short">{{ 'filter.type.short' | t }}</option>
|
||||
<option value="channel">{{ 'filter.type.channel' | t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="language-filter" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.language' | t }}</label>
|
||||
<select id="language-filter" class="w-full bg-slate-700 text-white rounded-md px-3 py-2 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
[value]="languageFilter()" (change)="onLanguageChange($event)">
|
||||
<option value="any">{{ 'filter.language.any' | t }}</option>
|
||||
<option value="en">{{ 'filter.language.en' | t }}</option>
|
||||
<option value="fr">{{ 'filter.language.fr' | t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="date-filter" class="block text-sm font-medium text-slate-300 mb-1">{{ 'filter.date' | t }}</label>
|
||||
<select id="date-filter" class="w-full bg-slate-700 text-white rounded-md px-3 py-2 border border-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
[value]="dateFilter()" (change)="onDateChange($event)">
|
||||
<option value="any">{{ 'filter.date.any' | t }}</option>
|
||||
<option value="day">{{ 'filter.date.day' | t }}</option>
|
||||
<option value="week">{{ 'filter.date.week' | t }}</option>
|
||||
<option value="month">{{ 'filter.date.month' | t }}</option>
|
||||
<option value="year">{{ 'filter.date.year' | t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,17 +134,19 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-search-result-grid [videos]="filteredResults()" [loading]="loading()"></app-search-result-grid>
|
||||
|
||||
<!-- Infinite scroll anchor & indicators -->
|
||||
<app-search-result-grid [videos]="filteredResults()" [loading]="loading()" [loadingMore]="loadingMore()"></app-search-result-grid>
|
||||
|
||||
<!-- Standard infinite scroll: shared sentinel + bottom loader + end state -->
|
||||
<div class="flex flex-col items-center justify-center py-6">
|
||||
@if (loading()) {
|
||||
@if (loading() || loadingMore()) {
|
||||
<div class="text-slate-400 text-sm">Chargement…</div>
|
||||
}
|
||||
@if (endReached()) {
|
||||
@if (!loading() && !loadingMore() && endReached() && filteredResults().length > 0) {
|
||||
<div class="text-slate-500 text-xs">Fin des résultats</div>
|
||||
}
|
||||
</div>
|
||||
<div #infiniteScrollAnchor class="h-1 w-full"></div>
|
||||
<app-infinite-anchor [disabled]="endReached() || !hasQuery()"
|
||||
[busy]="loading() || loadingMore()"
|
||||
(loadMore)="loadNextPage()"></app-infinite-anchor>
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user