diff --git a/src/lib/components/GenreFilterButton.svelte b/src/lib/components/GenreFilterButton.svelte index 95c3e7941..3f941aa9b 100644 --- a/src/lib/components/GenreFilterButton.svelte +++ b/src/lib/components/GenreFilterButton.svelte @@ -6,6 +6,7 @@ hasActiveFilter, getAvailableGenres, getSelectedGenreIds, + getSelectedGenreNames, loadGenres, subscribe as subscribeGenre, type GenreInfo @@ -46,12 +47,11 @@ }); function updateSelectedName() { - const selectedIds = getSelectedGenreIds(); - if (selectedIds.size === 1) { - const genres = getAvailableGenres(); - const selectedId = Array.from(selectedIds)[0]; - const genre = genres.find(g => g.id === selectedId); - selectedGenreName = genre?.name ?? null; + const selectedNames = getSelectedGenreNames(); + if (selectedNames.length === 1) { + selectedGenreName = selectedNames[0]; + } else if (selectedNames.length > 1) { + selectedGenreName = `${selectedNames.length} genres`; } else { selectedGenreName = null; } diff --git a/src/lib/components/GenreFilterPopup.svelte b/src/lib/components/GenreFilterPopup.svelte index 69e16b94a..f7c8106ca 100644 --- a/src/lib/components/GenreFilterPopup.svelte +++ b/src/lib/components/GenreFilterPopup.svelte @@ -81,6 +81,7 @@ function handleClearAll() { clearSelection(); + onClose(); } function handleRememberToggle() { diff --git a/src/lib/components/views/HomeView.svelte b/src/lib/components/views/HomeView.svelte index c9be06a61..c4401c9f1 100644 --- a/src/lib/components/views/HomeView.svelte +++ b/src/lib/components/views/HomeView.svelte @@ -18,7 +18,8 @@ } from '$lib/stores/homeSettingsStore'; import { getSelectedGenreId, - getSelectedGenreName, + getSelectedGenreIds, + getSelectedGenreNames, hasActiveFilter as hasGenreFilter } from '$lib/stores/genreFilterStore'; import { setPlaybackContext } from '$lib/stores/playbackContextStore'; @@ -323,7 +324,7 @@ total: number; } - async function fetchFeaturedAlbums(featuredType: string, limit: number, genreId?: number): Promise { + async function fetchFeaturedAlbumsSingle(featuredType: string, limit: number, genreId?: number): Promise { try { const response = await invoke('get_featured_albums', { featuredType, @@ -337,6 +338,33 @@ } } + async function fetchFeaturedAlbums(featuredType: string, limit: number, genreIds: number[]): Promise { + if (genreIds.length === 0) { + // No filter, fetch without genre + return fetchFeaturedAlbumsSingle(featuredType, limit); + } + if (genreIds.length === 1) { + // Single genre, use API filter + return fetchFeaturedAlbumsSingle(featuredType, limit, genreIds[0]); + } + // Multiple genres: fetch each and merge (dedupe by album id) + const perGenreLimit = Math.ceil(limit / genreIds.length) + 2; + const results = await Promise.all( + genreIds.map(gid => fetchFeaturedAlbumsSingle(featuredType, perGenreLimit, gid)) + ); + const seen = new Set(); + const merged: AlbumCardData[] = []; + for (const albums of results) { + for (const album of albums) { + if (!seen.has(album.id)) { + seen.add(album.id); + merged.push(album); + } + } + } + return merged.slice(0, limit); + } + function toAlbumCard(album: QobuzAlbum): AlbumCardData { return { id: album.id, @@ -447,11 +475,13 @@ } function filterAlbumsByGenre(albums: AlbumCardData[]): AlbumCardData[] { - const selectedGenreName = getSelectedGenreName(); - if (!selectedGenreName) return albums; - // Filter albums whose genre contains the selected genre name (case-insensitive) + const selectedGenreNames = getSelectedGenreNames(); + if (selectedGenreNames.length === 0) return albums; + // Filter albums whose genre matches any of the selected genres (case-insensitive) return albums.filter(album => - album.genre.toLowerCase().includes(selectedGenreName.toLowerCase()) + selectedGenreNames.some(genreName => + album.genre.toLowerCase().includes(genreName.toLowerCase()) + ) ); } @@ -490,12 +520,12 @@ limitFavorites: Math.max(homeLimits.favoriteAlbums, homeLimits.favoriteTracks) }); - // Get current genre filter - const genreId = getSelectedGenreId(); + // Get current genre filter (array of IDs for multi-select) + const genreIds = Array.from(getSelectedGenreIds()); // Start Qobuz API calls in parallel (don't await) if (isSectionVisible('newReleases')) { - fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums, genreId).then(async albums => { + fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums, genreIds).then(async albums => { newReleases = albums; await loadAllAlbumDownloadStatuses(albums); loadingNewReleases = false; @@ -506,7 +536,7 @@ } if (isSectionVisible('pressAwards')) { - fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums, genreId).then(async albums => { + fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums, genreIds).then(async albums => { pressAwards = albums; await loadAllAlbumDownloadStatuses(albums); loadingPressAwards = false; @@ -517,7 +547,7 @@ } if (isSectionVisible('mostStreamed')) { - fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums, genreId).then(async albums => { + fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums, genreIds).then(async albums => { mostStreamed = albums; await loadAllAlbumDownloadStatuses(albums); loadingMostStreamed = false; @@ -528,7 +558,7 @@ } if (isSectionVisible('qobuzissimes')) { - fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums, genreId).then(async albums => { + fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums, genreIds).then(async albums => { qobuzissimes = albums; await loadAllAlbumDownloadStatuses(albums); loadingQobuzissimes = false; @@ -539,7 +569,7 @@ } if (isSectionVisible('editorPicks')) { - fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums, genreId).then(async albums => { + fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums, genreIds).then(async albums => { editorPicks = albums; await loadAllAlbumDownloadStatuses(albums); loadingEditorPicks = false; diff --git a/src/lib/stores/genreFilterStore.ts b/src/lib/stores/genreFilterStore.ts index f546ffc90..5347198ee 100644 --- a/src/lib/stores/genreFilterStore.ts +++ b/src/lib/stores/genreFilterStore.ts @@ -116,6 +116,13 @@ export function getSelectedGenreName(): string | undefined { return genre?.name; } +export function getSelectedGenreNames(): string[] { + const ids = Array.from(state.selectedGenreIds); + return ids + .map(id => state.availableGenres.find(g => g.id === id)?.name) + .filter((name): name is string => !!name); +} + export function isGenreSelected(genreId: number): boolean { return state.selectedGenreIds.has(genreId); } @@ -128,8 +135,6 @@ export function toggleGenre(genreId: number): void { if (state.selectedGenreIds.has(genreId)) { state.selectedGenreIds.delete(genreId); } else { - // For now, only allow single selection (API limitation) - state.selectedGenreIds.clear(); state.selectedGenreIds.add(genreId); } saveToStorage();