chore: update Angular TypeScript build info cache
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, SimpleChanges, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { PlaylistsService, Playlist } from '../../../../services/playlists.service';
|
||||
import { AuthService } from '../../../../services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-to-playlist',
|
||||
standalone: true,
|
||||
imports: [CommonModule, HttpClientModule],
|
||||
template: `
|
||||
<div class="relative inline-block text-left" (click)="$event.stopPropagation();">
|
||||
<button (click)="toggleOpen($event)"
|
||||
[disabled]="!isAuthenticated()"
|
||||
class="transition-colors duration-200 p-1 rounded-full hover:bg-slate-700/50 flex items-center justify-center text-slate-300 disabled:opacity-60"
|
||||
[attr.aria-expanded]="open()"
|
||||
[attr.aria-haspopup]="true"
|
||||
[attr.title]="!isAuthenticated() ? 'Connectez-vous pour gérer vos playlists' : 'Ajouter à une playlist'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M4 6a2 2 0 012-2h12a1 1 0 010 2H6v10a1 1 0 11-2 0V6z" />
|
||||
<path d="M14 11a1 1 0 011-1h5a1 1 0 110 2h-5a1 1 0 01-1-1zM14 15a1 1 0 011-1h5a1 1 0 110 2h-5a1 1 0 01-1-1zM10 11a1 1 0 112 0v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1H9a1 1 0 110-2h1v-1z" />
|
||||
</svg>
|
||||
<span class="sr-only">Ajouter à une playlist</span>
|
||||
</button>
|
||||
|
||||
@if (open()) {
|
||||
<div class="absolute right-0 mt-2 w-72 origin-top-right rounded-md bg-slate-900 border border-slate-700 shadow-lg focus:outline-none z-50">
|
||||
<div class="p-3 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm font-semibold text-slate-200">Vos playlists</div>
|
||||
<button class="text-xs text-slate-400 hover:text-slate-200" (click)="reloadPlaylists()">Rafraîchir</button>
|
||||
</div>
|
||||
|
||||
@if (loading()) {
|
||||
<div class="text-sm text-slate-400">Chargement...</div>
|
||||
} @else if (error()) {
|
||||
<div class="text-sm text-red-300">{{ error() }}</div>
|
||||
} @else if (playlists().length === 0) {
|
||||
<div class="text-sm text-slate-400">Aucune playlist. Créez-en une ci-dessous.</div>
|
||||
} @else {
|
||||
<div class="max-h-48 overflow-auto space-y-2">
|
||||
@for (pl of playlists(); track pl.id) {
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm text-slate-200">{{ pl.title }}</div>
|
||||
<div class="text-xs text-slate-400">{{ pl.itemsCount || 0 }} vidéo(s)</div>
|
||||
</div>
|
||||
<button class="px-2 py-1 rounded bg-slate-700 hover:bg-slate-600 text-slate-100 text-xs"
|
||||
[disabled]="busyAdd()"
|
||||
(click)="onAddTo(pl)">Ajouter</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="border-t border-slate-700 pt-2">
|
||||
<label class="block text-xs text-slate-400 mb-1">Nouvelle playlist</label>
|
||||
<input type="text" class="w-full bg-slate-950 text-slate-100 border border-slate-700 rounded px-2 py-1 text-sm"
|
||||
[value]="newTitle()" (input)="onTitleChange($event)" placeholder="Titre de la playlist" />
|
||||
<div class="mt-2 flex items-center justify-end gap-2">
|
||||
<label class="flex items-center gap-2 text-xs text-slate-300">
|
||||
<input type="checkbox" class="accent-red-500" [checked]="newPrivate()" (change)="onPrivateChange($event)" />
|
||||
Privée
|
||||
</label>
|
||||
<button class="px-3 py-1.5 rounded bg-red-600 hover:bg-red-500 text-white text-xs font-semibold disabled:opacity-60"
|
||||
[disabled]="!newTitle().trim() || busyAdd()"
|
||||
(click)="onQuickCreate()">Créer + Ajouter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.sr-only { position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0; }
|
||||
`
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AddToPlaylistComponent implements OnInit, OnDestroy {
|
||||
private api = inject(PlaylistsService);
|
||||
private auth = inject(AuthService);
|
||||
|
||||
@Input() provider: string = 'youtube';
|
||||
@Input() videoId: string = '';
|
||||
@Input() title?: string;
|
||||
@Input() thumbnail?: string;
|
||||
|
||||
open = signal<boolean>(false);
|
||||
loading = signal<boolean>(false);
|
||||
error = signal<string | null>(null);
|
||||
playlists = signal<Playlist[]>([]);
|
||||
busyAdd = signal<boolean>(false);
|
||||
newTitle = signal<string>('');
|
||||
newPrivate = signal<boolean>(true);
|
||||
private destroyed = false;
|
||||
|
||||
ngOnInit(): void {}
|
||||
ngOnDestroy(): void { this.destroyed = true; }
|
||||
|
||||
isAuthenticated(): boolean { return !!this.auth.currentUser(); }
|
||||
|
||||
toggleOpen(ev: Event): void {
|
||||
try { ev.preventDefault(); ev.stopPropagation(); } catch {}
|
||||
if (!this.isAuthenticated()) return;
|
||||
const next = !this.open();
|
||||
this.open.set(next);
|
||||
if (next && this.playlists().length === 0 && !this.loading()) {
|
||||
this.reloadPlaylists();
|
||||
}
|
||||
}
|
||||
|
||||
reloadPlaylists(): void {
|
||||
this.loading.set(true);
|
||||
this.error.set(null);
|
||||
this.api.list(200, 0).subscribe({
|
||||
next: (rows) => { if (!this.destroyed) { this.playlists.set(rows || []); this.loading.set(false); } },
|
||||
error: (err) => { if (!this.destroyed) { this.error.set(err?.error?.error || err?.message || 'Erreur'); this.loading.set(false); } }
|
||||
});
|
||||
}
|
||||
|
||||
onAddTo(pl: Playlist): void {
|
||||
if (this.busyAdd() || !pl?.id) return;
|
||||
this.busyAdd.set(true);
|
||||
this.api.addVideo(pl.id, this.provider, this.videoId, this.title, this.thumbnail).subscribe({
|
||||
next: () => { this.busyAdd.set(false); this.open.set(false); },
|
||||
error: (err) => { this.busyAdd.set(false); this.error.set(err?.error?.error || err?.message || 'Échec de l\'ajout'); }
|
||||
});
|
||||
}
|
||||
|
||||
onQuickCreate(): void {
|
||||
const t = this.newTitle().trim();
|
||||
if (!t) return;
|
||||
if (this.busyAdd()) return;
|
||||
this.busyAdd.set(true);
|
||||
this.api.create(t, undefined, undefined, this.newPrivate()).subscribe({
|
||||
next: (pl) => {
|
||||
this.api.addVideo(pl.id, this.provider, this.videoId, this.title, this.thumbnail).subscribe({
|
||||
next: () => { this.busyAdd.set(false); this.open.set(false); this.newTitle.set(''); },
|
||||
error: (err) => { this.busyAdd.set(false); this.error.set(err?.error?.error || err?.message || 'Échec de l\'ajout'); }
|
||||
});
|
||||
},
|
||||
error: (err) => { this.busyAdd.set(false); this.error.set(err?.error?.error || err?.message || 'Échec de la création'); }
|
||||
});
|
||||
}
|
||||
|
||||
onTitleChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
this.newTitle.set(input.value);
|
||||
}
|
||||
|
||||
onPrivateChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
this.newPrivate.set(input.checked);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user