feat: updates

This commit is contained in:
qier222 2022-06-06 01:00:25 +08:00
parent cf7a4528dd
commit 0e58bb6e80
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
44 changed files with 1027 additions and 496 deletions

View file

@ -1,17 +1,97 @@
import TrackListHeader from '@/web/components/New/TrackListHeader'
import useAlbum from '@/web/api/hooks/useAlbum'
import useTracks from '@/web/api/hooks/useTracks'
import { useParams } from 'react-router-dom'
import { NavLink, useParams } from 'react-router-dom'
import PageTransition from '@/web/components/New/PageTransition'
import TrackList from '@/web/components/New/TrackList'
import { player } from '@/web/store'
import toast from 'react-hot-toast'
import { useSnapshot } from 'valtio'
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
import { css, cx } from '@emotion/css'
import CoverRow from '@/web/components/New/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')
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(
'h-px bg-white/20',
css`
margin: 30px 0;
`
)}
></div>
{/* Title */}
<div className='mb-5 text-14 font-bold text-neutral-300'>
MORE BY{' '}
<NavLink
to={`/artist/${album?.artist.id}`}
className='transition duration-300 ease-in-out hover:text-neutral-100'
>
{album?.artist.name}
</NavLink>
</div>
<CoverRow albums={filteredAlbums} />
</div>
)
}
const Album = () => {
const params = useParams()
const { data: album, isLoading } = useAlbum({
id: Number(params.id) || 0,
const { data: album } = useAlbum({
id: Number(params.id),
})
const { data: tracks } = useTracks({
@ -37,12 +117,13 @@ const Album = () => {
return (
<PageTransition>
<TrackListHeader album={album?.album} onPlay={() => onPlay()} />
<TrackListHeader album={album?.album} onPlay={onPlay} />
<TrackList
tracks={tracks?.songs}
className='z-10 mt-20'
tracks={tracks?.songs || album?.songs || album?.album.songs}
className='z-10 mt-10'
onPlay={onPlay}
/>
<MoreByArtist album={album?.album} />
</PageTransition>
)
}

View file

@ -1,10 +1,108 @@
import CoverWall from '@/web/components/New/CoverWall'
import PageTransition from '@/web/components/New/PageTransition'
import {
fetchPlaylistWithReactQuery,
fetchFromCache,
} from '@/web/api/hooks/usePlaylist'
import useTracks, { fetchTracksWithReactQuery } from '@/web/api/hooks/useTracks'
import { useEffect, useMemo, useState } from 'react'
import { sampleSize } from 'lodash-es'
import { FetchPlaylistResponse } from '@/shared/api/Playlists'
interface DiscoverAlbum {
id: number
coverUrl: string
large: boolean
}
const getAlbumsFromAPI = async () => {
const playlistsIds = [
2859214503, // 一周欧美上新
2829816518, // 欧美私人订制
5327906368, // 乐迷雷达
5362359247, // 宝藏雷达
3136952023, // 私人雷达
60198, // Billboard 排行榜
180106, // UK 排行榜
5212729721, // 欧美点唱机
2724708415, // 私藏推荐精选
5300458264, // 新歌雷达
7463185187, // 开发者夹带私货
]
const playlists = (await Promise.all(
sampleSize(playlistsIds, 5).map(
id =>
new Promise(resolve => {
const cache = fetchFromCache(id)
if (cache) {
resolve(cache)
return
}
return fetchPlaylistWithReactQuery({ id })
})
)
)) as FetchPlaylistResponse[]
const ids: number[] = []
playlists.forEach(playlist =>
playlist?.playlist?.trackIds?.forEach(t => ids.push(t.id))
)
if (!ids.length) {
return []
}
const tracks = await fetchTracksWithReactQuery({ ids })
if (!tracks.songs.length) {
return []
}
// 从歌单中抽出歌曲
const pickedIds: number[] = []
let albums: DiscoverAlbum[] = []
tracks.songs.forEach(t => {
if (pickedIds.includes(t.al.id)) return
pickedIds.push(t.al.id)
albums.push({
id: t.al.id,
coverUrl: t.al.picUrl,
large: false,
})
})
// 挑选出大图
albums = sampleSize(albums, 100)
const largeCover = sampleSize([...Array(100).keys()], ~~(100 / 3))
albums.map((album, index) => (album.large = largeCover.includes(index)))
localStorage.setItem('discoverAlbums', JSON.stringify(albums))
localStorage.setItem('discoverAlbumsTime', String(Date.now()))
return albums
}
const Discover = () => {
const [albums, setAlbums] = useState<DiscoverAlbum[]>([])
useEffect(() => {
const get = async () => {
const albumsInLocalStorageTime =
localStorage.getItem('discoverAlbumsTime')
if (
!albumsInLocalStorageTime ||
Date.now() - Number(albumsInLocalStorageTime) > 1000 * 60 * 60 * 2
) {
setAlbums(await getAlbumsFromAPI())
} else {
setAlbums(JSON.parse(localStorage.getItem('discoverAlbums') || '[]'))
}
}
get()
}, [])
return (
<PageTransition disableEnterAnimation={true}>
<CoverWall />
<CoverWall albums={albums} />
</PageTransition>
)
}

View file

@ -4,10 +4,12 @@ import PageTransition from '@/web/components/New/PageTransition'
import useUserArtists from '@/web/api/hooks/useUserArtists'
import ArtistRow from '@/web/components/New/ArtistRow'
import Tabs from '@/web/components/New/Tabs'
import { useState } from 'react'
import { useMemo, useState } from 'react'
import CoverRow from '@/web/components/New/CoverRow'
import useUserPlaylists from '@/web/api/hooks/useUserPlaylists'
import useUserAlbums from '@/web/api/hooks/useUserAlbums'
import useUserListenedRecords from '@/web/api/hooks/useUserListenedRecords'
import useArtists from '@/web/api/hooks/useArtists'
const tabs = [
{
@ -34,12 +36,41 @@ const My = () => {
const { data: albums } = useUserAlbums()
const [selectedTab, setSelectedTab] = useState(tabs[0].id)
const { data: listenedRecords } = useUserListenedRecords({ type: 'week' })
const recentListenedArtistsIDs = useMemo(() => {
const artists: {
id: number
playCount: number
}[] = []
listenedRecords?.weekData?.forEach(record => {
const artist = record.song.ar[0]
const index = artists.findIndex(a => a.id === artist.id)
if (index === -1) {
artists.push({
id: artist.id,
playCount: record.playCount,
})
} else {
artists[index].playCount += record.playCount
}
})
return artists
.sort((a, b) => b.playCount - a.playCount)
.slice(0, 5)
.map(artist => artist.id)
}, [listenedRecords])
const { data: recentListenedArtists } = useArtists(recentListenedArtistsIDs)
return (
<PageTransition>
<div className='grid grid-cols-1 gap-10'>
<PlayLikedSongsCard />
<div>
<ArtistRow artists={artists?.data} title='ARTISTS' />
<ArtistRow
artists={recentListenedArtists?.map(a => a.artist)}
title='RECENTLY LISTENED'
/>
</div>
<div>
@ -48,7 +79,7 @@ const My = () => {
value={selectedTab}
onChange={(id: string) => setSelectedTab(id)}
/>
<CoverRow albums={albums?.data} className='mt-6' />
<CoverRow playlists={playlists?.playlist} className='mt-6' />
</div>
</div>
</PageTransition>

View file

@ -0,0 +1,48 @@
import TrackListHeader from '@/web/components/New/TrackListHeader'
import { NavLink, useParams } from 'react-router-dom'
import PageTransition from '@/web/components/New/PageTransition'
import TrackList from '@/web/components/New/TrackList'
import { player } from '@/web/store'
import toast from 'react-hot-toast'
import { useSnapshot } from 'valtio'
import { memo, useEffect, useMemo } from 'react'
import usePlaylist from '@/web/api/hooks/usePlaylist'
import useTracksInfinite from '@/web/api/hooks/useTracksInfinite'
import useScroll from '@/web/hooks/useScroll'
const Playlist = () => {
const params = useParams()
const { data: playlist, isLoading } = usePlaylist({
id: Number(params.id),
})
const playerSnapshot = useSnapshot(player)
const onPlay = async (trackID: number | null = null) => {
if (!playlist?.playlist.id) {
toast('无法播放歌单,该歌单不存在')
return
}
if (
playerSnapshot.trackListSource?.type === 'playlist' &&
playerSnapshot.trackListSource?.id === playlist.playlist.id &&
playlist?.playlist?.trackIds?.[0].id
) {
await player.playTrack(trackID ?? playlist.playlist.trackIds[0].id)
return
}
await player.playPlaylist(playlist.playlist.id, trackID)
}
return (
<PageTransition>
<TrackListHeader playlist={playlist?.playlist} onPlay={onPlay} />
<TrackList
tracks={playlist?.playlist?.tracks ?? []}
onPlay={onPlay}
className='z-10 mt-10'
/>
</PageTransition>
)
}
export default Playlist