feat: updates

This commit is contained in:
qier222 2022-06-25 13:47:07 +08:00
parent f340a90117
commit cec4c5909d
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
50 changed files with 1304 additions and 207 deletions

View file

@ -0,0 +1,31 @@
import useArtist from '@/web/api/hooks/useArtist'
import { cx, css } from '@emotion/css'
import { useParams } from 'react-router-dom'
import Header from './Header'
import Popular from './Popular'
import ArtistAlbum from './ArtistAlbums'
import FansAlsoLike from './FansAlsoLike'
const Artist = () => {
const params = useParams()
const { data: artist, isLoading: isLoadingArtist } = useArtist({
id: Number(params.id) || 0,
})
return (
<div>
<Header artist={artist?.artist} />
<div className='mt-10 mb-7.5 h-px w-full bg-white/20'></div>
<Popular tracks={artist?.hotSongs} />
<ArtistAlbum />
<FansAlsoLike />
</div>
)
}
export default Artist

View file

@ -0,0 +1,27 @@
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
import CoverRow from '@/web/components/New/CoverRow'
import { useMemo } from 'react'
import { useParams } from 'react-router-dom'
const ArtistAlbum = () => {
const params = useParams()
const { data: albumsRaw, isLoading: isLoadingAlbums } = useArtistAlbums({
id: Number(params.id) || 0,
limit: 1000,
})
const albums = useMemo(() => albumsRaw?.hotAlbums, [albumsRaw?.hotAlbums])
return (
<div>
<div className='mb-4 mt-11 text-12 font-medium uppercase text-neutral-300'>
Albums
</div>
<CoverRow albums={albums?.slice(0, 12)} />
</div>
)
}
export default ArtistAlbum

View file

@ -0,0 +1,20 @@
import ArtistRow from '@/web/components/New/ArtistRow'
import useSimilarArtists from '@/web/api/hooks/useSimilarArtists'
import { useParams } from 'react-router-dom'
const FansAlsoLike = () => {
const params = useParams()
const { data: artists } = useSimilarArtists({ id: Number(params.id) || 0 })
return (
<div>
<div className='mb-6 mt-10 text-12 font-medium uppercase text-neutral-300'>
Fans Also Like
</div>
<ArtistRow artists={artists?.artists?.slice(0, 5)} />
</div>
)
}
export default FansAlsoLike

View file

@ -0,0 +1,13 @@
const Actions = () => {
return (
<div className='mt-11 flex items-end justify-between lg:z-10 lg:mt-6'>
<div className='flex items-end'>
<button className='mr-2.5 h-14 w-14 rounded-full dark:bg-white/10'></button>
<button className='h-14 w-14 rounded-full dark:bg-white/10'></button>
</div>
<button className='h-14 w-[125px] rounded-full dark:bg-brand-700 lg:w-[170px]'></button>
</div>
)
}
export default Actions

View file

@ -0,0 +1,28 @@
import useIsMobile from '@/web/hooks/useIsMobile'
const ArtistInfo = ({ artist }: { artist?: Artist }) => {
const isMobile = useIsMobile()
return (
<div>
<div className='text-28 font-semibold text-night-50 lg:text-32'>
{artist?.name}
</div>
<div className='mt-2.5 text-24 font-medium text-night-400 lg:mt-6'>
Artist
</div>
<div className='mt-1 text-12 font-medium text-night-400'>
{artist?.musicSize} Tracks · {artist?.albumSize} Albums ·{' '}
{artist?.mvSize} Videos
</div>
{/* Description */}
{!isMobile && (
<div className='line-clamp-5 mt-6 text-14 font-bold text-night-400'>
{artist?.briefDesc}
</div>
)}
</div>
)
}
export default ArtistInfo

View file

@ -0,0 +1,26 @@
import { resizeImage } from '@/web/utils/common'
import { cx, css } from '@emotion/css'
import useIsMobile from '@/web/hooks/useIsMobile'
const BlurBackground = ({ cover }: { cover?: string }) => {
const isMobile = useIsMobile()
return isMobile || !cover ? (
<></>
) : (
<img
className={cx(
'absolute z-0 object-cover opacity-70',
css`
top: -400px;
left: -370px;
width: 1572px;
height: 528px;
filter: blur(256px) saturate(1.2);
`
)}
src={resizeImage(cover, 'sm')}
/>
)
}
export default BlurBackground

