import React, { useEffect, useState } from 'react'; import Hero from '../components/Hero'; import AlbumRow from '../components/AlbumRow'; import SongRail from '../components/SongRail'; import { getAlbumList, getArtists, getRandomSongs, SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { NavLink, useNavigate } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; import { useHomeStore } from '../store/homeStore'; import { useAuthStore } from '../store/authStore'; import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mixRatingFilter'; /** Match Random Albums overshoot when mix filter uses album/artist axes so hero + discover row can still fill. */ const HOME_RANDOM_FETCH = 100; const HOME_HERO_COUNT = 8; const HOME_DISCOVER_SLICE = 20; const HOME_DISCOVER_SONGS_SIZE = 18; export default function Home() { const homeSections = useHomeStore(s => s.sections); const activeServerId = useAuthStore(s => s.activeServerId); const musicLibraryFilterVersion = useAuthStore(s => s.musicLibraryFilterVersion); const mixMinRatingFilterEnabled = useAuthStore(s => s.mixMinRatingFilterEnabled); const mixMinRatingAlbum = useAuthStore(s => s.mixMinRatingAlbum); const mixMinRatingArtist = useAuthStore(s => s.mixMinRatingArtist); const isVisible = (id: string) => homeSections.find(s => s.id === id)?.visible ?? true; const [starred, setStarred] = useState([]); const [recent, setRecent] = useState([]); const [random, setRandom] = useState([]); const [heroAlbums, setHeroAlbums] = useState([]); const [mostPlayed, setMostPlayed] = useState([]); const [recentlyPlayed, setRecentlyPlayed] = useState([]); const [randomArtists, setRandomArtists] = useState([]); const [discoverSongs, setDiscoverSongs] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; setLoading(true); (async () => { try { const mixCfg = getMixMinRatingsConfigFromAuth(); const albumMix = mixCfg.enabled && (mixCfg.minAlbum > 0 || mixCfg.minArtist > 0); const randomSize = albumMix ? HOME_RANDOM_FETCH : HOME_DISCOVER_SLICE; const [s, n, rRaw, f, rp, artists, songs] = await Promise.all([ getAlbumList('starred', 12).catch(() => []), getAlbumList('newest', 12).catch(() => []), getAlbumList('random', randomSize).catch(() => []), getAlbumList('frequent', 12).catch(() => []), getAlbumList('recent', 12).catch(() => []), isVisible('discoverArtists') ? getArtists().catch(() => []) : Promise.resolve([]), isVisible('discoverSongs') ? getRandomSongs(HOME_DISCOVER_SONGS_SIZE).catch(() => [] as SubsonicSong[]) : Promise.resolve([]), ]); if (cancelled) return; const r = await filterAlbumsByMixRatings(rRaw, mixCfg); setStarred(s); setRecent(n); setHeroAlbums(r.slice(0, HOME_HERO_COUNT)); setRandom(r.slice(HOME_HERO_COUNT, HOME_DISCOVER_SLICE)); setMostPlayed(f); setRecentlyPlayed(rp); setDiscoverSongs(songs); const shuffled = [...artists]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } setRandomArtists(shuffled.slice(0, 16)); } catch { /* ignore */ } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [ activeServerId, musicLibraryFilterVersion, homeSections, mixMinRatingFilterEnabled, mixMinRatingAlbum, mixMinRatingArtist, ]); const loadMore = async ( type: 'starred' | 'newest' | 'random' | 'frequent' | 'recent', currentList: SubsonicAlbum[], setter: React.Dispatch> ) => { try { const more = await getAlbumList(type, 12, currentList.length); const mixCfg = getMixMinRatingsConfigFromAuth(); const batch = type === 'random' ? await filterAlbumsByMixRatings(more, mixCfg) : more; const newItems = batch.filter(m => !currentList.find(c => c.id === m.id)); if (newItems.length > 0) setter(prev => [...prev, ...newItems]); } catch (e) { console.error('Failed to load more', e); } }; const { t } = useTranslation(); const navigate = useNavigate(); return (
{isVisible('hero') && }
{loading ? (
) : ( <> {isVisible('recent') && ( loadMore('newest', recent, setRecent)} moreText={t('home.loadMore')} /> )} {isVisible('discover') && ( loadMore('random', random, setRandom)} moreText={t('home.discoverMore')} /> )} {isVisible('discoverSongs') && discoverSongs.length > 0 && ( )} {isVisible('discoverArtists') && randomArtists.length > 0 && (
{t('home.discoverArtists')}
{randomArtists.map(a => ( ))}
)} {isVisible('recentlyPlayed') && recentlyPlayed.length > 0 && ( loadMore('recent', recentlyPlayed, setRecentlyPlayed)} moreText={t('home.loadMore')} /> )} {isVisible('starred') && starred.length > 0 && ( loadMore('starred', starred, setStarred)} moreText={t('home.loadMore')} /> )} {isVisible('mostPlayed') && ( loadMore('frequent', mostPlayed, setMostPlayed)} moreText={t('home.loadMore')} /> )} )}
); }