import React, { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Headphones, Zap, Music2, Music, Cpu, Mic, Radio, Cloud, Leaf, Heart, Sun, Flame, Film, Globe, BookOpen, Podcast, Star, Tags, type LucideIcon, } from 'lucide-react'; import { getGenres, SubsonicGenre } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; function getGenreIcon(name: string): LucideIcon { const n = name.toLowerCase(); if (/ambient|drone|new age/.test(n)) return Cloud; if (/metal|hardcore|thrash|death|grind|doom/.test(n)) return Zap; if (/rock/.test(n)) return Radio; if (/jazz/.test(n)) return Music2; if (/classical|orchestra|chamber|baroque|opera|symphon/.test(n)) return Music; if (/electronic|techno|edm|house|trance|electro|synth/.test(n)) return Cpu; if (/hip.?hop|rap/.test(n)) return Mic; if (/pop/.test(n)) return Star; if (/folk|country|bluegrass|americana/.test(n)) return Leaf; if (/blues/.test(n)) return Music2; if (/soul|r.?b|funk|gospel/.test(n)) return Heart; if (/reggae|ska|dub/.test(n)) return Sun; if (/punk/.test(n)) return Flame; if (/soundtrack|score|ost|film|movie|cinema/.test(n)) return Film; if (/world|latin|afro|celtic|tribal|traditional/.test(n)) return Globe; if (/audiobook|spoken|hörbuch|speech|comedy/.test(n)) return BookOpen; if (/podcast/.test(n)) return Podcast; return Headphones; } const CTP_COLORS = [ 'var(--ctp-rosewater)', 'var(--ctp-flamingo)', 'var(--ctp-pink)', 'var(--ctp-mauve)', 'var(--ctp-red)', 'var(--ctp-maroon)', 'var(--ctp-peach)', 'var(--ctp-yellow)', 'var(--ctp-green)', 'var(--ctp-teal)', 'var(--ctp-sky)', 'var(--ctp-sapphire)', 'var(--ctp-blue)', 'var(--ctp-lavender)', ]; function genreColor(name: string): string { let h = 0; for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0; return CTP_COLORS[h % CTP_COLORS.length]; } const SCROLL_KEY = 'genres-scroll'; export default function Genres() { const { t } = useTranslation(); const navigate = useNavigate(); const [genres, setGenres] = useState([]); const [loading, setLoading] = useState(true); const containerRef = useRef(null); useEffect(() => { getGenres() .then(data => { const sorted = [...data].sort((a, b) => b.albumCount - a.albumCount); setGenres(sorted); }) .finally(() => setLoading(false)); }, []); // getGenres is not folder-scoped — no dep on musicLibraryFilterVersion // Restore scroll position after genres are rendered useEffect(() => { if (loading || genres.length === 0) return; const saved = sessionStorage.getItem(SCROLL_KEY); if (!saved) return; const pos = parseInt(saved, 10); sessionStorage.removeItem(SCROLL_KEY); requestAnimationFrame(() => { if (containerRef.current) containerRef.current.scrollTop = pos; }); }, [loading, genres.length]); const handleGenreClick = (genreValue: string) => { if (containerRef.current) { sessionStorage.setItem(SCROLL_KEY, String(containerRef.current.scrollTop)); } navigate(`/genres/${encodeURIComponent(genreValue)}`); }; return (

{t('genres.title')}

{!loading && genres.length > 0 && ( {genres.length} {t('genres.genreCount')} )}
{loading &&

{t('genres.loading')}

} {!loading && genres.length === 0 &&

{t('genres.empty')}

} {!loading && genres.length > 0 && (
{genres.map(genre => { const Icon = getGenreIcon(genre.value); const color = genreColor(genre.value); return (
handleGenreClick(genre.value)} role="button" tabIndex={0} onKeyDown={e => e.key === 'Enter' && handleGenreClick(genre.value)} data-tooltip={genre.value} >

{genre.value}

{t('genres.albumCount', { count: genre.albumCount })}

); })}
)}
); }