feat(channel): page details chaine multi-providers avec onglets et infinite scroll
CI / build-and-test (push) Successful in 13m34s
CI / build-and-test (push) Successful in 13m34s
- Strategy Pattern front: ChannelProviderInterface + 6 providers (yt/dm/tw/pt/od/ru) + factory (ids longs/courts) - ChannelContentService: cache par onglet/tri/recherche (TTL 5min), pagination nextPage - ChannelPage: header (banniere/avatar/subs/description ...plus), tabs dynamiques par capabilities, grilles videos/shorts 9:16/playlists/live, recherche debounced, tri recent/populaire, infinite scroll IntersectionObserver, skeletons/empty/error - Backend: GET /api/channels/:provider/:id/content (videos/shorts/playlists/live) natif par provider
This commit is contained in:
@@ -4,13 +4,15 @@ import { CommonModule } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { DurationPipe } from '../../pipes/duration.pipe';
|
||||
import { ProviderBadgeComponent } from '../provider-badge/provider-badge.component';
|
||||
import { SubscribeButtonComponent } from '../subscribe-button/subscribe-button.component';
|
||||
import type { ChannelMeta, ProviderId } from '../../models/channel.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-card',
|
||||
templateUrl: './video-card.component.html',
|
||||
styleUrls: ['./video-card.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, DurationPipe, ProviderBadgeComponent],
|
||||
imports: [CommonModule, RouterLink, DurationPipe, ProviderBadgeComponent, SubscribeButtonComponent],
|
||||
})
|
||||
export class VideoCardComponent {
|
||||
@Input() video!: VideoItem;
|
||||
@@ -21,4 +23,37 @@ export class VideoCardComponent {
|
||||
if (v.provider === 'twitch' && v.channel) qp.channel = v.channel;
|
||||
return qp;
|
||||
}
|
||||
|
||||
get channelProviderId(): ProviderId | null {
|
||||
const map: Record<VideoItem['provider'], ProviderId> = {
|
||||
youtube: 'yt',
|
||||
dailymotion: 'dm',
|
||||
twitch: 'tw',
|
||||
peertube: 'pt',
|
||||
odysee: 'od',
|
||||
rumble: 'ru',
|
||||
};
|
||||
return map[this.video.provider] || null;
|
||||
}
|
||||
|
||||
channelExternalId(): string | null {
|
||||
return this.video.channelMeta?.externalId ?? this.video.channelExternalId ?? null;
|
||||
}
|
||||
|
||||
channelAvatarUrl(): string | null {
|
||||
return this.video.channelMeta?.avatarUrl ?? this.video.channelAvatarUrl ?? null;
|
||||
}
|
||||
|
||||
channelDisplayName(channel?: ChannelMeta | null, fallback?: string | null): string {
|
||||
const preferred = (channel?.title || channel?.handle || fallback || '').toString().trim();
|
||||
if (preferred.length > 0) {
|
||||
return preferred;
|
||||
}
|
||||
return (channel?.externalId || '').trim();
|
||||
}
|
||||
|
||||
channelInitial(channel?: ChannelMeta | null, fallback?: string | null): string {
|
||||
const name = this.channelDisplayName(channel, fallback);
|
||||
return name ? name.charAt(0).toUpperCase() : 'C';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user