chore: update Angular cache and TypeScript build info files
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, ElementRef, inject, signal, untracked, ViewChild } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { YoutubeApiService } from '../../services/youtube-api.service';
|
||||
import { Video } from '../../models/video.model';
|
||||
import { InfiniteAnchorComponent } from '../shared/infinite-anchor/infinite-anchor.component';
|
||||
@@ -11,19 +11,20 @@ import { Title } from '@angular/platform-browser';
|
||||
import { TranslatePipe } from '../../pipes/translate.pipe';
|
||||
import { LikeButtonComponent } from '../shared/components/like-button/like-button.component';
|
||||
import { AddToPlaylistComponent } from '../shared/components/add-to-playlist/add-to-playlist.component';
|
||||
import { SearchSuggestionsComponent } from './search-suggestions.component';
|
||||
import { SearchService, type SearchResponse } from '../../app/search/search.service';
|
||||
import type { ProviderId } from '../../app/core/providers/provider-registry';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
standalone: true,
|
||||
templateUrl: './search.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, RouterLink, InfiniteAnchorComponent, TranslatePipe, LikeButtonComponent, AddToPlaylistComponent, SearchSuggestionsComponent]
|
||||
imports: [CommonModule, RouterLink, InfiniteAnchorComponent, TranslatePipe, LikeButtonComponent, AddToPlaylistComponent]
|
||||
})
|
||||
export class SearchComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private api = inject(YoutubeApiService);
|
||||
private instances = inject(InstanceService);
|
||||
private history = inject(HistoryService);
|
||||
@@ -46,10 +47,16 @@ export class SearchComponent {
|
||||
providerParam = signal<Provider | null>(null);
|
||||
themeParam = signal<string | null>(null);
|
||||
providersListParam = signal<ProviderId[] | 'all' | null>(null);
|
||||
pageParam = signal<number>(1);
|
||||
sortParam = signal<'relevance' | 'date' | 'views'>('relevance');
|
||||
|
||||
// Unified multi-provider response (grouped suggestions)
|
||||
groups = signal<Record<ProviderId, any[]>>({} as any);
|
||||
showUnified = signal<boolean>(false);
|
||||
error = signal<string | null>(null);
|
||||
|
||||
// Dedup key to avoid recording the same search multiple times
|
||||
private lastRecordedKey: string | null = null;
|
||||
|
||||
hasQuery = computed(() => this.q().length > 0);
|
||||
providerLabel = computed(() => {
|
||||
@@ -60,7 +67,33 @@ export class SearchComponent {
|
||||
}
|
||||
return this.instances.selectedProviderLabel();
|
||||
});
|
||||
pageHeading = computed(() => `Search - ${this.providerLabel()}${this.q() ? ' - ' + this.q() : ''}`);
|
||||
|
||||
// Active providers derived from `providers` query param or fallback to all available
|
||||
activeProviders = computed<ProviderId[]>(() => {
|
||||
const p = this.providersListParam();
|
||||
if (p === 'all' || p == null) {
|
||||
try { return (this.instances.providers() || []).map(x => x.id as unknown as ProviderId); }
|
||||
catch { return ['yt','dm','tw','pt','od','ru']; }
|
||||
}
|
||||
if (Array.isArray(p) && p.length > 0) return p as ProviderId[];
|
||||
return ['yt','dm','tw','pt','od','ru'];
|
||||
});
|
||||
|
||||
// Subtitle display: codes separated by middot
|
||||
activeProvidersDisplay = computed(() => this.activeProviders().join(' · '));
|
||||
|
||||
// Helper to display provider label from registry/instances
|
||||
providerDisplay(p: ProviderId): string {
|
||||
try {
|
||||
const found = (this.instances.providers() || []).find(x => (x.id as any) === p);
|
||||
return found?.label || p.toUpperCase();
|
||||
} catch { return p.toUpperCase(); }
|
||||
}
|
||||
// Title: Search ${q ? ` — “${q}”` : ''}
|
||||
pageHeading = computed(() => {
|
||||
const q = this.q();
|
||||
return `Search ${q ? ` — “${q}”` : ''}`;
|
||||
});
|
||||
// Public computed used by the template to avoid referencing private `instances`
|
||||
selectedProviderForView = computed(() => this.providerParam() || this.instances.selectedProvider());
|
||||
|
||||
@@ -68,6 +101,16 @@ export class SearchComponent {
|
||||
// For Twitch provider: 'twitch_all' | 'twitch_live' | 'twitch_vod'.
|
||||
filterTag = signal<string>('all');
|
||||
|
||||
// Unified total results count (sum of groups lengths)
|
||||
unifiedTotal = computed(() => {
|
||||
const g = this.groups();
|
||||
try {
|
||||
return Object.values(g).reduce((acc: number, arr: any) => acc + (Array.isArray(arr) ? arr.length : 0), 0);
|
||||
} catch { return 0; }
|
||||
});
|
||||
|
||||
canPrev = computed(() => (this.pageParam() || 1) > 1);
|
||||
|
||||
// Helper computed: filtered list based on active tag (non-Twitch only)
|
||||
filteredResults = computed(() => {
|
||||
const tag = this.filterTag();
|
||||
@@ -247,24 +290,29 @@ export class SearchComponent {
|
||||
});
|
||||
|
||||
// Subscribe once to unified multi-provider results and reflect to UI
|
||||
this.unified.request$.subscribe({
|
||||
this.unified.request$.pipe(takeUntilDestroyed()).subscribe({
|
||||
next: (resp: SearchResponse) => {
|
||||
this.groups.set(resp.groups as any);
|
||||
this.loading.set(false);
|
||||
this.error.set(null);
|
||||
},
|
||||
error: () => {
|
||||
this.groups.set({} as any);
|
||||
this.loading.set(false);
|
||||
this.error.set('Le service de recherche est temporairement indisponible. Réessayer.');
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to query param changes (so subsequent searches update)
|
||||
this.route.queryParamMap.subscribe((pm) => {
|
||||
this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe((pm) => {
|
||||
const q = (pm.get('q') || '').trim();
|
||||
this.q.set(q);
|
||||
const prov = (pm.get('provider') as Provider) || null;
|
||||
const prov = (pm.get('provider') as Provider) || (pm.get('p') as Provider) || null;
|
||||
const theme = pm.get('theme');
|
||||
const providersParam = (pm.get('providers') || '').trim();
|
||||
const page = Number(pm.get('page') || '1');
|
||||
const pageSize = Number(pm.get('pageSize') || '24');
|
||||
const sort = pm.get('sort') || undefined;
|
||||
// Parse providers CSV if present
|
||||
let providersList: ProviderId[] | 'all' | null = null;
|
||||
if (providersParam) {
|
||||
@@ -274,6 +322,13 @@ export class SearchComponent {
|
||||
this.providerParam.set(prov);
|
||||
this.themeParam.set(theme);
|
||||
this.providersListParam.set(providersList);
|
||||
const pageNum = isFinite(page) ? page : 1;
|
||||
this.pageParam.set(pageNum);
|
||||
this.unified.setPage(pageNum);
|
||||
if (isFinite(pageSize)) this.unified.setPageSize(pageSize);
|
||||
const sortNorm = (sort === 'date' || sort === 'views') ? sort : 'relevance';
|
||||
this.sortParam.set(sortNorm);
|
||||
this.unified.setSort(sortNorm);
|
||||
|
||||
if (q) {
|
||||
this.notice.set(null);
|
||||
@@ -285,15 +340,33 @@ export class SearchComponent {
|
||||
const providersToUse = providersList || [provider as ProviderId];
|
||||
this.unified.setQuery(q);
|
||||
this.unified.setProviders(providersToUse);
|
||||
|
||||
// Record this search once per unique (q, providers) combination
|
||||
try {
|
||||
const key = `${q}|${Array.isArray(providersToUse) ? providersToUse.join(',') : String(providersToUse)}`;
|
||||
if (this.lastRecordedKey !== key) {
|
||||
this.lastRecordedKey = key;
|
||||
const recFilters: any = { provider, providers: Array.isArray(providersToUse) ? providersToUse : 'all' };
|
||||
this.history.recordSearch(q, recFilters).subscribe({
|
||||
next: () => {},
|
||||
error: (err) => console.error('Error recording search:', err)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in recordSearch:', err);
|
||||
}
|
||||
|
||||
this.showUnified.set(true);
|
||||
this.loading.set(true);
|
||||
this.groups.set({} as any); // Clear previous results
|
||||
this.error.set(null);
|
||||
} else {
|
||||
this.results.set([]);
|
||||
this.nextCursor.set(null);
|
||||
this.loading.set(false);
|
||||
this.groups.set({} as any);
|
||||
this.showUnified.set(false);
|
||||
this.error.set(null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -464,30 +537,21 @@ export class SearchComponent {
|
||||
return qp;
|
||||
}
|
||||
|
||||
// Submit handler from the embedded SearchBox: keep URL as source of truth
|
||||
onSearchBarSubmit(evt: { q: string; providers: ProviderId[] | 'all' }) {
|
||||
const q = (evt?.q || '').trim();
|
||||
if (!q) return;
|
||||
const provider = this.providerParam() || this.instances.selectedProvider();
|
||||
const qp: any = { q, p: provider, provider: null };
|
||||
if (Array.isArray(evt.providers)) qp.providers = evt.providers.join(',');
|
||||
else if (evt.providers === 'all') qp.providers = 'yt,dm,tw,pt,od,ru';
|
||||
qp.page = 1; // reset pagination on new search
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: qp, queryParamsHandling: 'merge' });
|
||||
}
|
||||
|
||||
@ViewChild('searchInput') searchInput?: ElementRef<HTMLInputElement>;
|
||||
|
||||
onSearchEnter(event: Event) {
|
||||
const keyboardEvent = event as KeyboardEvent;
|
||||
keyboardEvent.preventDefault();
|
||||
const query = this.q().trim();
|
||||
if (query) {
|
||||
// Route unified search regardless of providers param
|
||||
const provParam = this.providersListParam();
|
||||
const provider = this.providerParam() || this.instances.selectedProvider();
|
||||
const providersToUse = (provParam && (provParam === 'all' || (Array.isArray(provParam) && provParam.length > 0)))
|
||||
? (provParam as any)
|
||||
: [provider as unknown as ProviderId];
|
||||
this.unified.setQuery(query);
|
||||
this.unified.setProviders(providersToUse);
|
||||
this.showUnified.set(true);
|
||||
this.loading.set(true);
|
||||
this.closeSearchResults();
|
||||
// Retirer le focus de l'input
|
||||
if (this.searchInput) {
|
||||
this.searchInput.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy input handlers removed; SearchBox handles keyboard and submit
|
||||
|
||||
closeSearchResults() {
|
||||
// Cette méthode sera appelée pour fermer les résultats de recherche
|
||||
@@ -506,4 +570,29 @@ export class SearchComponent {
|
||||
setFilterTag(key: string) {
|
||||
this.filterTag.set(key);
|
||||
}
|
||||
|
||||
// Pagination controls
|
||||
nextPage() {
|
||||
const next = (this.pageParam() || 1) + 1;
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: { page: next }, queryParamsHandling: 'merge' });
|
||||
}
|
||||
prevPage() {
|
||||
const prev = Math.max(1, (this.pageParam() || 1) - 1);
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: { page: prev }, queryParamsHandling: 'merge' });
|
||||
}
|
||||
|
||||
// Sort control
|
||||
onSortChange(event: Event) {
|
||||
const value = (event.target as HTMLSelectElement)?.value as 'relevance' | 'date' | 'views' | '';
|
||||
const sort = (value === 'date' || value === 'views') ? value : 'relevance';
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: { sort, page: 1 }, queryParamsHandling: 'merge' });
|
||||
}
|
||||
|
||||
// CTA handler: navigate to focus on a single provider while preserving q
|
||||
onViewAll(p: ProviderId) {
|
||||
const q = (this.q() || '').trim();
|
||||
const qp: any = { provider: p, p, providers: p, page: 1 };
|
||||
if (q) qp.q = q;
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: qp, queryParamsHandling: 'merge' });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user