first commit
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { ThemesService } from '../../services/themes.service';
|
||||
import { YoutubeApiService } from '../../services/youtube-api.service';
|
||||
import { Provider, InstanceService } from '../../services/instance.service';
|
||||
import { Video } from '../../models/video.model';
|
||||
import { TranslatePipe } from '../../pipes/translate.pipe';
|
||||
|
||||
interface ProviderBlock {
|
||||
provider: Provider;
|
||||
label: string;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
items: Video[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-page',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './theme-page.component.html',
|
||||
imports: [CommonModule, RouterLink, TranslatePipe]
|
||||
})
|
||||
export class ThemePageComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
themes = inject(ThemesService);
|
||||
private api = inject(YoutubeApiService);
|
||||
private instances = inject(InstanceService);
|
||||
|
||||
// Show all supported providers on Tendances
|
||||
private providersToShow: Provider[] = ['youtube', 'twitch', 'dailymotion', 'peertube', 'rumble', 'odysee'];
|
||||
|
||||
readonly themeSlug = signal<string>('trending');
|
||||
readonly themeLabel = computed(() => this.themes.bySlug(this.themeSlug())?.label || this.themeSlug());
|
||||
|
||||
blocks = signal<ProviderBlock[]>([]);
|
||||
|
||||
constructor() {
|
||||
this.route.paramMap.subscribe(pm => {
|
||||
const slug = pm.get('theme') || pm.get('slug') || 'trending';
|
||||
this.themeSlug.set(slug);
|
||||
this.loadAllBlocks();
|
||||
});
|
||||
}
|
||||
|
||||
private loadAllBlocks() {
|
||||
const slug = this.themeSlug();
|
||||
const blocks: ProviderBlock[] = this.providersToShow.map(p => ({ provider: p, label: this.labelFor(p), loading: true, error: null, items: [] }));
|
||||
this.blocks.set(blocks);
|
||||
for (const b of blocks) this.fetchForProvider(b, slug);
|
||||
}
|
||||
|
||||
private tokensForTheme(slug: string): string[] {
|
||||
const tokens = this.themes.tokensFor(slug);
|
||||
// Special cases
|
||||
if (slug === 'trending') return [];
|
||||
if (slug === 'live') return ['live'];
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private labelFor(p: Provider): string {
|
||||
switch (p) {
|
||||
case 'youtube': return 'YouTube';
|
||||
case 'twitch': return 'Twitch';
|
||||
case 'peertube': return 'PeerTube';
|
||||
case 'rumble': return 'Rumble';
|
||||
case 'odysee': return 'Odysee';
|
||||
case 'dailymotion': return 'Dailymotion';
|
||||
}
|
||||
}
|
||||
|
||||
private fetchForProvider(block: ProviderBlock, slug: string) {
|
||||
// Provider readiness (e.g., YouTube key, Twitch creds). If not ready, surface reason and skip network calls.
|
||||
const ready = this.instances.getProviderReadiness(block.provider);
|
||||
if (!ready.ready) {
|
||||
block.loading = false;
|
||||
block.error = ready.reason || 'Provider not ready';
|
||||
console.warn('[ThemePage] Provider not ready', { provider: block.provider, theme: slug, reason: block.error });
|
||||
this.blocks.update(a => [...a]);
|
||||
return;
|
||||
}
|
||||
// If YouTube quota already exceeded, surface and skip
|
||||
if (block.provider === 'youtube' && this.api.quotaExceeded$.getValue()) {
|
||||
block.loading = false;
|
||||
block.error = 'YouTube quota exceeded. Try again later or switch provider.';
|
||||
console.warn('[ThemePage] Skip fetch: YouTube quota exceeded', { theme: slug });
|
||||
this.blocks.update(a => [...a]);
|
||||
return;
|
||||
}
|
||||
const tokens = this.tokensForTheme(slug);
|
||||
const max = 10;
|
||||
block.loading = true;
|
||||
block.error = null;
|
||||
if (slug === 'trending') {
|
||||
this.api.getTrendingPage(null, block.provider).subscribe({
|
||||
next: res => {
|
||||
block.items = (res.items || []).slice(0, max);
|
||||
block.loading = false;
|
||||
if (!block.items.length) console.info('[ThemePage] Empty block page', { provider: block.provider, theme: slug });
|
||||
this.blocks.update(a => [...a]);
|
||||
},
|
||||
error: (e) => {
|
||||
if (block.provider === 'youtube' && e?.status === 403 && (e?.error?.error?.errors?.[0]?.reason === 'quotaExceeded' || /quota/i.test(String(e?.error?.error?.message || e?.message || '')))) {
|
||||
this.api.quotaExceeded$.next(true);
|
||||
block.error = 'YouTube quota exceeded. Try again later or switch provider.';
|
||||
} else {
|
||||
block.error = 'Erreur de chargement';
|
||||
}
|
||||
block.loading = false; this.blocks.update(a => [...a]);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const q = tokens.join(' ');
|
||||
if (!q) {
|
||||
// fallback to trending
|
||||
this.api.getTrendingPage(null, block.provider).subscribe({
|
||||
next: res => {
|
||||
block.items = (res.items || []).slice(0, max);
|
||||
block.loading = false;
|
||||
if (!block.items.length) console.info('[ThemePage] Empty block page', { provider: block.provider, theme: slug, mode: 'fallback_trending' });
|
||||
this.blocks.update(a => [...a]);
|
||||
},
|
||||
error: (e) => {
|
||||
if (block.provider === 'youtube' && e?.status === 403 && (e?.error?.error?.errors?.[0]?.reason === 'quotaExceeded' || /quota/i.test(String(e?.error?.error?.message || e?.message || '')))) {
|
||||
this.api.quotaExceeded$.next(true);
|
||||
block.error = 'YouTube quota exceeded. Try again later or switch provider.';
|
||||
} else {
|
||||
block.error = 'Erreur de chargement';
|
||||
}
|
||||
block.loading = false; this.blocks.update(a => [...a]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.api.searchVideosPage(q, null, block.provider).subscribe({
|
||||
next: res => {
|
||||
block.items = (res.items || []).slice(0, max);
|
||||
block.loading = false;
|
||||
if (!block.items.length) console.info('[ThemePage] Empty block page', { provider: block.provider, theme: slug, query: q });
|
||||
this.blocks.update(a => [...a]);
|
||||
},
|
||||
error: (e) => {
|
||||
if (block.provider === 'youtube' && e?.status === 403 && (e?.error?.error?.errors?.[0]?.reason === 'quotaExceeded' || /quota/i.test(String(e?.error?.error?.message || e?.message || '')))) {
|
||||
this.api.quotaExceeded$.next(true);
|
||||
block.error = 'YouTube quota exceeded. Try again later or switch provider.';
|
||||
} else {
|
||||
block.error = 'Erreur de chargement';
|
||||
}
|
||||
block.loading = false; this.blocks.update(a => [...a]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchQueryParams(provider: Provider, v: Video): Record<string, any> {
|
||||
const qp: any = { p: provider };
|
||||
if (provider === 'odysee' && v.url?.startsWith('https://odysee.com/')) {
|
||||
try {
|
||||
let slug = v.url.substring('https://odysee.com/'.length);
|
||||
if (slug.startsWith('/')) slug = slug.slice(1);
|
||||
qp.slug = slug;
|
||||
} catch {}
|
||||
}
|
||||
if (provider === 'twitch' && v.type === 'channel') {
|
||||
// Try to use channel name
|
||||
if (v.videoId) qp.channel = v.videoId;
|
||||
}
|
||||
return qp;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user