mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 05:38:04 +00:00
feat: updates
This commit is contained in:
parent
a1b0bcf4d3
commit
884f3df41a
198 changed files with 4572 additions and 5336 deletions
58
packages/web/pages/Album/Album.tsx
Normal file
58
packages/web/pages/Album/Album.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import useAlbum from '@/web/api/hooks/useAlbum'
|
||||
import useTracks from '@/web/api/hooks/useTracks'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import PageTransition from '@/web/components/PageTransition'
|
||||
import TrackList from '@/web/components/TrackList'
|
||||
import player from '@/web/states/player'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useCallback } from 'react'
|
||||
import MoreByArtist from './MoreByArtist'
|
||||
import Header from './Header'
|
||||
|
||||
const Album = () => {
|
||||
const params = useParams()
|
||||
const { data: album, isLoading } = useAlbum({
|
||||
id: Number(params.id),
|
||||
})
|
||||
|
||||
const { data: tracks } = useTracks({
|
||||
ids: album?.songs?.map(track => track.id) ?? [],
|
||||
})
|
||||
|
||||
const onPlay = useCallback(
|
||||
async (trackID: number | null = null) => {
|
||||
if (!album?.album?.id) {
|
||||
toast('无法播放专辑,该专辑不存在')
|
||||
return
|
||||
}
|
||||
player.playAlbum(album.album.id, trackID)
|
||||
},
|
||||
[album?.album?.id]
|
||||
)
|
||||
|
||||
return (
|
||||
<PageTransition>
|
||||
<Header />
|
||||
<TrackList
|
||||
tracks={
|
||||
tracks?.songs?.length
|
||||
? tracks?.songs
|
||||
: album?.album?.songs?.length
|
||||
? album?.album.songs
|
||||
: album?.songs?.length
|
||||
? album.songs
|
||||
: undefined
|
||||
}
|
||||
className='z-10 mx-2.5 mt-3 lg:mx-0 lg:mt-10'
|
||||
onPlay={onPlay}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<MoreByArtist album={album?.album} />
|
||||
|
||||
{/* Page padding */}
|
||||
<div className='h-16'></div>
|
||||
</PageTransition>
|
||||
)
|
||||
}
|
||||
|
||||
export default Album
|
||||
116
packages/web/pages/Album/Header.tsx
Normal file
116
packages/web/pages/Album/Header.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import useAlbum from '@/web/api/hooks/useAlbum'
|
||||
import useUserAlbums, {
|
||||
useMutationLikeAAlbum,
|
||||
} from '@/web/api/hooks/useUserAlbums'
|
||||
import Icon from '@/web/components/Icon'
|
||||
import TrackListHeader from '@/web/components/TrackListHeader'
|
||||
import useAppleMusicAlbum from '@/web/hooks/useAppleMusicAlbum'
|
||||
import useVideoCover from '@/web/hooks/useVideoCover'
|
||||
import player from '@/web/states/player'
|
||||
import { formatDuration } from '@/web/utils/common'
|
||||
import dayjs from 'dayjs'
|
||||
import { useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const Header = () => {
|
||||
const { t, i18n } = useTranslation()
|
||||
const params = useParams()
|
||||
|
||||
const { data: userLikedAlbums } = useUserAlbums()
|
||||
|
||||
const { data: albumRaw, isLoading: isLoadingAlbum } = useAlbum({
|
||||
id: Number(params.id),
|
||||
})
|
||||
const album = useMemo(() => albumRaw?.album, [albumRaw])
|
||||
|
||||
const { data: albumFromApple, isLoading: isLoadingAlbumFromApple } =
|
||||
useAppleMusicAlbum({
|
||||
id: album?.id,
|
||||
name: album?.name,
|
||||
artist: album?.artist.name,
|
||||
})
|
||||
|
||||
const { data: videoCoverFromRemote } = useVideoCover({
|
||||
id: album?.id,
|
||||
name: album?.name,
|
||||
artist: album?.artist.name,
|
||||
enabled: !window.env?.isElectron,
|
||||
})
|
||||
|
||||
// For <Cover />
|
||||
const cover = album?.picUrl
|
||||
const videoCover =
|
||||
albumFromApple?.attributes?.editorialVideo?.motionSquareVideo1x1?.video ||
|
||||
videoCoverFromRemote?.video
|
||||
|
||||
// For <Info />
|
||||
const title = album?.name
|
||||
const creatorName = album?.artist.name
|
||||
const creatorLink = `/artist/${album?.artist.id}`
|
||||
const description = isLoadingAlbumFromApple
|
||||
? ''
|
||||
: albumFromApple?.attributes?.editorialNotes?.standard || album?.description
|
||||
const extraInfo = useMemo(() => {
|
||||
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
|
||||
const albumDuration = formatDuration(
|
||||
duration,
|
||||
i18n.language,
|
||||
'hh[hr] mm[min]'
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{album?.mark === 1056768 && (
|
||||
<Icon
|
||||
name='explicit'
|
||||
className='mb-px mr-1 h-3 w-3 lg:h-3.5 lg:w-3.5'
|
||||
/>
|
||||
)}{' '}
|
||||
{dayjs(album?.publishTime || 0).year()} ·{' '}
|
||||
{t('common.track-with-count', { count: album?.songs?.length })},{' '}
|
||||
{albumDuration}
|
||||
</>
|
||||
)
|
||||
}, [album?.mark, album?.publishTime, album?.songs, i18n.language, t])
|
||||
|
||||
// For <Actions />
|
||||
const isLiked = useMemo(() => {
|
||||
const id = Number(params.id)
|
||||
if (!id) return false
|
||||
return !!userLikedAlbums?.data.find(item => item.id === id)
|
||||
}, [params.id, userLikedAlbums?.data])
|
||||
|
||||
const onPlay = async (trackID: number | null = null) => {
|
||||
if (!album?.id) {
|
||||
toast('无法播放专辑,该专辑不存在')
|
||||
return
|
||||
}
|
||||
player.playAlbum(album.id, trackID)
|
||||
}
|
||||
|
||||
const likeAAlbum = useMutationLikeAAlbum()
|
||||
const onLike = async () => {
|
||||
likeAAlbum.mutateAsync(album?.id || Number(params.id))
|
||||
}
|
||||
|
||||
return (
|
||||
<TrackListHeader
|
||||
{...{
|
||||
isLoading: isLoadingAlbum,
|
||||
title,
|
||||
creatorName,
|
||||
creatorLink,
|
||||
description,
|
||||
extraInfo,
|
||||
cover,
|
||||
videoCover,
|
||||
isLiked,
|
||||
onLike,
|
||||
onPlay,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
91
packages/web/pages/Album/MoreByArtist.tsx
Normal file
91
packages/web/pages/Album/MoreByArtist.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { NavLink } from 'react-router-dom'
|
||||
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
|
||||
import { cx } from '@emotion/css'
|
||||
import CoverRow from '@/web/components/CoverRow'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
const MoreByArtist = ({ album }: { album?: Album }) => {
|
||||
const { data: albums } = useArtistAlbums({
|
||||
id: album?.artist?.id || 0,
|
||||
limit: 1000,
|
||||
})
|
||||
|
||||
const filteredAlbums = useMemo((): Album[] => {
|
||||
if (!albums) return []
|
||||
const allReleases = albums?.hotAlbums || []
|
||||
const filteredAlbums = allReleases.filter(
|
||||
album =>
|
||||
['专辑', 'EP/Single', 'EP'].includes(album.type) && album.size > 1
|
||||
)
|
||||
const singles = allReleases.filter(
|
||||
album => album.type === 'Single' || album.size === 1
|
||||
)
|
||||
|
||||
const qualifiedAlbums = [...filteredAlbums, ...singles]
|
||||
|
||||
const formatName = (name: string) =>
|
||||
name.toLowerCase().replace(/(\s|deluxe|edition|\(|\))/g, '')
|
||||
|
||||
const uniqueAlbums: Album[] = []
|
||||
qualifiedAlbums.forEach(a => {
|
||||
// 去除当前页面的专辑
|
||||
if (formatName(a.name) === formatName(album?.name ?? '')) return
|
||||
|
||||
// 去除重复的专辑(包含 deluxe edition 的专辑会视为重复)
|
||||
if (
|
||||
uniqueAlbums.findIndex(aa => {
|
||||
return formatName(a.name) === formatName(aa.name)
|
||||
}) !== -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 去除 remix 专辑
|
||||
if (
|
||||
a.name.toLowerCase().includes('remix)') ||
|
||||
a.name.toLowerCase().includes('remixes)')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
uniqueAlbums.push(a)
|
||||
})
|
||||
|
||||
return uniqueAlbums.slice(0, 4)
|
||||
}, [album?.name, albums])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Dividing line */}
|
||||
<div className={cx('mx-2.5 my-7.5 h-px bg-white/10 lg:mx-0')}></div>
|
||||
|
||||
{/* Title */}
|
||||
<div className='mx-2.5 mb-5 text-14 font-bold text-neutral-300 lg:mx-0'>
|
||||
{album?.artist.name ? (
|
||||
<>
|
||||
MORE BY{' '}
|
||||
<NavLink
|
||||
to={`/artist/${album?.artist.id}`}
|
||||
className='transition duration-300 ease-in-out hover:text-neutral-100'
|
||||
>
|
||||
{album.artist.name}
|
||||
</NavLink>
|
||||
</>
|
||||
) : (
|
||||
<span className='inline-block h-full rounded-full bg-white/10 text-transparent'>
|
||||
MORE BY PLACEHOLDER
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CoverRow
|
||||
albums={filteredAlbums}
|
||||
itemTitle='name'
|
||||
itemSubtitle='year'
|
||||
className='mx-2.5 lg:mx-0'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MoreByArtist
|
||||
2
packages/web/pages/Album/index.tsx
Normal file
2
packages/web/pages/Album/index.tsx
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Album from './Album'
|
||||
export default Album
|
||||
Loading…
Add table
Add a link
Reference in a new issue