View file

@ -0,0 +1,65 @@
import { resizeImage } from '@/web/utils/common'
import { cx, css } from '@emotion/css'
import Image from '@/web/components/New/Image'
import { useMemo } from 'react'
import { breakpoint as bp } from '@/web/utils/const'
import BlurBackground from './BlurBackground'
import ArtistInfo from './ArtistInfo'
import Actions from './Actions'
import LatestRelease from './LatestRelease'
const Header = ({ artist }: { artist?: Artist }) => {
return (
<div
className={cx(
'lg:grid lg:gap-10',
css`
grid-template-columns: auto 558px;
grid-template-areas:
'info cover'
'info cover';
`
)}
>
<Image
className={cx(
'z-10 aspect-square lg:rounded-24',
css`
grid-area: cover;
`
)}
src={resizeImage(artist?.img1v1Url || '', 'lg')}
/>
<BlurBackground cover={artist?.img1v1Url} />
<div
className={cx(
'lg:flex lg:flex-col lg:justify-between',
css`
grid-area: info;
`
)}
>
<div
className={cx(
'mx-2.5 rounded-48 bg-white/10 p-8 backdrop-blur-3xl lg:mx-0 lg:bg-transparent lg:p-0 lg:backdrop-blur-none',
css`
margin-top: -60px;
${bp.lg} {
margin-top: 0px;
}
`
)}
>
<ArtistInfo artist={artist} />
<Actions />
</div>
<LatestRelease />
</div>
</div>
)
}
export default Header

View file

@ -0,0 +1,97 @@
import { resizeImage } from '@/web/utils/common'
import dayjs from 'dayjs'
import { cx, css } from '@emotion/css'
import { useNavigate, useParams } from 'react-router-dom'
import Image from '@/web/components/New/Image'
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
import { useMemo } from 'react'
const Album = () => {
const params = useParams()
const navigate = useNavigate()
const { data: albumsRaw, isLoading: isLoadingAlbums } = useArtistAlbums({
id: Number(params.id) || 0,
limit: 1000,
})
const album = useMemo(() => albumsRaw?.hotAlbums?.[0], [albumsRaw?.hotAlbums])
if (!album) {
return <></>
}
return (
<div
onClick={() => navigate(`/album/${album.id}`)}
className='flex rounded-24 bg-white/10 p-2.5'
>
<Image
src={resizeImage(album.picUrl, 'sm')}
className={cx(
'aspect-square',
css`
height: 60px;
width: 60px;
border-radius: 16px;
`
)}
/>
<div className='flex-shrink-1 ml-2'>
<div className='line-clamp-1 text-16 font-medium text-night-100'>
{album.name}
</div>
<div className='mt-1 text-14 font-bold text-night-500'>
{album.type}
{album.size > 1 ? `· ${album.size} Tracks` : ''}
</div>
<div className='mt-1.5 text-12 font-medium text-night-500'>
{dayjs(album?.publishTime || 0).format('MMM DD, YYYY')}
</div>
</div>
</div>
)
}
const Video = () => {
return (
<div className='mt-4 flex rounded-24 bg-white/10 p-2.5'>
<Image
src={resizeImage(
'https://p1.music.126.net/am47BH30IGQit_L2vYaArg==/109951167502760845.jpg',
'sm'
)}
className={cx(
css`
height: 60px;
width: 106px;
border-radius: 16px;
`
)}
/>
<div className='flex-shrink-1 ml-2'>
<div className='line-clamp-2 text-16 font-medium text-night-100'>
Swedish House Mafia & The Weeknd Live at C...
</div>
<div className='mt-1.5 text-12 font-medium text-night-500'>
{dayjs().format('MMM DD, YYYY')}
</div>
</div>
</div>
)
}
const LatestRelease = () => {
return (
<div className='mx-2.5 lg:mx-0'>
<div className='mt-7 mb-3 text-14 font-bold text-neutral-300'>
Latest Releases
</div>
<Album />
<Video />
</div>
)
}
export default LatestRelease

