fix(search): loading par frame sur les cartes, plus de cadres vides
CI / build-and-test (push) Successful in 14m8s
CI / build-and-test (push) Successful in 14m8s
- video-card: etat de chargement de la miniature (shimmer + spinner), fallback 'Apertu indisponible' avec retry, referrerpolicy no-referrer - grid: eager sur les premieres cartes, bandeau de rafraichissement quand la grille est deja peuplee - search: garde MIN_QUERY_LENGTH=2 (le loading partait pour une requete que request$ filtrait, grille figee sur des skeletons) + filet de securite 20s - skeleton: ligne chaine pour coller a la hauteur reelle de la carte
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, Input, OnChanges, SimpleChanges, signal } from '@angular/core';
|
||||
import { VideoItem } from '../../models/video-item.model';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
@@ -15,8 +15,57 @@ import type { ChannelMeta, ProviderId } from '../../models/channel.model';
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, DurationPipe, ProviderBadgeComponent, SubscribeButtonComponent, ChannelIdentityComponent],
|
||||
})
|
||||
export class VideoCardComponent {
|
||||
export class VideoCardComponent implements OnChanges {
|
||||
@Input() video!: VideoItem;
|
||||
/** Cartes du haut de la grille : chargement immédiat (lazy désactivé). */
|
||||
@Input() eager = false;
|
||||
|
||||
/**
|
||||
* État de chargement de la frame (miniature). Tant que l'image n'a pas
|
||||
* répondu, la frame affiche un shimmer + spinner au lieu d'un rectangle vide.
|
||||
*/
|
||||
readonly thumbSrc = signal<string | null>(null);
|
||||
readonly thumbLoaded = signal(false);
|
||||
readonly thumbFailed = signal(false);
|
||||
|
||||
ngOnChanges(_changes: SimpleChanges): void {
|
||||
this.syncThumb();
|
||||
}
|
||||
|
||||
private syncThumb(): void {
|
||||
const raw = (this.video as any)?.thumbnailUrl;
|
||||
const url = typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : null;
|
||||
if (url === this.thumbSrc()) {
|
||||
// Meme source : le navigateur ne rejouera pas `load`, on garde l'etat
|
||||
// courant (sinon le spinner resterait bloque indefiniment).
|
||||
this.thumbFailed.set(!url);
|
||||
return;
|
||||
}
|
||||
this.thumbSrc.set(url);
|
||||
this.thumbFailed.set(!url);
|
||||
this.thumbLoaded.set(false);
|
||||
}
|
||||
|
||||
onThumbLoad(): void {
|
||||
this.thumbLoaded.set(true);
|
||||
this.thumbFailed.set(false);
|
||||
}
|
||||
|
||||
onThumbError(): void {
|
||||
this.thumbLoaded.set(false);
|
||||
this.thumbFailed.set(true);
|
||||
}
|
||||
|
||||
/** Relance l'image après un échec (hotlink bloqué / 403 / réseau). */
|
||||
retryThumb(): void {
|
||||
const url = this.thumbSrc();
|
||||
if (!url) return;
|
||||
this.thumbFailed.set(false);
|
||||
this.thumbLoaded.set(false);
|
||||
this.thumbSrc.set(null);
|
||||
// Relance après un tick : le <img> est réellement recréé par le binding.
|
||||
setTimeout(() => this.thumbSrc.set(url), 0);
|
||||
}
|
||||
|
||||
buildQueryParams(v: VideoItem): Record<string, any> {
|
||||
const qp: any = { p: v.provider };
|
||||
|
||||
Reference in New Issue
Block a user