feat: update Angular cache with latest TypeScript definitions

This commit is contained in:
2025-09-22 22:17:38 -04:00
parent d78afda4cd
commit d372b7d509
27 changed files with 512 additions and 646 deletions
+20 -177
View File
@@ -1,7 +1,18 @@
<div class="container mx-auto p-4 sm:p-6">
<header class="mb-4">
<h1 class="text-xl sm:text-2xl font-semibold text-slate-100">{{ pageHeading() }}</h1>
<p class="text-sm text-white/60 mt-1">{{ activeProvidersDisplay() }}</p>
<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()) {
@@ -30,38 +41,6 @@
</div>
}
<!-- Unified per-provider sections -->
@if (showUnified()) {
<section *ngFor="let p of activeProviders()">
<div class="sticky top-0 z-10 flex items-center justify-between bg-black/50 backdrop-blur px-4 py-2 border-b border-white/10">
<h2 class="text-sm font-medium">{{ providerDisplay(p) }} <span class="text-white/40" *ngIf="groups()[p]">({{ groups()[p].length || 0 }})</span></h2>
<button class="inline-flex items-center rounded-xl px-3 py-1.5 text-xs border border-white/20 hover:bg-white/5"
(click)="onViewAll(p)"
aria-label="Voir tout sur {{ providerDisplay(p) }}">
Voir tout sur {{ providerDisplay(p) }}
</button>
</div>
<div *ngIf="groups()[p]?.length; else noResTpl" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 p-4">
<a *ngFor="let v of groups()[p]"
class="group block rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition overflow-hidden"
[href]="v.url || '#'" target="_blank" rel="noopener noreferrer"
[attr.aria-label]="v.title">
<div class="relative">
<img [src]="v.thumbnail" [alt]="v.title" class="w-full h-44 object-cover" loading="lazy">
</div>
<div class="p-3">
<div class="text-sm font-medium text-slate-100 line-clamp-2">{{ v.title }}</div>
<div class="mt-1 text-xs text-white/60 line-clamp-1">{{ v.uploaderName }}</div>
</div>
</a>
</div>
<ng-template #noResTpl>
<div class="px-4 py-6 text-white/50 text-sm">Aucun résultat pour ce fournisseur.</div>
</ng-template>
</section>
}
@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">
@@ -80,150 +59,14 @@
<button class="px-3 py-1 rounded bg-slate-700 hover:bg-slate-600 text-slate-100" (click)="nextPage()">Suivant</button>
</div>
</div>
}
@if (showUnified() && !loading() && unifiedTotal() === 0 && hasQuery()) {
<p class="text-slate-400">Aucun résultat pour « {{ q() }} » avec les fournisseurs sélectionnés.</p>
}
@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>
}
@if (loading()) {
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@for (item of [1,2,3,4,5,6,7,8]; track item) {
<div class="animate-pulse bg-slate-800 rounded-lg overflow-hidden">
<div class="w-full h-48 bg-slate-700"></div>
<div class="p-4 space-y-3">
<div class="h-4 bg-slate-700 rounded w-3/4"></div>
<div class="h-4 bg-slate-700 rounded w-1/2"></div>
</div>
</div>
}
</div>
} @else if (selectedProviderForView() === 'twitch') {
<!-- Twitch: two sections -->
<div class="space-y-10">
<!-- Live Channels -->
<section *ngIf="filterTag() === 'twitch_all' || filterTag() === 'twitch_live'">
<h3 class="text-xl font-semibold text-slate-200 mb-3">Chaînes en direct</h3>
@if (twitchChannels().length > 0) {
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-6 gap-y-8">
@for (video of twitchChannels(); track video.videoId) {
<a [routerLink]="['/watch', video.videoId]" [queryParams]="watchQueryParams(video)" [state]="{ video }" class="group flex flex-col bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50 transition-all duration-300 transform hover:-translate-y-1">
<div class="relative">
<img [src]="video.thumbnail" [alt]="video.title" class="w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105">
<div class="absolute top-2 left-2 bg-black/75 text-white text-xs px-2 py-1 rounded">
{{ formatViews(video.views) }} en direct
</div>
<div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="'twitch'" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
<app-add-to-playlist class="ml-2" [provider]="'twitch'" [videoId]="video.videoId" [title]="video.title" [thumbnail]="video.thumbnail"></app-add-to-playlist>
</div>
</div>
<div class="p-4 flex-grow flex flex-col">
<h4 class="font-semibold text-slate-100 group-hover:text-red-400 transition-colors duration-200 line-clamp-2">{{ video.title }}</h4>
<div class="mt-2 flex items-center space-x-3 text-sm text-slate-400">
<img [src]="video.uploaderAvatar" [alt]="video.uploaderName" class="w-8 h-8 rounded-full">
<span>{{ video.uploaderName }}</span>
</div>
</div>
</a>
}
</div>
@if (twitchCursorChannels()) {
<div class="mt-4 flex justify-center">
<button class="px-4 py-2 rounded bg-slate-700 hover:bg-slate-600 text-slate-100 disabled:opacity-50" (click)="loadMoreTwitchChannels()" [disabled]="twitchBusyChannels()">Afficher plus</button>
</div>
}
} @else {
<p class="text-slate-400">Aucune chaîne en direct trouvée.</p>
}
</section>
<!-- Past Videos (VODs) -->
<section *ngIf="filterTag() === 'twitch_all' || filterTag() === 'twitch_vod'">
<h3 class="text-xl font-semibold text-slate-200 mb-3">Vidéos (VOD)</h3>
@if (twitchVods().length > 0) {
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-6 gap-y-8">
@for (video of twitchVods(); track video.videoId) {
<a [routerLink]="['/watch', video.videoId]" [queryParams]="watchQueryParams(video)" [state]="{ video }" class="group flex flex-col bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50 transition-all duration-300 transform hover:-translate-y-1">
<div class="relative">
<img [src]="video.thumbnail" [alt]="video.title" class="w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105">
<div class="absolute bottom-2 right-2 bg-black/75 text-white text-xs px-2 py-1 rounded">
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div>
<div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="'twitch'" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
<app-add-to-playlist class="ml-2" [provider]="'twitch'" [videoId]="video.videoId" [title]="video.title" [thumbnail]="video.thumbnail"></app-add-to-playlist>
</div>
</div>
<div class="p-4 flex-grow flex flex-col">
<h4 class="font-semibold text-slate-100 group-hover:text-red-400 transition-colors duration-200 line-clamp-2">{{ video.title }}</h4>
<div class="mt-2 flex items-center space-x-3 text-sm text-slate-400">
<img [src]="video.uploaderAvatar" [alt]="video.uploaderName" class="w-8 h-8 rounded-full">
<span>{{ video.uploaderName }}</span>
</div>
<div class="mt-auto pt-2 text-sm text-slate-400">
<span>{{ formatViews(video.views) }} visionnements</span> &bull; <span>{{ formatRelative(video.uploadedDate) }}</span>
</div>
</div>
</a>
}
</div>
@if (twitchCursorVods()) {
<div class="mt-4 flex justify-center">
<button class="px-4 py-2 rounded bg-slate-700 hover:bg-slate-600 text-slate-100 disabled:opacity-50" (click)="loadMoreTwitchVods()" [disabled]="twitchBusyVods()">Afficher plus</button>
</div>
}
} @else {
<p class="text-slate-400">Aucune vidéo VOD trouvée.</p>
}
</section>
</div>
} @else if (results().length > 0) {
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-6 gap-y-8">
@for (video of filteredResults(); track video.videoId) {
<a [routerLink]="['/watch', video.videoId]" [queryParams]="watchQueryParams(video)" [state]="{ video }" class="group flex flex-col bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50 transition-all duration-300 transform hover:-translate-y-1">
<div class="relative">
<img [src]="video.thumbnail" [alt]="video.title" class="w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105">
<div class="absolute bottom-2 right-2 bg-black/75 text-white text-xs px-2 py-1 rounded">
{{ video.duration / 60 | number:'1.0-0' }}:{{ (video.duration % 60) | number:'2.0-0' }}
</div>
<div class="absolute bottom-2 left-2">
<app-like-button [videoId]="video.videoId" [provider]="selectedProviderForView()" [title]="video.title" [thumbnail]="video.thumbnail"></app-like-button>
<app-add-to-playlist class="ml-2" [provider]="selectedProviderForView()" [videoId]="video.videoId" [title]="video.title" [thumbnail]="video.thumbnail"></app-add-to-playlist>
</div>
</div>
<div class="p-4 flex-grow flex flex-col">
<h3 class="font-semibold text-slate-100 group-hover:text-red-400 transition-colors duration-200 line-clamp-2">{{ video.title }}</h3>
<div class="mt-2 flex items-center space-x-3 text-sm text-slate-400">
<img [src]="video.uploaderAvatar" [alt]="video.uploaderName" class="w-8 h-8 rounded-full">
<span>{{ video.uploaderName }}</span>
</div>
<div class="mt-auto pt-2 text-sm text-slate-400">
<span>{{ formatViews(video.views) }} visionnements</span> &bull; <span>{{ formatRelative(video.uploadedDate) }}</span>
</div>
</div>
</a>
}
</div>
<!-- Infinite scroll anchor -->
<app-infinite-anchor class="mt-6"
[disabled]="!nextCursor()"
[busy]="busyMore()"
(loadMore)="fetchNextPage()"></app-infinite-anchor>
@if (busyMore()) {
<div class="flex items-center justify-center py-4 text-slate-400">
<svg class="animate-spin h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
{{ 'loading.more' | t }}
@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>
}
} @else if (hasQuery()) {
<p class="text-slate-400">{{ 'search.noResults' | t }}</p>
<app-search-result-grid [videos]="filteredResults()" [loading]="loading()"></app-search-result-grid>
}
</div>