View file

@ -0,0 +1,3 @@
import Header from './Header'
export default Header

View file

@ -0,0 +1,76 @@
import { resizeImage } from '@/web/utils/common'
import { player } from '@/web/store'
import { State as PlayerState } from '@/web/utils/player'
import useTracks from '@/web/api/hooks/useTracks'
import { css, cx } from '@emotion/css'
import Image from '@/web/components/New/Image'
const Track = ({
track,
isPlaying,
onPlay,
}: {
track?: Track
isPlaying?: boolean
onPlay: (id: number) => void
}) => {
return (
<div
className='flex items-center justify-between'
onClick={e => {
if (e.detail === 2 && track?.id) onPlay(track.id)
}}
>
{/* Cover */}
<Image
className='mr-4 aspect-square h-14 w-14 flex-shrink-0 rounded-12'
src={resizeImage(track?.al?.picUrl || '', 'sm')}
animation={false}
placeholder={false}
/>
{/* Track info */}
<div className='mr-3 flex-grow'>
<div
className={cx(
'line-clamp-1 text-16 font-medium ',
isPlaying
? 'text-brand-700'
: 'text-neutral-700 dark:text-neutral-200'
)}
>
{track?.name}
</div>
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 dark:text-neutral-700'>
{track?.ar.map(a => a.name).join(', ')}
</div>
</div>
</div>
)
}
const Popular = ({ tracks }: { tracks?: Track[] }) => {
const onPlay = (id: number) => {
if (!tracks) return
player.playAList(
tracks.map(t => t.id),
id
)
}
return (
<div>
<div className='mb-4 text-12 font-medium uppercase text-neutral-300'>
Popular
</div>
<div className='grid grid-cols-3 grid-rows-3 gap-4 overflow-hidden'>
{tracks?.slice(0, 9)?.map(t => (
<Track key={t.id} track={t} onPlay={onPlay} />
))}
</div>
</div>
)
}
export default Popular

View file

@ -0,0 +1,3 @@
import Artist from './Artist'
export default Artist

View file

@ -16,6 +16,7 @@ import topbarBackground from '@/web/assets/images/topbar-background.png'
const reactQueryOptions = {
refetchOnWindowFocus: false,
refetchInterval: 1000 * 60 * 60, // 1 hour
refetchOnMount: false,
}
const Recommend = () => {

View file

@ -8,6 +8,7 @@ import { fetchTracksWithReactQuery } from '@/web/api/hooks/useTracks'
import { useEffect, useState } from 'react'
import { sampleSize } from 'lodash-es'
import { FetchPlaylistResponse } from '@/shared/api/Playlists'
import { useQuery } from 'react-query'
interface DiscoverAlbum {
id: number
@ -82,28 +83,31 @@ const getAlbumsFromAPI = async () => {
}
const Discover = () => {
const [albums, setAlbums] = useState<DiscoverAlbum[]>([])
useEffect(() => {
const get = async () => {
const { data: albums } = useQuery(
['DiscoveryAlbums'],
async () => {
const albumsInLocalStorageTime =
localStorage.getItem('discoverAlbumsTime')
if (
!albumsInLocalStorageTime ||
Date.now() - Number(albumsInLocalStorageTime) > 1000 * 60 * 60 * 2 // 2小时刷新一次
) {
setAlbums(await getAlbumsFromAPI())
return await getAlbumsFromAPI()
} else {
setAlbums(JSON.parse(localStorage.getItem('discoverAlbums') || '[]'))
return JSON.parse(localStorage.getItem('discoverAlbums') || '[]')
}
},
{
staleTime: 1000 * 60 * 60 * 2, // 2hr
refetchOnWindowFocus: false,
refetchInterval: 1000 * 60 * 60 * 2, // 2hr
}
get()
}, [])
)
return (
<PageTransition disableEnterAnimation={true}>
<div className='mx-2.5 pb-10 lg:mx-0 lg:pb-16'>
<CoverWall albums={albums} />
<CoverWall albums={albums || []} />
</div>
</PageTransition>
)