feat: updates

This commit is contained in:
qier222 2022-08-22 16:51:23 +08:00
parent ebebf2a733
commit a1b0bcf4d3
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
68 changed files with 4776 additions and 5559 deletions

View file

@ -1,22 +1,17 @@
import TrackListHeader from '@/web/components/New/TrackListHeader'
import useAlbum from '@/web/api/hooks/useAlbum'
import useTracks from '@/web/api/hooks/useTracks'
import { NavLink, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import PageTransition from '@/web/components/New/PageTransition'
import TrackList from '@/web/components/New/TrackList'
import player from '@/web/states/player'
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 { useCallback, useMemo } from 'react'
import { useCallback } from 'react'
import MoreByArtist from './MoreByArtist'
import Header from './Header'
const Album = () => {
const params = useParams()
const { data: album } = useAlbum({
const { data: album, isLoading } = useAlbum({
id: Number(params.id),
})
@ -39,9 +34,18 @@ const Album = () => {
<PageTransition>
<Header />
<TrackList
tracks={tracks?.songs || album?.album.songs || album?.songs}
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} />

View file

@ -17,7 +17,7 @@ const Header = () => {
const params = useParams()
const { data: userLikedAlbums } = useUserAlbums()
const { data: albumRaw } = useAlbum({
const { data: albumRaw, isLoading: isLoadingAlbum } = useAlbum({
id: Number(params.id),
})
const album = useMemo(() => albumRaw?.album, [albumRaw])
@ -89,6 +89,7 @@ const Header = () => {
return (
<TrackListHeader
{...{
isLoading: isLoadingAlbum,
title,
creatorName,
creatorLink,

View file

@ -78,7 +78,12 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
</NavLink>
</div>
<CoverRow albums={filteredAlbums} className='mx-2.5 lg:mx-0' />
<CoverRow
albums={filteredAlbums}
itemTitle='name'
itemSubtitle='year'
className='mx-2.5 lg:mx-0'
/>
</div>
)
}

View file

@ -1,6 +1,3 @@
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'
@ -8,20 +5,12 @@ import FansAlsoLike from './FansAlsoLike'
import ArtistMVs from './ArtistMVs'
const Artist = () => {
const params = useParams()
const { data: artist, isLoading: isLoadingArtist } = useArtist({
id: Number(params.id) || 0,
})
return (
<div>
<Header artist={artist?.artist} />
<Header />
{/* Dividing line */}
<div className='mt-10 mb-7.5 h-px w-full bg-white/20'></div>
<Popular tracks={artist?.hotSongs} />
<Popular />
<ArtistAlbum />
<ArtistMVs />
<FansAlsoLike />

View file

@ -36,6 +36,8 @@ const ArtistAlbum = () => {
<CoverRow
key={index}
albums={page}
itemTitle='name'
itemSubtitle='year'
className='h-full w-full flex-shrink-0'
/>
))}

View file

@ -2,10 +2,13 @@ import useUserArtists, {
useMutationLikeAArtist,
} from '@/web/api/hooks/useUserArtists'
import Icon from '@/web/components/Icon'
import { openContextMenu } from '@/web/states/contextMenus'
import player from '@/web/states/player'
import { cx } from '@emotion/css'
import toast from 'react-hot-toast'
import { useParams } from 'react-router-dom'
const Actions = () => {
const Actions = ({ isLoading }: { isLoading: boolean }) => {
const { data: likedArtists } = useUserArtists()
const params = useParams()
const id = Number(params.id) || 0
@ -16,14 +19,33 @@ const Actions = () => {
<div className='mt-11 flex items-end justify-between lg:z-10 lg:mt-6'>
<div className='flex items-end'>
{/* Menu */}
<button className='mr-2.5 flex h-14 w-14 items-center justify-center rounded-full text-white/40 transition duration-400 hover:text-white/70 dark:bg-white/10 hover:dark:bg-white/30'>
<Icon name='more' className='h-7 w-7' />
<button
onClick={event => {
openContextMenu({
event,
type: 'artist',
dataSourceID: id,
})
}}
className={cx(
'mr-2.5 flex h-14 w-14 items-center justify-center rounded-full transition duration-400 dark:bg-white/10 ',
isLoading
? 'text-transparent'
: 'text-white/40 hover:text-white/70 hover:dark:bg-white/30 '
)}
>
<Icon name='more' className='pointer-events-none h-7 w-7' />
</button>
{/* Like */}
<button
onClick={() => likeArtist.mutateAsync(id)}
className='flex h-14 w-14 items-center justify-center rounded-full text-white/40 transition duration-400 hover:text-white/70 dark:bg-white/10 hover:dark:bg-white/30'
className={cx(
'mr-2.5 flex h-14 w-14 items-center justify-center rounded-full transition duration-400 dark:bg-white/10 ',
isLoading
? 'text-transparent'
: 'text-white/40 hover:text-white/70 hover:dark:bg-white/30 '
)}
>
<Icon
name={isLiked ? 'heart' : 'heart-outline'}
@ -35,7 +57,10 @@ const Actions = () => {
{/* Listen */}
<button
onClick={() => player.playArtistPopularTracks(id)}
className='h-14 rounded-full px-10 text-18 font-medium text-white dark:bg-brand-700'
className={cx(
'h-14 rounded-full px-10 text-18 font-medium',
isLoading ? 'bg-white/10 text-transparent' : 'bg-brand-700 text-white'
)}
>
Listen
</button>

View file

@ -2,7 +2,13 @@ import useIsMobile from '@/web/hooks/useIsMobile'
import useAppleMusicArtist from '@/web/hooks/useAppleMusicArtist'
import { cx, css } from '@emotion/css'
const ArtistInfo = ({ artist }: { artist?: Artist }) => {
const ArtistInfo = ({
artist,
isLoading,
}: {
artist?: Artist
isLoading: boolean
}) => {
const isMobile = useIsMobile()
const { data: artistFromApple, isLoading: isLoadingArtistFromApple } =
useAppleMusicArtist({
@ -12,30 +18,67 @@ const ArtistInfo = ({ artist }: { artist?: Artist }) => {
return (
<div>
<div className='text-28 font-semibold text-white/70 lg:text-32'>
{artist?.name}
</div>
<div className='mt-2.5 text-24 font-medium text-white/40 lg:mt-6'>
Artist
</div>
<div className='mt-1 text-12 font-medium text-white/40'>
{artist?.musicSize} Tracks · {artist?.albumSize} Albums ·{' '}
{artist?.mvSize} Videos
</div>
{/* Description */}
{!isMobile && !isLoadingArtistFromApple && (
<div
className={cx(
'line-clamp-5 mt-6 text-14 font-bold text-white/40',
css`
height: 86px;
`
)}
>
{artistFromApple?.attributes?.artistBio || artist?.briefDesc}
{/* Name */}
{isLoading ? (
<div className=' text-28 font-semibold text-transparent lg:text-32'>
<span className='rounded-full bg-white/10'>PLACEHOLDER</span>
</div>
) : (
<div className='text-28 font-semibold text-white/70 lg:text-32'>
{artist?.name}
</div>
)}
{/* Type */}
{isLoading ? (
<div className='mt-2.5 text-24 font-medium text-transparent lg:mt-6'>
<span className='rounded-full bg-white/10'>Artist</span>
</div>
) : (
<div className='mt-2.5 text-24 font-medium text-white/40 lg:mt-6'>
Artist
</div>
)}
{/* Counts */}
{isLoading ? (
<div className='mt-1 text-12 font-medium text-transparent'>
<span className='rounded-full bg-white/10'>PLACEHOLDER12345</span>
</div>
) : (
<div className='mt-1 text-12 font-medium text-white/40'>
{artist?.musicSize} Tracks · {artist?.albumSize} Albums ·{' '}
{artist?.mvSize} Videos
</div>
)}
{/* Description */}
{!isMobile &&
(isLoading || isLoadingArtistFromApple ? (
<div
className={cx(
'line-clamp-5 mt-6 text-14 font-bold text-transparent',
css`
height: 86px;
`
)}
>
<span className='rounded-full bg-white/10'>
PLACEHOLDER1234567890
</span>
</div>
) : (
<div
className={cx(
'line-clamp-5 mt-6 text-14 font-bold text-white/40',
css`
height: 86px;
`
)}
>
{artistFromApple?.attributes?.artistBio || artist?.briefDesc}
</div>
))}
</div>
)
}

View file

@ -3,8 +3,16 @@ import { breakpoint as bp } from '@/web/utils/const'
import ArtistInfo from './ArtistInfo'
import Actions from './Actions'
import LatestRelease from './LatestRelease'
import Cover from "./Cover"
const Header = ({ artist }: { artist?: Artist }) => {
import Cover from './Cover'
import useArtist from '@/web/api/hooks/useArtist'
import { useParams } from 'react-router-dom'
const Header = () => {
const params = useParams()
const { data: artistRaw, isLoading } = useArtist({
id: Number(params.id) || 0,
})
const artist = artistRaw?.artist
return (
<div
@ -39,8 +47,8 @@ const Header = ({ artist }: { artist?: Artist }) => {
`
)}
>
<ArtistInfo artist={artist} />
<Actions />
<ArtistInfo isLoading={isLoading} artist={artist} />
<Actions isLoading={isLoading} />
</div>
<LatestRelease />

View file

@ -6,18 +6,11 @@ import Image from '@/web/components/New/Image'
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
import { useMemo } from 'react'
import useArtistMV from '@/web/api/hooks/useArtistMV'
import { motion } from 'framer-motion'
const Album = () => {
const params = useParams()
const Album = ({ album }: { album?: Album }) => {
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 <></>
}
@ -25,7 +18,7 @@ const Album = () => {
return (
<div
onClick={() => navigate(`/album/${album.id}`)}
className='flex rounded-24 bg-white/10 p-2.5'
className='group flex rounded-24 bg-white/10 p-2.5 transition-colors duration-400 hover:bg-white/20'
>
<Image
src={resizeImage(album.picUrl, 'sm')}
@ -39,14 +32,14 @@ const Album = () => {
)}
/>
<div className='flex-shrink-1 ml-2'>
<div className='line-clamp-1 text-16 font-medium text-night-100'>
<div className='line-clamp-1 text-16 font-medium text-night-100 transition-colors duration-400 group-hover:text-night-50'>
{album.name}
</div>
<div className='mt-1 text-14 font-bold text-night-500'>
<div className='mt-1 text-14 font-bold text-night-500 transition-colors duration-400 group-hover:text-night-200'>
{album.type}
{album.size > 1 ? `· ${album.size} Tracks` : ''}
</div>
<div className='mt-1.5 text-12 font-medium text-night-500'>
<div className='mt-1.5 text-12 font-medium text-night-500 transition-colors duration-400 group-hover:text-night-200'>
{dayjs(album?.publishTime || 0).format('MMM DD, YYYY')}
</div>
</div>
@ -54,17 +47,14 @@ const Album = () => {
)
}
const Video = () => {
const params = useParams()
const { data: videos } = useArtistMV({ id: Number(params.id) || 0 })
const video = videos?.mvs?.[0]
const Video = ({ video }: { video?: any }) => {
const navigate = useNavigate()
return (
<>
{video && (
<div
className='mt-4 flex rounded-24 bg-white/10 p-2.5'
className='group mt-4 flex rounded-24 bg-white/10 p-2.5 transition-colors duration-400 hover:bg-white/20'
onClick={() => navigate(`/mv/${video.id}`)}
>
<img
@ -78,11 +68,13 @@ const Video = () => {
)}
/>
<div className='flex-shrink-1 ml-2'>
<div className='line-clamp-1 text-16 font-medium text-night-100'>
<div className='line-clamp-1 text-16 font-medium text-night-100 transition-colors duration-400 group-hover:text-night-50'>
{video.name}
</div>
<div className='mt-1 text-14 font-bold text-night-500'>MV</div>
<div className='mt-1.5 text-12 font-medium text-night-500'>
<div className='mt-1 text-14 font-bold text-night-500 transition-colors duration-400 group-hover:text-night-200'>
MV
</div>
<div className='mt-1.5 text-12 font-medium text-night-500 transition-colors duration-400 group-hover:text-night-200'>
{dayjs(video.publishTime).format('MMM DD, YYYY')}
</div>
</div>
@ -93,15 +85,37 @@ const Video = () => {
}
const LatestRelease = () => {
return (
<div className='mx-2.5 lg:mx-0'>
<div className='mb-3 mt-7 text-14 font-bold text-neutral-300'>
Latest Releases
</div>
const params = useParams()
<Album />
<Video />
</div>
const { data: albumsRaw, isLoading: isLoadingAlbums } = useArtistAlbums({
id: Number(params.id) || 0,
limit: 1000,
})
const album = useMemo(() => albumsRaw?.hotAlbums?.[0], [albumsRaw?.hotAlbums])
const { data: videos, isLoading: isLoadingVideos } = useArtistMV({
id: Number(params.id) || 0,
})
const video = videos?.mvs?.[0]
return (
<>
{!isLoadingVideos && !isLoadingAlbums && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className='mx-2.5 lg:mx-0'
>
<div className='mb-3 mt-7 text-14 font-bold text-neutral-300'>
Latest Releases
</div>
<Album album={album} />
<Video video={video} />
</motion.div>
)}
</>
)
}

View file

@ -4,6 +4,8 @@ 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'
import useArtist from '@/web/api/hooks/useArtist'
import { useParams } from 'react-router-dom'
const Track = ({
track,
@ -49,7 +51,13 @@ const Track = ({
)
}
const Popular = ({ tracks }: { tracks?: Track[] }) => {
const Popular = () => {
const params = useParams()
const { data: artist, isLoading: isLoadingArtist } = useArtist({
id: Number(params.id) || 0,
})
const tracks = artist?.hotSongs || []
const onPlay = (id: number) => {
if (!tracks) return
player.playAList(

View file

@ -0,0 +1,5 @@
const Lyrics = () => {
return <div className='text-white'></div>
}
export default Lyrics

View file

@ -1,13 +1,19 @@
import { css, cx } from '@emotion/css'
import useUserArtists from '@/web/api/hooks/useUserArtists'
import Tabs from '@/web/components/New/Tabs'
import { useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, 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 { useSnapshot } from 'valtio'
import uiStates from '@/web/states/uiStates'
import ArtistRow from '@/web/components/New/ArtistRow'
import { playerWidth, topbarHeight } from '@/web/utils/const'
import topbarBackground from '@/web/assets/images/topbar-background.png'
import useIntersectionObserver from '@/web/hooks/useIntersectionObserver'
import { AnimatePresence, motion } from 'framer-motion'
import { scrollToBottom } from '@/web/utils/common'
import { throttle } from 'lodash-es'
const tabs = [
{
@ -31,7 +37,9 @@ const tabs = [
const Albums = () => {
const { data: albums } = useUserAlbums()
return <CoverRow albums={albums?.data} />
return (
<CoverRow albums={albums?.data} itemTitle='name' itemSubtitle='artist' />
)
}
const Playlists = () => {
@ -45,7 +53,7 @@ const Artists = () => {
return <ArtistRow artists={artists?.data || []} />
}
const Collections = () => {
const CollectionTabs = ({ showBg }: { showBg: boolean }) => {
const { librarySelectedTab: selectedTab } = useSnapshot(uiStates)
const setSelectedTab = (
id: 'playlists' | 'albums' | 'artists' | 'videos'
@ -54,18 +62,73 @@ const Collections = () => {
}
return (
<div>
<>
{/* Topbar background */}
<AnimatePresence>
{showBg && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={cx(
'pointer-events-none fixed top-0 left-10 z-10 hidden lg:block',
css`
height: 230px;
right: ${playerWidth + 32}px;
background-image: url(${topbarBackground});
`
)}
></motion.div>
)}
</AnimatePresence>
<Tabs
tabs={tabs}
value={selectedTab}
onChange={(id: string) => setSelectedTab(id)}
className='px-2.5 lg:px-0'
onChange={(id: string) => {
setSelectedTab(id)
scrollToBottom(true)
}}
className={cx(
'sticky z-10 -mb-10 px-2.5 lg:px-0',
css`
top: ${topbarHeight}px;
`
)}
/>
<div className='mt-6 px-2.5 lg:px-0'>
</>
)
}
const Collections = () => {
const { librarySelectedTab: selectedTab } = useSnapshot(uiStates)
const observePoint = useRef<HTMLDivElement | null>(null)
const { onScreen: isScrollReachBottom } =
useIntersectionObserver(observePoint)
const onScroll = throttle(() => {
if (isScrollReachBottom) return
scrollToBottom(true)
}, 500)
return (
<div>
<CollectionTabs showBg={isScrollReachBottom} />
<div
className={cx(
'no-scrollbar overflow-y-auto px-2.5 pt-16 pb-16 lg:px-0',
css`
height: calc(100vh - ${topbarHeight}px);
`
)}
onScroll={onScroll}
>
{selectedTab === 'albums' && <Albums />}
{selectedTab === 'playlists' && <Playlists />}
{selectedTab === 'artists' && <Artists />}
</div>
<div ref={observePoint}></div>
</div>
)
}