import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage'; import { getRandomSongs } from '../../api/subsonicLibrary'; import type { SubsonicSong } from '../../api/subsonicTypes'; import { songToTrack } from '../../utils/playback/songToTrack'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../../store/authStore'; import { usePlayerStore } from '../../store/playerStore'; import SongRail from '../SongRail'; import { playSongNow } from '../../utils/playback/playSong'; import { ndListSongs, ndInvalidateSongsCache } from '../../api/navidromeBrowse'; import { usePerfProbeFlags } from '../../utils/perf/perfFlags'; import { useNavigateToAlbum } from '../../hooks/useNavigateToAlbum'; import { useNavigateToArtist } from '../../hooks/useNavigateToArtist'; import { OpenArtistRefInline } from '../OpenArtistRefInline'; import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs'; const RANDOM_RAIL_SIZE = 18; const RATED_RAIL_FETCH = 60; const RATED_RAIL_DISPLAY = 30; const RATED_RAIL_CACHE_MS = 60_000; const TRACKS_SONG_RAIL_WINDOWING = true; const TRACKS_SONG_RAIL_INITIAL_ARTWORK_BUDGET = 14; /** Tracks hub hero + song rails (above the browse-all list). */ export default function TracksPageChrome({ onLayoutReady, hideDiscoveryChrome = false, }: { /** Fires once when hero + rails finish their initial load (or fail). */ onLayoutReady?: () => void; /** When true, skip hero and song rails (active scoped search). */ hideDiscoveryChrome?: boolean; }) { const perfFlags = usePerfProbeFlags(); const { t } = useTranslation(); const navigateToArtist = useNavigateToArtist(); const navigateToAlbum = useNavigateToAlbum(); const activeServerId = useAuthStore(s => s.activeServerId); const enqueue = usePlayerStore(s => s.enqueue); const [hero, setHero] = useState(null); const [heroLoading, setHeroLoading] = useState(false); const [random, setRandom] = useState([]); const [randomLoading, setRandomLoading] = useState(true); const [rated, setRated] = useState([]); const [ratedLoading, setRatedLoading] = useState(true); const [ratedSupported, setRatedSupported] = useState(true); const layoutReadyNotifiedRef = useRef(false); const rerollHero = useCallback(async () => { setHeroLoading(true); try { const picks = await getRandomSongs(1); if (picks[0]) setHero(picks[0]); } finally { setHeroLoading(false); } }, []); const rerollRandom = useCallback(async () => { setRandomLoading(true); try { setRandom(await getRandomSongs(RANDOM_RAIL_SIZE)); } finally { setRandomLoading(false); } }, []); const reloadRated = useCallback(async () => { setRatedLoading(true); try { const songs = await ndListSongs(0, RATED_RAIL_FETCH, 'rating', 'DESC', RATED_RAIL_CACHE_MS); const filtered = songs.filter(s => (s.userRating ?? 0) > 0).slice(0, RATED_RAIL_DISPLAY); setRated(filtered); setRatedSupported(true); } catch { setRated([]); setRatedSupported(false); } finally { setRatedLoading(false); } }, []); useEffect(() => { if (!activeServerId || hideDiscoveryChrome) return; rerollHero(); rerollRandom(); reloadRated(); }, [activeServerId, hideDiscoveryChrome, rerollHero, rerollRandom, reloadRated]); useEffect(() => { if (!onLayoutReady || layoutReadyNotifiedRef.current) return; if (hideDiscoveryChrome || !activeServerId) { layoutReadyNotifiedRef.current = true; onLayoutReady(); return; } if (heroLoading || randomLoading || ratedLoading) return; layoutReadyNotifiedRef.current = true; onLayoutReady(); }, [activeServerId, hideDiscoveryChrome, onLayoutReady, heroLoading, randomLoading, ratedLoading]); const railSongs = useMemo( () => (hero ? random.filter(s => s.id !== hero.id) : random), [random, hero], ); const heroArtistRefs = hero ? resolveTrackArtistRefs(hero) : []; return ( <> {!perfFlags.disableMainstageStickyHeader && (

{t('tracks.title')}

{!hideDiscoveryChrome && (

{t('tracks.subtitle')}

)}
)} {!perfFlags.disableMainstageHero && !hideDiscoveryChrome && hero && (
{hero.albumId && hero.coverArt ? ( ) : (
)}
{t('tracks.heroEyebrow')}

{hero.title}

navigateToArtist(id)} as="none" linkTag="span" linkClassName="track-artist-link" separatorClassName="track-artist-sep" /> {hero.album && ( <> ยท hero.albumId && navigateToAlbum(hero.albumId)} >{hero.album} )}

)} {!perfFlags.disableMainstageRails && !hideDiscoveryChrome && ratedSupported && (ratedLoading || rated.length > 0) && ( { ndInvalidateSongsCache(); return reloadRated(); }} windowArtworkByViewport={TRACKS_SONG_RAIL_WINDOWING} initialArtworkBudget={TRACKS_SONG_RAIL_INITIAL_ARTWORK_BUDGET} /> )} {!perfFlags.disableMainstageRails && !hideDiscoveryChrome && ( )} ); }