mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 13:48:02 +00:00
feat: updates
This commit is contained in:
parent
222fb02355
commit
47e41dea9b
24 changed files with 380 additions and 130 deletions
|
|
@ -15,13 +15,13 @@ export default function useArtists(ids: number[]) {
|
|||
{
|
||||
enabled: !!ids && ids.length > 0,
|
||||
staleTime: 5 * 60 * 1000, // 5 mins
|
||||
// placeholderData: (): FetchArtistResponse[] =>
|
||||
// window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
|
||||
// api: APIs.Artist,
|
||||
// query: {
|
||||
// ids,
|
||||
// },
|
||||
// }),
|
||||
initialData: (): FetchArtistResponse[] =>
|
||||
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
|
||||
api: APIs.Artists,
|
||||
query: {
|
||||
ids,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default function useUserListenedRecords(params: {
|
|||
enabled: !!uid,
|
||||
placeholderData: (): FetchListenedRecordsResponse =>
|
||||
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
|
||||
api: APIs.UserArtists,
|
||||
api: APIs.ListenedRecords,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,30 @@ const Artist = ({ artist }: { artist: Artist }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const Placeholder = ({ row }: { row: number }) => {
|
||||
return (
|
||||
<div className='no-scrollbar flex snap-x overflow-x-scroll lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
{[...new Array(row * 5).keys()].map(i => (
|
||||
<div
|
||||
className='flex snap-start flex-col items-center px-2.5 lg:px-0'
|
||||
key={i}
|
||||
>
|
||||
<div
|
||||
className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800'
|
||||
style={{
|
||||
minHeight: '96px',
|
||||
minWidth: '96px',
|
||||
}}
|
||||
/>
|
||||
<div className='line-clamp-1 mt-2.5 w-1/2 rounded-full text-12 font-medium text-transparent dark:bg-neutral-800 lg:text-14 lg:font-bold'>
|
||||
NAME
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ArtistRow = ({
|
||||
artists,
|
||||
title,
|
||||
|
|
@ -70,24 +94,7 @@ const ArtistRow = ({
|
|||
)}
|
||||
|
||||
{/* Placeholder */}
|
||||
{placeholderRow && !artists && (
|
||||
<div className='no-scrollbar flex snap-x overflow-x-scroll lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
{[...new Array(placeholderRow * 5).keys()].map(i => (
|
||||
<div className='snap-start px-2.5 lg:px-0' key={i}>
|
||||
<div
|
||||
className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800'
|
||||
style={{
|
||||
minHeight: '96px',
|
||||
minWidth: '96px',
|
||||
}}
|
||||
/>
|
||||
<div className='mt-2.5 text-12 font-medium text-transparent lg:text-14 lg:font-bold'>
|
||||
PLACE
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{placeholderRow && !artists && <Placeholder row={placeholderRow} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ const TrackList = ({ className }: { className?: string }) => {
|
|||
overscan={10}
|
||||
components={{
|
||||
Header: () => <div className='h-8'></div>,
|
||||
Footer: () => <div className='h-1'></div>,
|
||||
Footer: () => <div className='h-8'></div>,
|
||||
}}
|
||||
itemContent={(index, track) => (
|
||||
<Track
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ const NavigationButtons = () => {
|
|||
await controlsBack.start({ x: -5 })
|
||||
await controlsBack.start({ x: 0 })
|
||||
}}
|
||||
className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600'
|
||||
className='app-region-no-drag rounded-full bg-white/10 p-2.5 text-white/40 backdrop-blur-3xl'
|
||||
>
|
||||
<motion.div animate={controlsBack} transition={transition}>
|
||||
<Icon name='back' className='h-7 w-7 text-neutral-500' />
|
||||
<Icon name='back' className='h-7 w-7 ' />
|
||||
</motion.div>
|
||||
</button>
|
||||
<button
|
||||
|
|
@ -32,10 +32,10 @@ const NavigationButtons = () => {
|
|||
await controlsForward.start({ x: 5 })
|
||||
await controlsForward.start({ x: 0 })
|
||||
}}
|
||||
className='app-region-no-drag ml-2.5 rounded-full bg-day-600 p-2.5 dark:bg-night-600'
|
||||
className='app-region-no-drag ml-2.5 rounded-full bg-white/10 p-2.5 text-white/40 backdrop-blur-3xl'
|
||||
>
|
||||
<motion.div animate={controlsForward} transition={transition}>
|
||||
<Icon name='forward' className='h-7 w-7 text-neutral-500' />
|
||||
<Icon name='forward' className='h-7 w-7' />
|
||||
</motion.div>
|
||||
</button>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const SearchBox = () => {
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'app-region-no-drag flex items-center rounded-full bg-day-600 p-2.5 text-neutral-500 dark:bg-night-600',
|
||||
'app-region-no-drag flex items-center rounded-full bg-white/10 p-2.5 text-white/40 backdrop-blur-3xl',
|
||||
css`
|
||||
${bp.lg} {
|
||||
min-width: 284px;
|
||||
|
|
@ -23,7 +23,7 @@ const SearchBox = () => {
|
|||
<input
|
||||
placeholder='Search'
|
||||
className={cx(
|
||||
'flex-shrink bg-transparent font-medium placeholder:text-neutral-500 dark:text-neutral-200',
|
||||
'flex-shrink bg-transparent font-medium placeholder:text-white/40 dark:text-white/80',
|
||||
css`
|
||||
@media (max-width: 420px) {
|
||||
width: 142px;
|
||||
|
|
|
|||
|
|
@ -8,14 +8,21 @@ import uiStates from '@/web/states/uiStates'
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { ease } from '@/web/utils/const'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
const Background = () => {
|
||||
const { hideTopbarBackground } = useSnapshot(uiStates)
|
||||
const location = useLocation()
|
||||
const isPageHaveBlurBG =
|
||||
location.pathname.startsWith('/album/') ||
|
||||
location.pathname.startsWith('/artist/') ||
|
||||
location.pathname.startsWith('/playlist/')
|
||||
const show = !hideTopbarBackground || !isPageHaveBlurBG
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{!hideTopbarBackground && (
|
||||
{show && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { ease } from '@/web/utils/const'
|
|||
import { injectGlobal } from '@emotion/css'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import BlurBackground from '@/web/components/New/BlurBackground'
|
||||
import useAppleMusicAlbum from '@/web/hooks/useAppleMusicAlbum'
|
||||
import { AppleMusicAlbum } from '@/shared/AppleMusic'
|
||||
|
||||
injectGlobal`
|
||||
.plyr__video-wrapper,
|
||||
|
|
@ -47,12 +49,20 @@ const VideoCover = ({ source }: { source?: string }) => {
|
|||
|
||||
const Cover = memo(
|
||||
({ album, playlist }: { album?: Album; playlist?: Playlist }) => {
|
||||
const isMobile = useIsMobile()
|
||||
const { data: videoCover } = useVideoCover({
|
||||
const { data: albumFromApple } = 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,
|
||||
})
|
||||
const videoCover =
|
||||
albumFromApple?.attributes?.editorialVideo?.motionSquareVideo1x1?.video ||
|
||||
videoCoverFromRemote
|
||||
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
||||
|
||||
return (
|
||||
|
|
@ -111,6 +121,12 @@ const TrackListHeader = ({
|
|||
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
|
||||
return formatDuration(duration, 'en', 'hh[hr] mm[min]')
|
||||
}, [album?.songs])
|
||||
const { data: albumFromApple, isLoading: isLoadingAlbumFromApple } =
|
||||
useAppleMusicAlbum({
|
||||
id: album?.id,
|
||||
name: album?.name,
|
||||
artist: album?.artist.name,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -172,9 +188,17 @@ const TrackListHeader = ({
|
|||
|
||||
{/* Description */}
|
||||
{!isMobile && (
|
||||
<div className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-night-400 '>
|
||||
{album?.description || playlist?.description}
|
||||
</div>
|
||||
<div
|
||||
className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-night-400 '
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: !isLoadingAlbumFromApple
|
||||
? albumFromApple?.attributes?.editorialNotes?.standard ||
|
||||
album?.description ||
|
||||
playlist?.description ||
|
||||
''
|
||||
: '',
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
26
packages/web/hooks/useAppleMusicAlbum.ts
Normal file
26
packages/web/hooks/useAppleMusicAlbum.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
export default function useAppleMusicAlbum(props: {
|
||||
id?: number
|
||||
name?: string
|
||||
artist?: string
|
||||
}) {
|
||||
const { id, name, artist } = props
|
||||
return useQuery(
|
||||
['useAppleMusicAlbum', props],
|
||||
async () => {
|
||||
if (!id || !name || !artist) return
|
||||
return window.ipcRenderer?.invoke(IpcChannels.GetAlbumFromAppleMusic, {
|
||||
id,
|
||||
name,
|
||||
artist,
|
||||
})
|
||||
},
|
||||
{
|
||||
enabled: !!id && !!name && !!artist,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
33
packages/web/hooks/useAppleMusicArtist.ts
Normal file
33
packages/web/hooks/useAppleMusicArtist.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { AppleMusicArtist } from '@/shared/AppleMusic'
|
||||
import { APIs } from '@/shared/CacheAPIs'
|
||||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
export default function useAppleMusicArtist(props: {
|
||||
id?: number
|
||||
name?: string
|
||||
}) {
|
||||
const { id, name } = props
|
||||
return useQuery(
|
||||
['useAppleMusicArtist', props],
|
||||
async () => {
|
||||
if (!id || !name) return
|
||||
return window.ipcRenderer?.invoke(IpcChannels.GetArtistFromAppleMusic, {
|
||||
id,
|
||||
name,
|
||||
})
|
||||
},
|
||||
{
|
||||
enabled: !!id && !!name,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: false,
|
||||
initialData: (): AppleMusicArtist =>
|
||||
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
|
||||
api: APIs.AppleMusicArtist,
|
||||
query: {
|
||||
id,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import axios from 'axios'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
|
|
@ -6,25 +5,14 @@ export default function useVideoCover(props: {
|
|||
id?: number
|
||||
name?: string
|
||||
artist?: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const { id, name, artist } = props
|
||||
const { id, name, artist, enabled = true } = props
|
||||
return useQuery(
|
||||
['useVideoCover', props],
|
||||
async () => {
|
||||
if (!id || !name || !artist) return
|
||||
|
||||
const fromMainProcess = await window.ipcRenderer?.invoke(
|
||||
IpcChannels.GetVideoCover,
|
||||
{
|
||||
id,
|
||||
name,
|
||||
artist,
|
||||
}
|
||||
)
|
||||
if (fromMainProcess) {
|
||||
return fromMainProcess
|
||||
}
|
||||
|
||||
const fromRemote = await axios.get('/yesplaymusic/video-cover', {
|
||||
params: props,
|
||||
})
|
||||
|
|
@ -33,7 +21,7 @@ export default function useVideoCover(props: {
|
|||
}
|
||||
},
|
||||
{
|
||||
enabled: !!id && !!name && !!artist,
|
||||
enabled: !!id && !!name && !!artist && enabled,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: false,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
import useAppleMusicArtist from '@/web/hooks/useAppleMusicArtist'
|
||||
|
||||
const ArtistInfo = ({ artist }: { artist?: Artist }) => {
|
||||
const isMobile = useIsMobile()
|
||||
const { data: artistFromApple, isLoading: isLoadingArtistFromApple } =
|
||||
useAppleMusicArtist({
|
||||
id: artist?.id,
|
||||
name: artist?.name,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='text-28 font-semibold text-night-50 lg:text-32'>
|
||||
{artist?.name}
|
||||
</div>
|
||||
<div className='text-white-400 mt-2.5 text-24 font-medium lg:mt-6'>
|
||||
<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'>
|
||||
|
|
@ -16,9 +23,9 @@ const ArtistInfo = ({ artist }: { artist?: Artist }) => {
|
|||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{!isMobile && (
|
||||
{!isMobile && !isLoadingArtistFromApple && (
|
||||
<div className='line-clamp-5 mt-6 text-14 font-bold text-night-400'>
|
||||
{artist?.briefDesc}
|
||||
{artistFromApple?.attributes?.artistBio || artist?.briefDesc}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,15 @@ import BlurBackground from '@/web/components/New/BlurBackground'
|
|||
import ArtistInfo from './ArtistInfo'
|
||||
import Actions from './Actions'
|
||||
import LatestRelease from './LatestRelease'
|
||||
import useAppleMusicArtist from '@/web/hooks/useAppleMusicArtist'
|
||||
|
||||
const Header = ({ artist }: { artist?: Artist }) => {
|
||||
const { data: artistFromApple, isLoading: isLoadingArtistFromApple } =
|
||||
useAppleMusicArtist({
|
||||
id: artist?.id,
|
||||
name: artist?.name,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
|
|
@ -27,10 +34,23 @@ const Header = ({ artist }: { artist?: Artist }) => {
|
|||
grid-area: cover;
|
||||
`
|
||||
)}
|
||||
src={resizeImage(artist?.img1v1Url || '', 'lg')}
|
||||
src={resizeImage(
|
||||
isLoadingArtistFromApple
|
||||
? ''
|
||||
: artistFromApple?.attributes?.artwork?.url ||
|
||||
artist?.img1v1Url ||
|
||||
'',
|
||||
'lg'
|
||||
)}
|
||||
/>
|
||||
|
||||
<BlurBackground cover={artist?.img1v1Url} />
|
||||
<BlurBackground
|
||||
cover={
|
||||
isLoadingArtistFromApple
|
||||
? ''
|
||||
: artistFromApple?.attributes?.artwork?.url || artist?.img1v1Url
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ class ScrollPositions {
|
|||
}
|
||||
|
||||
set(pathname: string, top: number) {
|
||||
console.log('set', pathname, top)
|
||||
const nestedPath = `/${pathname.split('/')[1]}`
|
||||
const restPath = pathname.split('/').slice(2).join('/')
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ export function resizeImage(
|
|||
md: '512',
|
||||
lg: '1024',
|
||||
}
|
||||
|
||||
if (url.includes('mzstatic.com')) {
|
||||
// from Apple Music
|
||||
return url.replace('{w}', sizeMap[size]).replace('{h}', sizeMap[size])
|
||||
}
|
||||
|
||||
return `${url}?param=${sizeMap[size]}y${sizeMap[size]}`.replace(
|
||||
/http(s?):\/\/p\d.music.126.net/,
|
||||
'https://p1.music.126.net'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue