173 lines
9.2 KiB
HTML
173 lines
9.2 KiB
HTML
<div class="container mx-auto p-4 sm:p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center gap-2 text-slate-200">
|
|
<a [routerLink]="['/t', theme()]" class="px-3 py-1 rounded bg-slate-800 hover:bg-slate-700 border border-slate-700">← {{ 'themes.backToThemes' | t }}</a>
|
|
<h2 class="text-2xl font-bold">
|
|
{{ themes.bySlug(theme())?.emoji }} {{ themes.bySlug(theme())?.label }}
|
|
<span class="text-slate-400 text-base">— {{ provider() | titlecase }}</span>
|
|
</h2>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<label class="text-sm text-slate-400">{{ 'themes.changeProvider' | t }}</label>
|
|
<select [ngModel]="provider()" (ngModelChange)="changeProvider($event)" class="bg-gray-800 text-white rounded px-2 py-1">
|
|
<option *ngFor="let p of providersList()" [value]="p.id">{{ p.label }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="grid sm:grid-cols-4 gap-3 mb-6 bg-slate-800/50 p-3 rounded-lg border border-slate-700">
|
|
<div>
|
|
<label class="block text-xs text-slate-400 mb-1">{{ 'filter.sort' | t }}</label>
|
|
<select [ngModel]="sort()" (ngModelChange)="onSortChange($event)" class="w-full bg-gray-800 text-white rounded px-2 py-1">
|
|
<option value="recent">{{ 'filter.sort.recent' | t }}</option>
|
|
<option value="viewed">{{ 'filter.sort.viewed' | t }}</option>
|
|
<option value="longest">{{ 'filter.sort.longest' | t }}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs text-slate-400 mb-1">{{ 'filter.duration' | t }}</label>
|
|
<select [ngModel]="duration()" (ngModelChange)="onDurationChange($event)" class="w-full bg-gray-800 text-white rounded px-2 py-1">
|
|
<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 class="block text-xs text-slate-400 mb-1">{{ 'filter.language' | t }}</label>
|
|
<select [ngModel]="language()" (ngModelChange)="onLanguageChange($event)" class="w-full bg-gray-800 text-white rounded px-2 py-1">
|
|
<option value="any">Any</option>
|
|
<option value="en">English</option>
|
|
<option value="fr">Français</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs text-slate-400 mb-1">{{ 'filter.date' | t }}</label>
|
|
<select [ngModel]="date()" (ngModelChange)="onDateChange($event)" class="w-full bg-gray-800 text-white rounded px-2 py-1">
|
|
<option value="any">Any</option>
|
|
<option value="day">Last 24h</option>
|
|
<option value="week">Last week</option>
|
|
<option value="month">Last month</option>
|
|
<option value="year">Last year</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sections Tabs mimic (simple headings) -->
|
|
<div class="space-y-10">
|
|
<!-- Recorded Streams -->
|
|
<section>
|
|
<h3 class="text-xl font-semibold mb-3">{{ 'themes.recorded' | t }}</h3>
|
|
<div *ngIf="recorded.loading && !recorded.items.length" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
<div *ngFor="let i of [1,2,3,4,5,6,7,8]" class="animate-pulse bg-slate-800/50 rounded-lg overflow-hidden border border-slate-800">
|
|
<div class="w-full h-32 bg-slate-800"></div>
|
|
<div class="p-2">
|
|
<div class="h-3 bg-slate-700 rounded w-11/12 mb-2"></div>
|
|
<div class="h-3 bg-slate-700 rounded w-8/12"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="!recorded.loading && recorded.items.length === 0" class="text-slate-400">{{ 'empty.noItems' | t }}</div>
|
|
<div *ngIf="recorded.error" class="mt-2 p-3 bg-red-900/30 border border-red-700 text-red-200 rounded">
|
|
{{ recorded.error }}
|
|
<button class="ml-2 underline hover:text-red-100" (click)="retry(recorded)">{{ 'action.retry' | t }}</button>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4" *ngIf="recorded.items.length">
|
|
<a *ngFor="let v of (recorded.items | slice:0:recorded.visibleCount); trackBy: trackByVideo"
|
|
[routerLink]="['/watch', v.videoId]" [queryParams]="watchQueryParams(v)" [state]="{ video: v }"
|
|
class="group bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50">
|
|
<div class="relative">
|
|
<img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover">
|
|
<div class="absolute bottom-2 left-2">
|
|
<app-like-button [videoId]="v.videoId" [provider]="provider()"></app-like-button>
|
|
</div>
|
|
</div>
|
|
<div class="p-2 text-sm line-clamp-2">{{ v.title }}</div>
|
|
</a>
|
|
</div>
|
|
<!-- Infinite scroll anchor -->
|
|
<app-infinite-anchor
|
|
*ngIf="recorded.items.length && (recorded.items.length >= recorded.visibleCount)"
|
|
[busy]="recorded.loading"
|
|
[disabled]="!recorded.nextCursor && recorded.items.length <= recorded.visibleCount"
|
|
[rootMargin]="'1000px 0px 1000px 0px'"
|
|
(loadMore)="showMore(recorded)">
|
|
</app-infinite-anchor>
|
|
<div class="mt-2 flex items-center justify-center text-slate-400 text-sm" *ngIf="recorded.loading">
|
|
<span class="inline-block h-4 w-4 border-2 border-slate-500 border-t-transparent rounded-full animate-spin mr-2"></span>
|
|
{{ 'loading.more' | t }}
|
|
</div>
|
|
<div class="mt-3" *ngIf="recorded.items.length && (recorded.items.length > recorded.visibleCount || recorded.nextCursor)">
|
|
<button class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded border border-slate-700 text-slate-200 disabled:opacity-60"
|
|
[disabled]="recorded.loading"
|
|
(click)="showMore(recorded)">
|
|
{{ 'loading.more' | t }} (20)
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Videos -->
|
|
<section>
|
|
<h3 class="text-xl font-semibold mb-3">{{ 'themes.videos' | t }}</h3>
|
|
<div *ngIf="videos.loading && !videos.items.length" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
<div *ngFor="let i of [1,2,3,4,5,6,7,8]" class="animate-pulse bg-slate-800/50 rounded-lg overflow-hidden border border-slate-800">
|
|
<div class="w-full h-32 bg-slate-800"></div>
|
|
<div class="p-2">
|
|
<div class="h-3 bg-slate-700 rounded w-11/12 mb-2"></div>
|
|
<div class="h-3 bg-slate-700 rounded w-8/12"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="!videos.loading && videos.items.length === 0" class="text-slate-400">{{ 'empty.noItems' | t }}</div>
|
|
<div *ngIf="videos.error" class="mt-2 p-3 bg-red-900/30 border border-red-700 text-red-200 rounded">
|
|
{{ videos.error }}
|
|
<button class="ml-2 underline hover:text-red-100" (click)="retry(videos)">{{ 'action.retry' | t }}</button>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4" *ngIf="videos.items.length">
|
|
<a *ngFor="let v of (videos.items | slice:0:videos.visibleCount); trackBy: trackByVideo"
|
|
[routerLink]="['/watch', v.videoId]" [queryParams]="watchQueryParams(v)" [state]="{ video: v }"
|
|
class="group bg-slate-800/50 rounded-lg overflow-hidden hover:bg-slate-700/50">
|
|
<div class="relative">
|
|
<img [src]="v.thumbnail" [alt]="v.title" loading="lazy" decoding="async" class="w-full h-32 object-cover">
|
|
<div class="absolute bottom-2 left-2">
|
|
<app-like-button [videoId]="v.videoId" [provider]="provider()"></app-like-button>
|
|
</div>
|
|
</div>
|
|
<div class="p-2 text-sm line-clamp-2">{{ v.title }}</div>
|
|
</a>
|
|
</div>
|
|
<!-- Infinite scroll anchor -->
|
|
<app-infinite-anchor
|
|
*ngIf="videos.items.length && (videos.items.length >= videos.visibleCount)"
|
|
[busy]="videos.loading"
|
|
[disabled]="!videos.nextCursor && videos.items.length <= videos.visibleCount"
|
|
[rootMargin]="'1000px 0px 1000px 0px'"
|
|
(loadMore)="showMore(videos)">
|
|
</app-infinite-anchor>
|
|
<div class="mt-2 flex items-center justify-center text-slate-400 text-sm" *ngIf="videos.loading">
|
|
<span class="inline-block h-4 w-4 border-2 border-slate-500 border-t-transparent rounded-full animate-spin mr-2"></span>
|
|
{{ 'loading.more' | t }}
|
|
</div>
|
|
<div class="mt-3" *ngIf="videos.items.length && (videos.items.length > videos.visibleCount || videos.nextCursor)">
|
|
<button class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded border border-slate-700 text-slate-200 disabled:opacity-60"
|
|
[disabled]="videos.loading"
|
|
(click)="showMore(videos)">
|
|
{{ 'loading.more' | t }} (20)
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Categories (static recommended) -->
|
|
<section>
|
|
<h3 class="text-xl font-semibold mb-3">{{ 'themes.categories' | t }}</h3>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
<div *ngFor="let c of categories()" class="bg-slate-800/60 rounded-xl p-4 border border-slate-700">
|
|
<div class="text-4xl">{{ c.emoji }}</div>
|
|
<div class="mt-2 font-semibold">{{ c.label }}</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|