import { useEffect, useMemo, useState } from 'react' import { ChevronLeft, ChevronRight, Info } from 'lucide-react' import type { DiscoverItem } from '../api/discover' import { imageURL } from '../api/client' import { discoverItemSource } from './discoverPageModel' export function ContentRow({ title, items, page = 1, canNext = false, imageVersion, refreshImageVersion, onPageChange, onSelect, }: { title: string items: DiscoverItem[] page?: number canNext?: boolean imageVersion?: string refreshImageVersion?: string onPageChange?: (delta: number) => void onSelect: (item: DiscoverItem) => void }) { return (

{title}

{onPageChange && (
第 {page} 页
)}
{items.map((item, index) => ( ))}
) } export function DiscoverSkeleton() { return (
{[1, 2, 3].map((section) => (
{[1, 2, 3, 4, 5, 6, 7, 8].map((item) => (
))}
))}
) } function DiscoverCard({ item, imageVersion, refreshImageVersion, onSelect, }: { item: DiscoverItem imageVersion?: string refreshImageVersion?: string onSelect: (item: DiscoverItem) => void }) { const source = discoverItemSource(item) const imageCandidates = useMemo( () => [item.poster_url, item.backdrop_url] .map((value) => value?.trim()) .filter((value, index, values): value is string => Boolean(value) && values.indexOf(value) === index), [item.poster_url, item.backdrop_url], ) const [imageIndex, setImageIndex] = useState(0) const [posterRetry, setPosterRetry] = useState(0) const [posterUnavailable, setPosterUnavailable] = useState(false) const posterVersion = [imageVersion, posterRetry > 0 ? `r${posterRetry}` : ''].filter(Boolean).join('-') const activeImage = imageCandidates[imageIndex] ?? '' const shouldRefreshCache = Boolean( (imageVersion && refreshImageVersion === imageVersion) || posterRetry > 0, ) const posterSrc = useMemo( () => imageURL(activeImage, posterVersion, { refreshCache: shouldRefreshCache, retryFailed: true, }), [activeImage, posterVersion, shouldRefreshCache], ) useEffect(() => { setImageIndex(0) setPosterRetry(0) setPosterUnavailable(false) }, [item.poster_url, item.backdrop_url, imageVersion]) useEffect(() => { if (!posterUnavailable) return if (imageIndex + 1 < imageCandidates.length) { const timer = window.setTimeout(() => { setImageIndex((current) => Math.min(current + 1, imageCandidates.length - 1)) setPosterRetry(0) setPosterUnavailable(false) }, 150) return () => window.clearTimeout(timer) } if (posterRetry >= 3) return const timer = window.setTimeout(() => { setPosterRetry((current) => current + 1) setPosterUnavailable(false) }, 1200 * (posterRetry + 1)) return () => window.clearTimeout(timer) }, [imageCandidates.length, imageIndex, posterRetry, posterUnavailable]) const markPosterUnavailable = () => setPosterUnavailable(true) if (!posterSrc || posterUnavailable) return null return ( ) } function discoverKey(item: DiscoverItem, index: number): string { return `${item.source || 'source'}:${item.tmdb_id || item.douban_id || item.bangumi_id || item.title}:${index}` }