Files
NewTube/src/components/themes/theme-page.component.html
T
bruno 579354cdb3
CI / build-and-test (push) Successful in 13m25s
feat(channel): identite chaine partagee cliquable partout + logos fiabilises
- Nouveau app-channel-identity (avatar+nom -> /channel, fallback resolve /api/channels, initiales sinon)
- ProviderBadge: accepte les ids courts yt/dm/tw/pt/od/ru
- Search adapters yt/dm/pt/od: conservent channelExternalId/channelAvatarUrl (pt derive instance|channel)
- video-card/themes/watch/related/suggestions/subscriptions/channel-header uniformises sur l'identite partagee
2026-09-25 10:37:36 -04:00

86 lines
4.7 KiB
HTML

<div class="container mx-auto p-4 sm:p-6">
<h2 class="text-3xl font-bold mb-4 text-slate-100 border-l-4 border-red-500 pl-4 flex items-center gap-2">
<span>{{ themeLabel() }}</span>
</h2>
<!-- Filtre Vidéos / Shorts : les deux formats ne sont plus mélangés sans contrôle -->
<div class="flex items-center gap-2 mb-6" role="tablist" aria-label="Filtrer vidéos ou shorts">
<button (click)="setContentFilter('all')" [attr.aria-pressed]="contentFilter() === 'all'"
class="px-3 py-1.5 rounded-full text-sm border transition-colors"
[class]="contentFilter() === 'all' ? 'bg-red-600 border-red-500 text-white' : 'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'">
Tout
</button>
<button (click)="setContentFilter('videos')" [attr.aria-pressed]="contentFilter() === 'videos'"
class="px-3 py-1.5 rounded-full text-sm border transition-colors"
[class]="contentFilter() === 'videos' ? 'bg-red-600 border-red-500 text-white' : 'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'">
Vidéos
</button>
<button (click)="setContentFilter('shorts')" [attr.aria-pressed]="contentFilter() === 'shorts'"
class="px-3 py-1.5 rounded-full text-sm border transition-colors"
[class]="contentFilter() === 'shorts' ? 'bg-red-600 border-red-500 text-white' : 'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'">
Shorts
</button>
</div>
<div class="space-y-10">
<section *ngFor="let b of blocks()">
<div class="flex items-center justify-between mb-3">
<h3 class="text-xl font-semibold">{{ b.label }}</h3>
<a [routerLink]="['/p', b.provider, 't', themeSlug()]" class="text-sm px-3 py-1 rounded bg-slate-800 hover:bg-slate-700 border border-slate-700">{{ 'themes.viewAll' | t }}</a>
</div>
<!-- Loading state -->
<div *ngIf="b.loading" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
<div *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]" class="animate-pulse bg-slate-800 rounded-lg h-40"></div>
</div>
<!-- Error / empty -->
<div *ngIf="!b.loading && b.error" class="text-amber-300">{{ b.error }}</div>
<div *ngIf="!b.loading && !b.error && b.items.length === 0" class="text-slate-400">{{ 'empty.noItems' | t }}</div>
<div *ngIf="!b.loading && !b.error && b.items.length > 0 && filteredItems(b).length === 0" class="text-slate-400">
Aucun {{ contentFilter() === 'shorts' ? 'Short' : 'vidéo' }} pour ce fournisseur — essayez l'autre filtre.
</div>
<!-- Grid -->
<div *ngIf="!b.loading && !b.error && filteredItems(b).length" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
<div *ngFor="let v of filteredItems(b)"
class="group bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50 transition-all duration-300">
<a [routerLink]="['/watch', v.videoId]"
[queryParams]="watchQueryParams(b.provider, v)"
[state]="{ video: v }"
class="block">
<div class="relative">
<img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-40 object-cover">
<div class="absolute top-2 right-2">
<app-provider-badge [provider]="b.provider"></app-provider-badge>
</div>
<div class="absolute top-2 left-2 flex gap-1">
<span *ngIf="kindOf(v, b.provider) === 'short'" class="bg-red-600 text-white text-[11px] font-bold px-1.5 py-0.5 rounded">SHORT</span>
<span *ngIf="kindOf(v, b.provider) === 'live'" class="bg-purple-600 text-white text-[11px] font-bold px-1.5 py-0.5 rounded">LIVE</span>
</div>
<div class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-0.5 rounded" *ngIf="v.duration && v.duration > 0">
{{ v.duration | duration }}
</div>
<div class="absolute bottom-2 left-2">
<app-like-button [videoId]="v.videoId" [provider]="b.provider" [title]="v.title" [thumbnail]="v.thumbnail"></app-like-button>
</div>
</div>
<div class="p-3 pb-1">
<h4 class="font-semibold line-clamp-2">{{ v.title }}</h4>
</div>
</a>
<div class="px-3 pb-3">
<app-channel-identity
size="sm"
[provider]="v.provider || b.provider"
[externalId]="v.channelExternalId ?? null"
[avatarUrl]="v.channelAvatarUrl || v.uploaderAvatar || null"
[name]="v.uploaderName || null"
></app-channel-identity>
</div>
</div>
</div>
</section>
</div>
</div>