153 lines
9.0 KiB
HTML
153 lines
9.0 KiB
HTML
<div class="container mx-auto p-4 sm:p-6">
|
|
<header class="mb-6">
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-white mb-2">{{ pageHeading() }}</h1>
|
|
|
|
<div class="flex flex-wrap gap-2 mt-3">
|
|
@for (provider of activeProvidersInfo(); track provider.id) {
|
|
<div class="flex items-center gap-2 rounded px-2 py-1 text-xs font-medium border"
|
|
[ngStyle]="getProviderColors(provider.id)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
|
</svg>
|
|
{{ provider.label }}
|
|
</div>
|
|
}
|
|
</div>
|
|
</header>
|
|
|
|
@if (notice()) {
|
|
<div class="mb-4 bg-amber-900/40 border border-amber-600 text-amber-200 p-3 rounded">
|
|
{{ notice() }}
|
|
</div>
|
|
}
|
|
|
|
@if (!hasQuery()) {
|
|
<p class="text-slate-400">{{ 'search.hint' | t }}</p>
|
|
}
|
|
|
|
@if (hasQuery() && availableTags().length > 0) {
|
|
<div class="mb-4 flex items-center gap-2 overflow-x-auto pb-1">
|
|
@for (tag of availableTags(); track tag.key) {
|
|
<button
|
|
class="px-3 py-1 rounded-full text-sm whitespace-nowrap border transition-colors"
|
|
[ngClass]="{
|
|
'bg-slate-200 text-slate-900 border-slate-200': filterTag() === tag.key,
|
|
'bg-slate-800 text-slate-200 border-slate-600 hover:bg-slate-700': filterTag() !== tag.key
|
|
}"
|
|
(click)="setFilterTag(tag.key)">
|
|
{{ tag.label }}
|
|
</button>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (hasQuery()) {
|
|
<!-- 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>
|
|
|
|
@if (error()) {
|
|
<div class="mb-4 bg-red-900/40 border border-red-600 text-red-200 p-3 rounded flex items-center justify-between">
|
|
<span>{{ error() }}</span>
|
|
<button class="px-3 py-1 rounded bg-red-600 hover:bg-red-500 text-white text-sm" (click)="onSearchBarSubmit({ q: q(), providers: providersListParam() || 'all' })">Réessayer</button>
|
|
</div>
|
|
}
|
|
|
|
<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() || loadingMore()) {
|
|
<div class="text-slate-400 text-sm">Chargement…</div>
|
|
}
|
|
@if (!loading() && !loadingMore() && endReached() && filteredResults().length > 0) {
|
|
<div class="text-slate-500 text-xs">Fin des résultats</div>
|
|
}
|
|
</div>
|
|
<app-infinite-anchor [disabled]="endReached() || !hasQuery()"
|
|
[busy]="loading() || loadingMore()"
|
|
(loadMore)="loadNextPage()"></app-infinite-anchor>
|
|
}
|
|
</div>
|