chore: update Angular cache and TypeScript build info
This commit is contained in:
@@ -52,12 +52,6 @@
|
||||
<option value="views">Vues</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 justify-end">
|
||||
<button class="px-3 py-1 rounded bg-slate-700 hover:bg-slate-600 text-slate-100 disabled:opacity-50"
|
||||
(click)="prevPage()" [disabled]="!canPrev()">Précédent</button>
|
||||
<div class="text-sm text-slate-300">Page {{ pageParam() }}</div>
|
||||
<button class="px-3 py-1 rounded bg-slate-700 hover:bg-slate-600 text-slate-100" (click)="nextPage()">Suivant</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (error()) {
|
||||
@@ -68,5 +62,16 @@
|
||||
}
|
||||
|
||||
<app-search-result-grid [videos]="filteredResults()" [loading]="loading()"></app-search-result-grid>
|
||||
|
||||
<!-- Infinite scroll anchor & indicators -->
|
||||
<div class="flex flex-col items-center justify-center py-6">
|
||||
@if (loading()) {
|
||||
<div class="text-slate-400 text-sm">Chargement…</div>
|
||||
}
|
||||
@if (endReached()) {
|
||||
<div class="text-slate-500 text-xs">Fin des résultats</div>
|
||||
}
|
||||
</div>
|
||||
<div #infiniteScrollAnchor class="h-1 w-full"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, ViewChild, ElementRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||
@@ -21,7 +21,7 @@ import { Observable, Subscription } from 'rxjs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, TranslatePipe, SearchResultGridComponent]
|
||||
})
|
||||
export class SearchComponent {
|
||||
export class SearchComponent implements AfterViewInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private api = inject(YoutubeApiService);
|
||||
@@ -43,9 +43,13 @@ export class SearchComponent {
|
||||
groups = signal<Record<string, any[]>>({});
|
||||
showUnified = signal<boolean>(false);
|
||||
error = signal<string | null>(null);
|
||||
endReached = signal<boolean>(false);
|
||||
|
||||
// Dedup key to avoid recording the same search multiple times
|
||||
private lastRecordedKey: string | null = null;
|
||||
// Signature to detect new searches (q + providers + sort); excludes page
|
||||
private lastSearchSignature: string | null = null;
|
||||
private io?: IntersectionObserver;
|
||||
|
||||
hasQuery = computed(() => this.q().length > 0);
|
||||
providerLabel = computed(() => {
|
||||
@@ -107,6 +111,43 @@ export class SearchComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Merge groups with dedup by id/videoId/url
|
||||
private mergeGroups(prev: Record<string, any[]>, incoming: Record<string, any[]>): Record<string, any[]> {
|
||||
const result: Record<string, any[]> = { ...(prev || {}) };
|
||||
const providers = new Set<string>([...Object.keys(prev || {}), ...Object.keys(incoming || {})]);
|
||||
providers.forEach(pid => {
|
||||
const a = Array.isArray(prev?.[pid]) ? prev[pid] : [];
|
||||
const b = Array.isArray(incoming?.[pid]) ? incoming[pid] : [];
|
||||
if (a.length === 0) { result[pid] = b.slice(); return; }
|
||||
if (b.length === 0) { result[pid] = a.slice(); return; }
|
||||
const seen = new Set<string>();
|
||||
const merged: any[] = [];
|
||||
const keyOf = (v: any) => String((v && (v.id || v.videoId || v.url)) ?? JSON.stringify(v));
|
||||
a.forEach(v => { const k = keyOf(v); if (!seen.has(k)) { seen.add(k); merged.push(v); } });
|
||||
b.forEach(v => { const k = keyOf(v); if (!seen.has(k)) { seen.add(k); merged.push(v); } });
|
||||
result[pid] = merged;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set up IntersectionObserver for infinite scroll
|
||||
ngAfterViewInit(): void {
|
||||
const anchor = this.infiniteScrollAnchor?.nativeElement;
|
||||
if (!anchor) return;
|
||||
this.io = new IntersectionObserver((entries) => {
|
||||
const e = entries[0];
|
||||
if (e && e.isIntersecting) {
|
||||
this.loadNextPage();
|
||||
}
|
||||
}, { root: null, rootMargin: '200px 0px', threshold: 0 });
|
||||
this.io.observe(anchor);
|
||||
}
|
||||
|
||||
// Optional: clean up observer
|
||||
ngOnDestroy(): void {
|
||||
try { this.io?.disconnect(); } catch {}
|
||||
}
|
||||
|
||||
// Page title with search query
|
||||
pageHeading = computed(() => {
|
||||
const q = this.q();
|
||||
@@ -280,14 +321,28 @@ export class SearchComponent {
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe({
|
||||
next: (resp: any) => {
|
||||
// Convertir la réponse en un format compatible avec notre modèle
|
||||
const groups: Record<string, any[]> = {};
|
||||
// Convertir la réponse et concaténer si page > 1 (scroll infini)
|
||||
const incoming: Record<string, any[]> = {};
|
||||
if (resp.groups) {
|
||||
Object.entries(resp.groups).forEach(([key, value]) => {
|
||||
groups[key] = Array.isArray(value) ? value : [];
|
||||
incoming[key] = Array.isArray(value) ? value : [];
|
||||
});
|
||||
}
|
||||
this.groups.set(groups);
|
||||
|
||||
const currentPage = this.pageParam() || 1;
|
||||
const isContinuation = currentPage > 1 && this.lastSearchSignature != null;
|
||||
if (isContinuation) {
|
||||
const merged = this.mergeGroups(this.groups(), incoming);
|
||||
this.groups.set(merged);
|
||||
// If no new items were added, mark end reached
|
||||
const addedCount = Object.values(incoming).reduce((a: number, arr: any[]) => a + (arr?.length || 0), 0);
|
||||
if (addedCount === 0) this.endReached.set(true);
|
||||
} else {
|
||||
this.groups.set(incoming);
|
||||
// If first page is empty for all providers, end reached
|
||||
const total = Object.values(incoming).reduce((a: number, arr: any[]) => a + (arr?.length || 0), 0);
|
||||
this.endReached.set(total === 0);
|
||||
}
|
||||
this.loading.set(false);
|
||||
this.error.set(null);
|
||||
},
|
||||
@@ -338,6 +393,11 @@ export class SearchComponent {
|
||||
this.unified.setQuery(q);
|
||||
this.unified.setProviders(providersToUse);
|
||||
|
||||
// Determine if this is a new search (signature excludes page)
|
||||
const signature = `${q}|${Array.isArray(providersToUse) ? providersToUse.join(',') : String(providersToUse)}|${sortNorm}`;
|
||||
const isNewSearch = this.lastSearchSignature !== signature || pageNum === 1;
|
||||
this.lastSearchSignature = signature;
|
||||
|
||||
// Record this search once per unique (q, providers) combination
|
||||
try {
|
||||
const key = `${q}|${Array.isArray(providersToUse) ? providersToUse.join(',') : String(providersToUse)}`;
|
||||
@@ -355,13 +415,17 @@ export class SearchComponent {
|
||||
|
||||
this.showUnified.set(true);
|
||||
this.loading.set(true);
|
||||
this.groups.set({} as any); // Clear previous results
|
||||
if (isNewSearch) {
|
||||
this.groups.set({} as any); // Clear only for a new search
|
||||
this.endReached.set(false);
|
||||
}
|
||||
this.error.set(null);
|
||||
} else {
|
||||
this.loading.set(false);
|
||||
this.groups.set({} as any);
|
||||
this.showUnified.set(false);
|
||||
this.error.set(null);
|
||||
this.endReached.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -430,6 +494,7 @@ export class SearchComponent {
|
||||
}
|
||||
|
||||
@ViewChild('searchInput', { static: false }) searchInput?: ElementRef<HTMLInputElement>;
|
||||
@ViewChild('infiniteScrollAnchor', { static: false }) infiniteScrollAnchor?: ElementRef<HTMLDivElement>;
|
||||
|
||||
// Legacy input handlers removed; SearchBox handles keyboard and submit
|
||||
|
||||
@@ -452,6 +517,22 @@ export class SearchComponent {
|
||||
}
|
||||
|
||||
// Pagination controls
|
||||
private loadNextPage() {
|
||||
const current = this.pageParam() || 1;
|
||||
const next = current + 1;
|
||||
if (this.loading() || this.endReached()) return;
|
||||
const cleanParams = this.cleanUrlParams({
|
||||
...this.route.snapshot.queryParams,
|
||||
page: next
|
||||
});
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: cleanParams,
|
||||
queryParamsHandling: '',
|
||||
replaceUrl: true
|
||||
});
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
const next = (this.pageParam() || 1) + 1;
|
||||
const cleanParams = this.cleanUrlParams({
|
||||
|
||||
Reference in New Issue
Block a user