YesPlayMusic/src/renderer/components/TracksList.tsx

241 lines
7.3 KiB
TypeScript
Raw Normal View History

2022-03-29 00:11:05 +08:00
import { memo } from 'react'
2022-03-13 14:40:38 +08:00
import { NavLink } from 'react-router-dom'
import ArtistInline from '@/components/ArtistsInline'
import Skeleton from '@/components/Skeleton'
import SvgIcon from '@/components/SvgIcon'
import useUser from '@/hooks/useUser'
2022-04-05 02:30:07 +08:00
import useUserLikedTracksIDs, {
useMutationLikeATrack,
} from '@/hooks/useUserLikedTracksIDs'
2022-03-13 14:40:38 +08:00
import { formatDuration, resizeImage } from '@/utils/common'
import { player } from '@/store'
2022-03-13 14:40:38 +08:00
const Track = memo(
({
track,
isLiked = false,
isSkeleton = false,
2022-04-02 18:46:08 +08:00
isHighlight = false,
2022-03-13 14:40:38 +08:00
onClick,
}: {
track: Track
isLiked?: boolean
isSkeleton?: boolean
2022-04-02 18:46:08 +08:00
isHighlight?: boolean
2022-03-13 14:40:38 +08:00
onClick: (e: React.MouseEvent<HTMLElement>, trackID: number) => void
}) => {
2022-04-02 18:46:08 +08:00
const subtitle = useMemo(
() => track.tns?.at(0) ?? track.alia?.at(0),
[track.alia, track.tns]
)
2022-04-05 02:30:07 +08:00
const mutationLikeATrack = useMutationLikeATrack()
2022-04-02 18:46:08 +08:00
2022-03-13 14:40:38 +08:00
return (
<div
onClick={e => onClick(e, track.id)}
className={classNames(
2022-03-21 02:03:25 +08:00
'group grid w-full rounded-xl after:scale-[.98] after:rounded-xl ',
2022-03-13 14:40:38 +08:00
'grid-cols-12 p-2 pr-4',
2022-03-19 17:03:29 +08:00
!isSkeleton &&
2022-04-02 18:46:08 +08:00
!isHighlight &&
2022-03-21 02:03:25 +08:00
'btn-hover-animation after:bg-gray-100 dark:after:bg-white/10',
2022-04-02 18:46:08 +08:00
!isSkeleton && isHighlight && 'bg-brand-50 dark:bg-gray-800'
2022-03-13 14:40:38 +08:00
)}
>
{/* Track info */}
2022-03-17 19:30:43 +08:00
<div className='col-span-6 grid grid-cols-[4.2rem_auto] pr-8'>
2022-03-13 14:40:38 +08:00
{/* Cover */}
<div>
{isSkeleton ? (
2022-03-17 19:30:43 +08:00
<Skeleton className='mr-4 h-12 w-12 rounded-md border border-gray-100 dark:border-gray-800' />
2022-03-13 14:40:38 +08:00
) : (
<img
src={resizeImage(track.al.picUrl, 'xs')}
2022-03-17 19:30:43 +08:00
className='box-content h-12 w-12 rounded-md border border-black border-opacity-[.03]'
2022-03-13 14:40:38 +08:00
/>
)}
</div>
{/* Track name & Artists */}
2022-03-17 19:30:43 +08:00
<div className='flex flex-col justify-center'>
2022-03-13 14:40:38 +08:00
{isSkeleton ? (
2022-03-17 19:30:43 +08:00
<Skeleton className='text-lg'>PLACEHOLDER12345</Skeleton>
2022-03-13 14:40:38 +08:00
) : (
<div
className={classNames(
'line-clamp-1 break-all text-lg font-semibold',
2022-04-02 18:46:08 +08:00
isHighlight ? 'text-brand-500' : 'text-black dark:text-white'
)}
>
<span>{track.name}</span>
{subtitle && (
<span
title={subtitle}
className={classNames(
'ml-1',
2022-04-02 18:46:08 +08:00
isHighlight ? 'text-brand-500/[.8]' : 'text-gray-400'
)}
>
({subtitle})
</span>
)}
2022-03-13 14:40:38 +08:00
</div>
)}
<div
className={classNames(
'text-sm',
2022-04-02 18:46:08 +08:00
isHighlight
2022-03-19 17:03:29 +08:00
? 'text-brand-500'
: 'text-gray-600 dark:text-gray-400'
)}
>
2022-03-13 14:40:38 +08:00
{isSkeleton ? (
2022-03-17 19:30:43 +08:00
<Skeleton className='w-2/3 translate-y-px'>PLACE</Skeleton>
2022-03-13 14:40:38 +08:00
) : (
2022-03-24 14:23:04 +08:00
<span className='inline-flex items-center'>
{track.mark === 1318912 && (
<SvgIcon
name='explicit'
className='mr-1 h-3.5 w-3.5 text-gray-300 dark:text-gray-500'
/>
)}
<ArtistInline artists={track.ar} />
</span>
2022-03-13 14:40:38 +08:00
)}
</div>
</div>
</div>
{/* Album name */}
2022-03-17 19:30:43 +08:00
<div className='col-span-4 flex items-center text-gray-600 dark:text-gray-400'>
2022-03-13 14:40:38 +08:00
{isSkeleton ? (
<Skeleton>PLACEHOLDER1234567890</Skeleton>
) : (
2022-03-29 00:11:05 +08:00
<>
2022-03-13 14:40:38 +08:00
<NavLink
to={`/album/${track.al.id}`}
className={classNames(
2022-03-19 17:03:29 +08:00
'hover:underline',
2022-04-02 18:46:08 +08:00
isHighlight && 'text-brand-500'
)}
2022-03-13 14:40:38 +08:00
>
{track.al.name}
</NavLink>
2022-03-17 19:30:43 +08:00
<span className='flex-grow'></span>
2022-03-29 00:11:05 +08:00
</>
2022-03-13 14:40:38 +08:00
)}
</div>
{/* Actions & Track duration */}
2022-03-17 19:30:43 +08:00
<div className='col-span-2 flex items-center justify-end'>
2022-03-13 14:40:38 +08:00
{/* Like button */}
{!isSkeleton && (
<button
2022-04-05 02:30:07 +08:00
onClick={() => track?.id && mutationLikeATrack.mutate(track.id)}
2022-03-13 14:40:38 +08:00
className={classNames(
'mr-5 cursor-default transition duration-300 hover:scale-[1.2]',
!isLiked && 'text-gray-600 opacity-0 dark:text-gray-400',
isLiked && 'text-brand-500 opacity-100',
!isSkeleton && 'group-hover:opacity-100'
)}
>
<SvgIcon
name={isLiked ? 'heart' : 'heart-outline'}
2022-03-21 02:03:25 +08:00
className='h-5 w-5'
2022-03-13 14:40:38 +08:00
/>
</button>
)}
{/* Track duration */}
{isSkeleton ? (
<Skeleton>0:00</Skeleton>
) : (
<div
className={classNames(
'min-w-[2.5rem] text-right',
2022-04-02 18:46:08 +08:00
isHighlight
2022-03-19 17:03:29 +08:00
? 'text-brand-500'
: 'text-gray-600 dark:text-gray-400'
)}
>
2022-03-13 14:40:38 +08:00
{formatDuration(track.dt, 'en', 'hh:mm:ss')}
</div>
)}
</div>
</div>
)
}
)
Track.displayName = 'Track'
const TracksList = memo(
({
tracks,
isSkeleton = false,
onTrackDoubleClick,
}: {
tracks: Track[]
isSkeleton?: boolean
onTrackDoubleClick?: (trackID: number) => void
}) => {
console.debug('Rendering TrackList.tsx TrackList')
// Fake data when isLoading is true
const skeletonTracks: Track[] = new Array(12).fill({})
// Liked songs ids
2022-04-05 02:30:07 +08:00
const { data: userLikedSongs } = useUserLikedTracksIDs()
2022-03-13 14:40:38 +08:00
const handleClick = (e: React.MouseEvent<HTMLElement>, trackID: number) => {
if (e.detail === 2) onTrackDoubleClick?.(trackID)
}
const playerSnapshot = useSnapshot(player)
const playingTrack = useMemo(
() => playerSnapshot.track,
[playerSnapshot.track]
)
2022-03-13 14:40:38 +08:00
return (
2022-03-29 00:11:05 +08:00
<>
2022-03-13 14:40:38 +08:00
{/* Tracks table header */}
2022-03-17 19:30:43 +08:00
<div className='ml-2 mr-4 mt-10 mb-2 grid grid-cols-12 border-b border-gray-100 py-2.5 text-sm text-gray-400 dark:border-gray-800 dark:text-gray-500'>
<div className='col-span-6 grid grid-cols-[4.2rem_auto]'>
2022-03-13 14:40:38 +08:00
<div></div>
2022-03-24 14:23:04 +08:00
<div></div>
2022-03-13 14:40:38 +08:00
</div>
2022-03-24 14:23:04 +08:00
<div className='col-span-4'></div>
<div className='col-span-2 justify-self-end'></div>
2022-03-13 14:40:38 +08:00
</div>
2022-03-21 02:03:25 +08:00
<div className='grid w-full'>
2022-03-13 14:40:38 +08:00
{/* Tracks */}
{isSkeleton
? skeletonTracks.map((track, index) => (
<Track
key={index}
track={track}
onClick={() => null}
isSkeleton={true}
/>
))
: tracks.map(track => (
<Track
onClick={handleClick}
key={track.id}
track={track}
isLiked={userLikedSongs?.ids?.includes(track.id) ?? false}
isSkeleton={false}
isHighlight={track.id === playingTrack?.id}
/>
))}
2022-03-13 14:40:38 +08:00
</div>
2022-03-29 00:11:05 +08:00
</>
2022-03-13 14:40:38 +08:00
)
}
)
TracksList.displayName = 'TracksList'
export default TracksList