mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 21:58:03 +00:00
feat: updates
This commit is contained in:
parent
a1b0bcf4d3
commit
884f3df41a
198 changed files with 4572 additions and 5336 deletions
|
|
@ -1,229 +1,136 @@
|
|||
import Cover from '@/web/components/Cover'
|
||||
import Skeleton from '@/web/components/Skeleton'
|
||||
import Icon from '@/web/components/Icon'
|
||||
import { resizeImage } from '@/web/utils/common'
|
||||
import { cx } from '@emotion/css'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Image from './Image'
|
||||
import { prefetchAlbum } from '@/web/api/hooks/useAlbum'
|
||||
import { prefetchPlaylist } from '@/web/api/hooks/usePlaylist'
|
||||
import { formatDate, resizeImage, scrollToTop } from '@/web/utils/common'
|
||||
import { cx } from '@emotion/css'
|
||||
import { useMemo } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { memo, useCallback } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import ArtistInline from './ArtistsInLine'
|
||||
|
||||
export enum Subtitle {
|
||||
Copywriter = 'copywriter',
|
||||
Creator = 'creator',
|
||||
TypeReleaseYear = 'type+releaseYear',
|
||||
Artist = 'artist',
|
||||
}
|
||||
type ItemTitle = undefined | 'name'
|
||||
type ItemSubTitle = undefined | 'artist' | 'year'
|
||||
|
||||
const Title = ({
|
||||
title,
|
||||
seeMoreLink,
|
||||
const Album = ({
|
||||
album,
|
||||
itemTitle,
|
||||
itemSubtitle,
|
||||
}: {
|
||||
title: string
|
||||
seeMoreLink: string
|
||||
album: Album
|
||||
itemTitle?: ItemTitle
|
||||
itemSubtitle?: ItemSubTitle
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const goTo = () => {
|
||||
navigate(`/album/${album.id}`)
|
||||
}
|
||||
const prefetch = () => {
|
||||
prefetchAlbum({ id: album.id })
|
||||
}
|
||||
|
||||
const title =
|
||||
itemTitle &&
|
||||
{
|
||||
name: album.name,
|
||||
}[itemTitle]
|
||||
|
||||
const subtitle =
|
||||
itemSubtitle &&
|
||||
{
|
||||
artist: (
|
||||
<ArtistInline
|
||||
artists={album.artists}
|
||||
hoverClassName='hover:text-white/50 transition-colors duration-400'
|
||||
/>
|
||||
),
|
||||
year: dayjs(album.publishTime || 0).year(),
|
||||
}[itemSubtitle]
|
||||
|
||||
return (
|
||||
<div className='flex items-baseline justify-between'>
|
||||
<div className='my-4 text-[28px] font-bold text-black dark:text-white'>
|
||||
{title}
|
||||
</div>
|
||||
{seeMoreLink && (
|
||||
<div className='text-13px font-semibold text-gray-600 hover:underline'>
|
||||
See More
|
||||
<div>
|
||||
<Image
|
||||
onClick={goTo}
|
||||
key={album.id}
|
||||
src={resizeImage(album?.picUrl || '', 'md')}
|
||||
className='aspect-square rounded-24'
|
||||
onMouseOver={prefetch}
|
||||
/>
|
||||
{title && (
|
||||
<div className='line-clamp-2 mt-2 text-14 font-medium text-neutral-300'>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
{subtitle && (
|
||||
<div className='mt-1 text-14 font-medium text-neutral-700'>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getSubtitleText = (
|
||||
item: Album | Playlist | Artist,
|
||||
subtitle: Subtitle
|
||||
) => {
|
||||
const nickname = 'creator' in item ? item.creator.nickname : 'someone'
|
||||
const artist =
|
||||
'artist' in item
|
||||
? item.artist.name
|
||||
: 'artists' in item
|
||||
? item.artists?.[0]?.name
|
||||
: 'unknown'
|
||||
const copywriter = 'copywriter' in item ? item.copywriter : 'unknown'
|
||||
const releaseYear =
|
||||
('publishTime' in item &&
|
||||
formatDate(item.publishTime ?? 0, 'en', 'YYYY')) ||
|
||||
'unknown'
|
||||
const Playlist = ({ playlist }: { playlist: Playlist }) => {
|
||||
const navigate = useNavigate()
|
||||
const goTo = useCallback(() => {
|
||||
navigate(`/playlist/${playlist.id}`)
|
||||
}, [navigate, playlist.id])
|
||||
const prefetch = useCallback(() => {
|
||||
prefetchPlaylist({ id: playlist.id })
|
||||
}, [playlist.id])
|
||||
|
||||
const type = {
|
||||
playlist: 'playlist',
|
||||
album: 'Album',
|
||||
专辑: 'Album',
|
||||
Single: 'Single',
|
||||
'EP/Single': 'EP',
|
||||
EP: 'EP',
|
||||
unknown: 'unknown',
|
||||
精选集: 'Collection',
|
||||
}[('type' in item && typeof item.type !== 'number' && item.type) || 'unknown']
|
||||
|
||||
const table = {
|
||||
[Subtitle.Creator]: `by ${nickname}`,
|
||||
[Subtitle.TypeReleaseYear]: `${type} · ${releaseYear}`,
|
||||
[Subtitle.Artist]: artist,
|
||||
[Subtitle.Copywriter]: copywriter,
|
||||
}
|
||||
|
||||
return table[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')
|
||||
return (
|
||||
<Image
|
||||
onClick={goTo}
|
||||
key={playlist.id}
|
||||
src={resizeImage(playlist.coverImgUrl || playlist?.picUrl || '', 'md')}
|
||||
className='aspect-square rounded-24'
|
||||
onMouseOver={prefetch}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const CoverRow = ({
|
||||
title,
|
||||
albums,
|
||||
artists,
|
||||
playlists,
|
||||
subtitle = Subtitle.Copywriter,
|
||||
seeMoreLink,
|
||||
isSkeleton,
|
||||
title,
|
||||
className,
|
||||
rows = 2,
|
||||
navigateCallback, // Callback function when click on the cover/title
|
||||
itemTitle,
|
||||
itemSubtitle,
|
||||
}: {
|
||||
title?: string
|
||||
albums?: Album[]
|
||||
artists?: Artist[]
|
||||
playlists?: Playlist[]
|
||||
subtitle?: Subtitle
|
||||
seeMoreLink?: string
|
||||
isSkeleton?: boolean
|
||||
className?: string
|
||||
rows?: number
|
||||
navigateCallback?: () => void
|
||||
albums?: Album[]
|
||||
playlists?: Playlist[]
|
||||
itemTitle?: ItemTitle
|
||||
itemSubtitle?: ItemSubTitle
|
||||
}) => {
|
||||
const renderItems = useMemo(() => {
|
||||
if (isSkeleton) {
|
||||
return new Array(rows * 5).fill({}) as Array<Album | Playlist | Artist>
|
||||
}
|
||||
return albums ?? playlists ?? artists ?? []
|
||||
}, [albums, artists, isSkeleton, playlists, rows])
|
||||
|
||||
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}`)
|
||||
if (navigateCallback) navigateCallback()
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
const prefetch = (id: number) => {
|
||||
if (albums) prefetchAlbum({ id })
|
||||
if (playlists) prefetchPlaylist({ id })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{title && <Title title={title} seeMoreLink={seeMoreLink ?? ''} />}
|
||||
<div className={className}>
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h4 className='mb-6 text-14 font-bold uppercase dark:text-neutral-300'>
|
||||
{title}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'grid',
|
||||
className,
|
||||
!className &&
|
||||
'grid-cols-3 gap-x-6 gap-y-7 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-6 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)}
|
||||
showPlayButton={true}
|
||||
roundedClass={artists ? 'rounded-full' : 'rounded-xl'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 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={cx(
|
||||
'line-clamp-2 leading-tight',
|
||||
artists && 'mt-3 text-center'
|
||||
)}
|
||||
>
|
||||
{/* Playlist private icon */}
|
||||
{(item as Playlist).privacy === 10 && (
|
||||
<Icon
|
||||
name='lock'
|
||||
className='mr-1 mb-1 inline-block h-3 w-3 text-gray-300'
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Explicit icon */}
|
||||
{(item as Album)?.mark === 1056768 && (
|
||||
<Icon
|
||||
name='explicit'
|
||||
className='float-right mt-[2px] h-4 w-4 text-gray-300'
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Name */}
|
||||
<span
|
||||
onClick={() => goTo(item.id)}
|
||||
className='decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200'
|
||||
>
|
||||
{item.name}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Subtitle */}
|
||||
{isSkeleton ? (
|
||||
<Skeleton className='w-3/5 translate-y-px text-[12px]'>
|
||||
PLACEHOLDER
|
||||
</Skeleton>
|
||||
) : (
|
||||
!artists && (
|
||||
<div className='flex text-[12px] text-gray-500 dark:text-gray-400'>
|
||||
<span>{getSubtitleText(item, subtitle)}</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Items */}
|
||||
<div className='grid grid-cols-3 gap-4 lg:gap-6 xl:grid-cols-4 2xl:grid-cols-5'>
|
||||
{albums?.map(album => (
|
||||
<Album
|
||||
key={album.id}
|
||||
album={album}
|
||||
itemTitle={itemTitle}
|
||||
itemSubtitle={itemSubtitle}
|
||||
/>
|
||||
))}
|
||||
{playlists?.map(playlist => (
|
||||
<Playlist key={playlist.id} playlist={playlist} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoverRow
|
||||
const memoizedCoverRow = memo(CoverRow)
|
||||
memoizedCoverRow.displayName = 'CoverRow'
|
||||
export default memoizedCoverRow
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue