diff --git a/src/lib/components/GenreFilterButton.svelte b/src/lib/components/GenreFilterButton.svelte new file mode 100644 index 000000000..e2885b608 --- /dev/null +++ b/src/lib/components/GenreFilterButton.svelte @@ -0,0 +1,129 @@ + + +
+ + + +
+ + diff --git a/src/lib/components/GenreFilterPopup.svelte b/src/lib/components/GenreFilterPopup.svelte new file mode 100644 index 000000000..9fc6e0ba3 --- /dev/null +++ b/src/lib/components/GenreFilterPopup.svelte @@ -0,0 +1,378 @@ + + + + +{#if isOpen} +
+ + +
+ Remember my selection + +
+ +
+ {#each genres as genre (genre.id)} + + {/each} +
+ + {#if hasActiveFilter()} + + {/if} +
+{/if} + + diff --git a/src/lib/stores/genreFilterStore.ts b/src/lib/stores/genreFilterStore.ts new file mode 100644 index 000000000..b76e303ba --- /dev/null +++ b/src/lib/stores/genreFilterStore.ts @@ -0,0 +1,161 @@ +/** + * Genre filter store for Home page filtering + * Persists selected genres to localStorage + */ + +import { invoke } from '@tauri-apps/api/core'; + +export interface GenreInfo { + id: number; + name: string; + color?: string; + slug?: string; +} + +interface GenreFilterState { + availableGenres: GenreInfo[]; + selectedGenreIds: Set; + isLoading: boolean; + rememberSelection: boolean; +} + +const STORAGE_KEY = 'qbz_genre_filter'; + +let state: GenreFilterState = { + availableGenres: [], + selectedGenreIds: new Set(), + isLoading: false, + rememberSelection: true, +}; + +const listeners: Set<() => void> = new Set(); + +function notify() { + listeners.forEach((fn) => fn()); +} + +function saveToStorage() { + if (!state.rememberSelection) return; + try { + const data = { + selectedGenreIds: Array.from(state.selectedGenreIds), + rememberSelection: state.rememberSelection, + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); + } catch (e) { + console.error('Failed to save genre filter state:', e); + } +} + +function loadFromStorage() { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const data = JSON.parse(stored); + state.selectedGenreIds = new Set(data.selectedGenreIds || []); + state.rememberSelection = data.rememberSelection ?? true; + } + } catch (e) { + console.error('Failed to load genre filter state:', e); + } +} + +export async function loadGenres(): Promise { + if (state.availableGenres.length > 0) return; // Already loaded + + state.isLoading = true; + notify(); + + try { + const genres = await invoke('get_genres', {}); + state.availableGenres = genres; + + // Load saved selection after genres are available + loadFromStorage(); + + // Validate saved selection against available genres + const validIds = new Set(genres.map((g) => g.id)); + const validSelection = new Set(); + state.selectedGenreIds.forEach((id) => { + if (validIds.has(id)) { + validSelection.add(id); + } + }); + state.selectedGenreIds = validSelection; + } catch (e) { + console.error('Failed to load genres:', e); + state.availableGenres = []; + } finally { + state.isLoading = false; + notify(); + } +} + +export function getGenreFilterState(): GenreFilterState { + return { ...state, selectedGenreIds: new Set(state.selectedGenreIds) }; +} + +export function getAvailableGenres(): GenreInfo[] { + return state.availableGenres; +} + +export function getSelectedGenreIds(): Set { + return new Set(state.selectedGenreIds); +} + +export function getSelectedGenreId(): number | undefined { + // For API calls that only support single genre_id + const ids = Array.from(state.selectedGenreIds); + return ids.length === 1 ? ids[0] : undefined; +} + +export function isGenreSelected(genreId: number): boolean { + return state.selectedGenreIds.has(genreId); +} + +export function hasActiveFilter(): boolean { + return state.selectedGenreIds.size > 0; +} + +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(); + notify(); +} + +export function selectGenre(genreId: number): void { + state.selectedGenreIds.clear(); + state.selectedGenreIds.add(genreId); + saveToStorage(); + notify(); +} + +export function clearSelection(): void { + state.selectedGenreIds.clear(); + saveToStorage(); + notify(); +} + +export function setRememberSelection(remember: boolean): void { + state.rememberSelection = remember; + if (remember) { + saveToStorage(); + } else { + localStorage.removeItem(STORAGE_KEY); + } + notify(); +} + +export function subscribe(callback: () => void): () => void { + listeners.add(callback); + return () => listeners.delete(callback); +} + +// Initialize by loading from storage +loadFromStorage();