import { useEffect, useMemo, useState } from 'react' import { libraryAPI, type LibraryWithPreview } from '../api/library' import { playbackAPI, type HistoryItem } from '../api/playback' import type { Library, Media } from '../types' import type { SeriesCard } from '../utils/groupSeries' import { ContinueWatchingSection, HomeCarouselSection, HomeEmptyState, HomeLibrariesSection, HomeLibraryRowSection, HomeLoadingState, } from './HomePageSections' const hasArtwork = (media?: Media | null) => !!(media?.poster_url || media?.backdrop_url) const asArray = (value: unknown): T[] => (Array.isArray(value) ? (value as T[]) : []) export function HomePage() { const [libraries, setLibraries] = useState([]) const [libraryData, setLibraryData] = useState>({}) const [history, setHistory] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { let cancelled = false async function load() { setLoading(true) try { const [libs, hist] = await Promise.all([ libraryAPI .list({ withPreview: true, previewLimit: 20 }) .then((rows) => asArray(rows)) .catch(() => [] as LibraryWithPreview[]), playbackAPI .recentHistory() .then((rows) => asArray(rows)) .catch(() => [] as HistoryItem[]), ]) if (cancelled) return setLibraries(libs) setHistory(hist.filter((h) => h && !h.completed && !!h.media)) const mapData: Record = {} for (const lib of libs) { mapData[lib.id] = { cards: lib.cards ?? [], items: [], total: lib.total ?? 0, } } setLibraryData(mapData) } finally { if (!cancelled) setLoading(false) } } load() return () => { cancelled = true } }, []) // Quick lookup map for libraries const libraryMap = useMemo(() => { const map = new Map() for (const lib of libraries) { map.set(lib.id, lib) } return map }, [libraries]) // Counts lookup const libraryCounts = useMemo(() => { const counts: Record = {} for (const lib of libraries) { counts[lib.id] = libraryData[lib.id]?.total ?? 0 } return counts }, [libraries, libraryData]) // Compute items to show in the Hero Carousel. Only libraries flagged // carousel_enabled contribute; a library's items array is empty for // series-type libs (loaded via /series), so gate on cards instead. const carouselItems = useMemo(() => { const candidateMedia: Media[] = [] const effectiveSelectedIds = libraries .filter((l) => l.carousel_enabled === true) .map((l) => l.id) for (const libId of effectiveSelectedIds) { const data = libraryData[libId] if (data && data.cards.length > 0) { for (const card of data.cards) { if (hasArtwork(card.rep)) { candidateMedia.push(card.rep) } } } } // Fallback to all loaded items with artwork if (candidateMedia.length === 0) { for (const lib of libraries) { const data = libraryData[lib.id] if (data) { for (const card of data.cards) { candidateMedia.push(card.rep) } } } } return candidateMedia.slice(0, 10) }, [libraries, libraryData]) const empty = !loading && libraries.length === 0 && history.length === 0 if (loading) { return } if (empty) { return } return (
{/* 1. 顶部海报轮播区 */} {carouselItems.length > 0 && ( )} {/* 2. 继续观看(若有历史) */} {history.length > 0 && } {/* 3. 媒体库卡片区 */} {libraries.length > 0 && ( )} {/* 4. 各媒体库内容展示行 */}
{libraries.map((lib) => { const cards = libraryData[lib.id]?.cards || [] if (cards.length === 0) return null return ( ) })}
) }