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:
@@ -0,0 +1,74 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import type { ProviderId, ChannelMeta } from '../../models/channel.model';
|
||||
import { SubscriptionsService } from '../../../../services/subscriptions.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-subscribe-button',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: `
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400"
|
||||
[class.border-red-500/60]="isSubscribed()"
|
||||
[class.bg-red-500/20]="isSubscribed()"
|
||||
[class.text-red-100]="isSubscribed()"
|
||||
[class.border-slate-600]="!isSubscribed()"
|
||||
[class.bg-slate-800/70]="!isSubscribed()"
|
||||
[class.text-slate-200]="!isSubscribed()"
|
||||
[disabled]="isBusy()"
|
||||
(click)="toggle()"
|
||||
[attr.aria-pressed]="isSubscribed()"
|
||||
>
|
||||
<span *ngIf="!isSubscribed(); else unsub">S'abonner</span>
|
||||
<ng-template #unsub>Se désabonner</ng-template>
|
||||
</button>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SubscribeButtonComponent {
|
||||
private readonly subs = inject(SubscriptionsService);
|
||||
|
||||
@Input({ required: true }) provider!: ProviderId;
|
||||
@Input({ required: true }) externalId!: string;
|
||||
@Input() channelMeta?: ChannelMeta | null;
|
||||
@Input() optimistic = true;
|
||||
|
||||
@Output() subscribed = new EventEmitter<ChannelMeta | null>();
|
||||
@Output() unsubscribed = new EventEmitter<void>();
|
||||
|
||||
private busy = signal(false);
|
||||
readonly isBusy = computed(() => this.busy());
|
||||
|
||||
readonly isSubscribed = computed(() => this.subs.isSubscribed(this.provider, this.externalId));
|
||||
|
||||
toggle(): void {
|
||||
if (this.busy()) return;
|
||||
const subscribed = this.isSubscribed();
|
||||
this.busy.set(true);
|
||||
|
||||
if (subscribed) {
|
||||
const current = this.subs.items().find((item) => item.channel.provider === this.provider && item.channel.externalId === this.externalId);
|
||||
if (!current) {
|
||||
this.busy.set(false);
|
||||
return;
|
||||
}
|
||||
this.subs.unsubscribe(current.subscriptionId).subscribe({
|
||||
next: (ok) => {
|
||||
if (ok) this.unsubscribed.emit();
|
||||
},
|
||||
complete: () => this.busy.set(false),
|
||||
error: () => this.busy.set(false),
|
||||
});
|
||||
} else {
|
||||
this.subs.subscribe(this.provider, this.externalId, { refresh: this.optimistic }).subscribe({
|
||||
next: (item) => {
|
||||
this.subscribed.emit(item?.channel ?? null);
|
||||
},
|
||||
complete: () => this.busy.set(false),
|
||||
error: () => this.busy.set(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user