import Cover from '@/components/Cover' import Skeleton from '@/components/Skeleton' import SvgIcon from '@/components/SvgIcon' import { prefetchAlbum } from '@/hooks/useAlbum' import { prefetchPlaylist } from '@/hooks/usePlaylist' import { formatDate, resizeImage } from '@/utils/common' export enum Subtitle { COPYWRITER = 'copywriter', CREATOR = 'creator', TYPE_RELEASE_YEAR = 'type+releaseYear', ARTIST = 'artist', } const Title = ({ title, seeMoreLink, }: { title: string seeMoreLink: string }) => { return (
{title}
{seeMoreLink && (
See More
)}
) } const getSubtitleText = ( item: Album | Playlist | Artist, subtitle: Subtitle ) => { const nickname = 'creator' in item ? item.creator.nickname : 'someone' const releaseYear = 'publishTime' in item ? formatDate(item.publishTime ?? 0, 'en', 'YYYY') : 'unknown' const types = { playlist: 'playlist', album: 'Album', 专辑: 'Album', Single: 'Single', 'EP/Single': 'EP', EP: 'EP', unknown: 'unknown', } const type = 'type' in item ? item.type || 'unknown' : 'unknown' const artist = 'artist' in item ? item.artist.name : 'unknown' const table = { [Subtitle.CREATOR]: `by ${nickname}`, [Subtitle.TYPE_RELEASE_YEAR]: `${types[type]} · ${releaseYear}`, [Subtitle.ARTIST]: artist, } return table[subtitle] ?? item[subtitle] } const getImageUrl = (item: Album | Playlist | Artist) => { let cover: string | undefined = '' if ('coverImgUrl' in item) cover = item.coverImgUrl if ('picUrl' in item) cover = item.picUrl if ('img1v1Url' in item) cover = item.img1v1Url return resizeImage(cover || '', 'md') } const CoverRow = ({ title, albums, artists, playlists, subtitle = Subtitle.COPYWRITER, seeMoreLink, isSkeleton, className, }: { title?: string albums?: Album[] artists?: Artist[] playlists?: Playlist[] subtitle?: Subtitle seeMoreLink?: string isSkeleton?: boolean className?: string }) => { const renderItems = useMemo(() => { if (isSkeleton) { return new Array(10).fill({}) as Array } return albums ?? playlists ?? artists ?? [] }, [albums, artists, isSkeleton, playlists]) const navigate = useNavigate() const goTo = (id: number) => { if (isSkeleton) return if (albums) navigate(`/album/${id}`) if (playlists) navigate(`/playlist/${id}`) if (artists) navigate(`/artist/${id}`) } const prefetch = (id: number) => { if (albums) prefetchAlbum({ id }) if (playlists) prefetchPlaylist({ id }) } return (
{title && } <div className={classNames( 'grid gap-x-[24px] gap-y-7', className, !className && 'grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6' )} > {renderItems.map((item, index) => ( <div key={item.id ?? index} onMouseOver={() => prefetch(item.id)} className="grid gap-x-[24px] gap-y-7" > <div> {/* Cover */} {isSkeleton ? ( <Skeleton className="box-content aspect-square w-full rounded-xl border border-black border-opacity-0" /> ) : ( <Cover onClick={() => goTo(item.id)} imageUrl={getImageUrl(item)} /> )} {/* Info */} <div className="mt-2"> <div className="font-semibold"> {/* Name */} {isSkeleton ? ( <div className="flex w-full -translate-y-px flex-col"> <Skeleton className="w-full leading-tight"> PLACEHOLDER </Skeleton> <Skeleton className="w-1/3 translate-y-px leading-tight"> PLACEHOLDER </Skeleton> </div> ) : ( <span className="line-clamp-2 leading-tight "> {/* Playlist private icon */} {(item as Playlist).privacy && ( <SvgIcon name="lock" className="mr-1 mb-1 inline-block h-3 w-3 text-gray-300" /> )} <span onClick={() => goTo(item.id)} className="decoration-gray-600 decoration-2 hover:underline dark:text-white" > {item.name} </span> </span> )} </div> {/* Subtitle */} {isSkeleton ? ( <Skeleton className="w-3/5 translate-y-px text-[12px]"> PLACEHOLDER </Skeleton> ) : ( <div className="flex text-[12px] text-gray-500 dark:text-gray-400"> <span>{getSubtitleText(item, subtitle)}</span> </div> )} </div> </div> </div> ))} </div> </div> ) } export default CoverRow