feat(home): integrate genre filter into HomeView

- Add GenreFilterButton to home header
- Pass genre_id to fetchFeaturedAlbums API calls
- Reload home content when genre filter changes
- Featured sections now filter by selected genre
This commit is contained in:
vicrodh committed 2026-02-02 08:53:13 -06:00
1 parent 0d0da7c5fc
commit 8428fafaa5
1 file changed
+34 -11
+34 -11
View File
@@ -7,6 +7,7 @@
import AlbumCard from '../AlbumCard.svelte'; import AlbumCard from '../AlbumCard.svelte';
import TrackRow from '../TrackRow.svelte'; import TrackRow from '../TrackRow.svelte';
import HomeSettingsModal from '../HomeSettingsModal.svelte'; import HomeSettingsModal from '../HomeSettingsModal.svelte';
import GenreFilterButton from '../GenreFilterButton.svelte';
import { formatDuration, formatQuality, getQobuzImage } from '$lib/adapters/qobuzAdapters'; import { formatDuration, formatQuality, getQobuzImage } from '$lib/adapters/qobuzAdapters';
import { import {
subscribe as subscribeHomeSettings, subscribe as subscribeHomeSettings,
@@ -15,6 +16,10 @@
type HomeSettings, type HomeSettings,
type HomeSectionId type HomeSectionId
} from '$lib/stores/homeSettingsStore'; } from '$lib/stores/homeSettingsStore';
import {
getSelectedGenreId,
hasActiveFilter as hasGenreFilter
} from '$lib/stores/genreFilterStore';
import { setPlaybackContext } from '$lib/stores/playbackContextStore'; import { setPlaybackContext } from '$lib/stores/playbackContextStore';
import type { QobuzAlbum, QobuzArtist, QobuzTrack, DisplayTrack } from '$lib/types'; import type { QobuzAlbum, QobuzArtist, QobuzTrack, DisplayTrack } from '$lib/types';
@@ -317,11 +322,12 @@
total: number; total: number;
} }
async function fetchFeaturedAlbums(featuredType: string, limit: number): Promise<AlbumCardData[]> { async function fetchFeaturedAlbums(featuredType: string, limit: number, genreId?: number): Promise<AlbumCardData[]> {
try { try {
const response = await invoke<FeaturedAlbumsResponse>('get_featured_albums', { const response = await invoke<FeaturedAlbumsResponse>('get_featured_albums', {
featuredType, featuredType,
limit limit,
genreId: genreId ?? null
}); });
return response.items.map(toAlbumCard); return response.items.map(toAlbumCard);
} catch (err) { } catch (err) {
@@ -434,6 +440,11 @@
.slice(0, homeLimits.topArtists); .slice(0, homeLimits.topArtists);
} }
function handleGenreFilterChange() {
// Reload home page with new genre filter
loadHome();
}
async function loadHome() { async function loadHome() {
isInitializing = true; isInitializing = true;
isOverlayVisible = true; isOverlayVisible = true;
@@ -469,9 +480,12 @@
limitFavorites: Math.max(homeLimits.favoriteAlbums, homeLimits.favoriteTracks) limitFavorites: Math.max(homeLimits.favoriteAlbums, homeLimits.favoriteTracks)
}); });
// Get current genre filter
const genreId = getSelectedGenreId();
// Start Qobuz API calls in parallel (don't await) // Start Qobuz API calls in parallel (don't await)
if (isSectionVisible('newReleases')) { if (isSectionVisible('newReleases')) {
fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums).then(async albums => { fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums, genreId).then(async albums => {
newReleases = albums; newReleases = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingNewReleases = false; loadingNewReleases = false;
@@ -482,7 +496,7 @@
} }
if (isSectionVisible('pressAwards')) { if (isSectionVisible('pressAwards')) {
fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums).then(async albums => { fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums, genreId).then(async albums => {
pressAwards = albums; pressAwards = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingPressAwards = false; loadingPressAwards = false;
@@ -493,7 +507,7 @@
} }
if (isSectionVisible('mostStreamed')) { if (isSectionVisible('mostStreamed')) {
fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums).then(async albums => { fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums, genreId).then(async albums => {
mostStreamed = albums; mostStreamed = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingMostStreamed = false; loadingMostStreamed = false;
@@ -504,7 +518,7 @@
} }
if (isSectionVisible('qobuzissimes')) { if (isSectionVisible('qobuzissimes')) {
fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums).then(async albums => { fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums, genreId).then(async albums => {
qobuzissimes = albums; qobuzissimes = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingQobuzissimes = false; loadingQobuzissimes = false;
@@ -515,7 +529,7 @@
} }
if (isSectionVisible('editorPicks')) { if (isSectionVisible('editorPicks')) {
fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums).then(async albums => { fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums, genreId).then(async albums => {
editorPicks = albums; editorPicks = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingEditorPicks = false; loadingEditorPicks = false;
@@ -610,16 +624,19 @@
</div> </div>
{/if} {/if}
<!-- Header with greeting and settings --> <!-- Header with greeting, filter and settings -->
<div class="home-header"> <div class="home-header">
{#if homeSettings.greeting.enabled} {#if homeSettings.greeting.enabled}
<h2 class="greeting">{greetingText}</h2> <h2 class="greeting">{greetingText}</h2>
{:else} {:else}
<div></div> <div></div>
{/if} {/if}
<button class="settings-btn" onclick={() => isSettingsModalOpen = true} title={$t('home.customizeHome')}> <div class="header-actions">
<img src="/home-gear.svg" alt="Settings" class="settings-icon" /> <GenreFilterButton onFilterChange={handleGenreFilterChange} />
</button> <button class="settings-btn" onclick={() => isSettingsModalOpen = true} title={$t('home.customizeHome')}>
<img src="/home-gear.svg" alt="Settings" class="settings-icon" />
</button>
</div>
</div> </div>
{#if isInitializing && !isOverlayVisible} {#if isInitializing && !isOverlayVisible}
@@ -1057,6 +1074,12 @@
margin: 0; margin: 0;
} }
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.settings-btn { .settings-btn {
display: flex; display: flex;
align-items: center; align-items: center;