134 lines
4.9 KiB
TypeScript
134 lines
4.9 KiB
TypeScript
|
|
import { ChangeDetectionStrategy, Component, effect, inject, signal, untracked } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { RouterLink } from '@angular/router';
|
|
import { YoutubeApiService } from '../../services/youtube-api.service';
|
|
import { Video } from '../../models/video.model';
|
|
import { InstanceService } from '../../services/instance.service';
|
|
import { InfiniteAnchorComponent } from '../shared/infinite-anchor/infinite-anchor.component';
|
|
import { formatRelativeFr } from '../../utils/date.util';
|
|
import { TranslatePipe } from '../../pipes/translate.pipe';
|
|
import { LikeButtonComponent } from '../shared/components/like-button/like-button.component';
|
|
|
|
@Component({
|
|
selector: 'app-home',
|
|
templateUrl: './home.component.html',
|
|
standalone: true,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [
|
|
CommonModule,
|
|
RouterLink,
|
|
InfiniteAnchorComponent,
|
|
TranslatePipe,
|
|
LikeButtonComponent
|
|
]
|
|
})
|
|
export class HomeComponent {
|
|
private apiService = inject(YoutubeApiService);
|
|
private instances = inject(InstanceService);
|
|
|
|
trendingVideos = signal<Video[]>([]);
|
|
loading = signal(true);
|
|
busyMore = signal(false);
|
|
nextCursor = signal<string | null>(null);
|
|
notice = signal<string | null>(null);
|
|
|
|
constructor() {
|
|
this.reloadTrending();
|
|
// React to provider/region changes to refresh trending automatically
|
|
effect(() => {
|
|
// Rerun when these signals change
|
|
const provider = this.instances.selectedProvider();
|
|
const region = this.instances.region();
|
|
const ptInstance = this.instances.activePeerTubeInstance();
|
|
|
|
// Reset notice and trigger reload, but without re-triggering the effect
|
|
untracked(() => {
|
|
this.notice.set(null);
|
|
this.reloadTrending();
|
|
});
|
|
}, { allowSignalWrites: true });
|
|
}
|
|
|
|
reloadTrending() {
|
|
const readiness = this.instances.getProviderReadiness();
|
|
if (!readiness.ready) {
|
|
this.notice.set(readiness.reason || 'Le provider sélectionné n\'est pas prêt.');
|
|
this.trendingVideos.set([]);
|
|
this.nextCursor.set(null);
|
|
this.loading.set(false);
|
|
return;
|
|
}
|
|
this.loading.set(true);
|
|
this.trendingVideos.set([]);
|
|
this.nextCursor.set(null);
|
|
// Important: reset busy flag so a previous pending state doesn't block new loads
|
|
this.busyMore.set(false);
|
|
this.fetchNextPage();
|
|
}
|
|
|
|
fetchNextPage() {
|
|
if (this.busyMore()) return;
|
|
const readiness = this.instances.getProviderReadiness();
|
|
if (!readiness.ready) {
|
|
if (!this.notice()) this.notice.set(readiness.reason || 'Le provider sélectionné n\'est pas prêt.');
|
|
return;
|
|
}
|
|
this.busyMore.set(true);
|
|
this.apiService.getTrendingPage(this.nextCursor()).subscribe(res => {
|
|
const merged = [...this.trendingVideos(), ...res.items];
|
|
this.trendingVideos.set(merged);
|
|
this.nextCursor.set(res.nextCursor || null);
|
|
this.busyMore.set(false);
|
|
this.loading.set(false);
|
|
const provider = this.instances.selectedProvider();
|
|
if (merged.length === 0 && !this.notice()) {
|
|
const readiness2 = this.instances.getProviderReadiness();
|
|
if (!readiness2.ready) {
|
|
this.notice.set(readiness2.reason || 'Le provider sélectionné n\'est pas prêt.');
|
|
} else if (provider === 'youtube') {
|
|
this.notice.set('Aucune vidéo tendance YouTube chargée. Vérifiez que votre YOUTUBE_API_KEY est valide et que les restrictions HTTP referrer incluent http://localhost:4200/*.');
|
|
} else if (provider === 'peertube') {
|
|
const inst = this.instances.activePeerTubeInstance();
|
|
this.notice.set(`PeerTube: les vidéos ne sont pas disponibles depuis l'instance "${inst}" pour le moment. Essayez une autre instance dans l'en-tête.`);
|
|
} else if (provider === 'rumble') {
|
|
const label = this.instances.selectedProviderLabel();
|
|
this.notice.set(`Les vidéos ne sont pas disponibles pour le provider "${label}" pour le moment. Réessayez plus tard ou choisissez un autre provider.`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
formatViews(views: number): string {
|
|
if (views >= 1_000_000_000) {
|
|
return (views / 1_000_000_000).toFixed(1) + 'B';
|
|
}
|
|
if (views >= 1_000_000) {
|
|
return (views / 1_000_000).toFixed(1) + 'M';
|
|
}
|
|
if (views >= 1_000) {
|
|
return (views / 1_000).toFixed(1) + 'K';
|
|
}
|
|
return views.toString();
|
|
}
|
|
|
|
// Relative date for cards (e.g., "il y a 2 heures")
|
|
formatRelative(dateIso?: string): string {
|
|
if (!dateIso) return '';
|
|
return formatRelativeFr(dateIso);
|
|
}
|
|
|
|
// Build query params for Watch page (provider + optional odysee slug)
|
|
watchQueryParams(v: Video): Record<string, any> | null {
|
|
const p = this.instances.selectedProvider();
|
|
const qp: any = { p };
|
|
if (p === 'odysee' && v.url?.startsWith('https://odysee.com/')) {
|
|
let slug = v.url.substring('https://odysee.com/'.length);
|
|
if (slug.startsWith('/')) slug = slug.slice(1);
|
|
qp.slug = slug;
|
|
}
|
|
return qp;
|
|
}
|
|
}
|
|
|