mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2024-11-22 15:10:24 +08:00
feat: updates
This commit is contained in:
parent
d4d8dd817b
commit
4c90db789b
|
@ -38,19 +38,21 @@ const ArtistRow = ({
|
|||
<div className={className}>
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h4 className='mb-6 text-12 font-medium uppercase dark:text-neutral-300 lg:text-14 lg:font-bold'>
|
||||
<h4
|
||||
className={cx(
|
||||
'text-12 font-medium uppercase dark:text-neutral-300 lg:text-14',
|
||||
'mx-2.5 mb-6 lg:mx-0 lg:font-bold'
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
{/* Artists */}
|
||||
{artists && (
|
||||
<div className='no-scrollbar -ml-2.5 flex w-screen snap-x overflow-x-scroll lg:ml-auto lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
<div className='no-scrollbar flex snap-x overflow-x-scroll lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
{artists.map(artist => (
|
||||
<div
|
||||
className='mr-5 snap-start first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
|
||||
key={artist.id}
|
||||
>
|
||||
<div className='snap-start px-2.5 lg:px-0' key={artist.id}>
|
||||
<Artist artist={artist} key={artist.id} />
|
||||
</div>
|
||||
))}
|
||||
|
@ -59,12 +61,9 @@ const ArtistRow = ({
|
|||
|
||||
{/* Placeholder */}
|
||||
{placeholderRow && !artists && (
|
||||
<div className='no-scrollbar -ml-2.5 flex w-screen overflow-x-scroll lg:ml-auto lg:grid lg:w-auto lg:grid-cols-5 lg:gap-10'>
|
||||
<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='mr-5 first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
|
||||
key={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={{
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import useBreakpoint from '@/web/hooks/useBreakpoint'
|
||||
import { ReactQueryDevtools } from 'react-query/devtools'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const Devtool = () => {
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = ['sm', 'md'].includes(breakpoint)
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
<ReactQueryDevtools
|
||||
initialIsOpen={false}
|
||||
|
@ -12,7 +12,7 @@ const Devtool = () => {
|
|||
position: 'fixed',
|
||||
bottom: isMobile ? 'auto' : 0,
|
||||
right: 0,
|
||||
top: isMobile ? 0 : 1,
|
||||
top: isMobile ? 0 : 'auto',
|
||||
left: 'auto',
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -14,7 +14,7 @@ const LayoutMobile = () => {
|
|||
|
||||
return (
|
||||
<div id='layout' className='select-none bg-white pb-32 pt-3 dark:bg-black'>
|
||||
<main className='min-h-screen overflow-y-auto overflow-x-hidden px-2.5 pb-16'>
|
||||
<main className='min-h-screen overflow-y-auto overflow-x-hidden pb-16'>
|
||||
<TopbarMobile />
|
||||
<Router />
|
||||
</main>
|
||||
|
@ -47,6 +47,19 @@ const LayoutMobile = () => {
|
|||
|
||||
<MenuBar />
|
||||
</div>
|
||||
|
||||
{/* Notch background */}
|
||||
{isIOS && isSafari && isPWA && (
|
||||
<div
|
||||
className={cx(
|
||||
'fixed left-0 right-0 bg-black/30 backdrop-blur-sm',
|
||||
css`
|
||||
top: -50px;
|
||||
height: 50px;
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ const PlayLikedSongsCard = () => {
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'flex flex-col justify-between rounded-24 p-8 dark:bg-night-600',
|
||||
'mx-2.5 flex flex-col justify-between rounded-24 p-8 dark:bg-white/10 lg:mx-0',
|
||||
css`
|
||||
height: 322px;
|
||||
`
|
||||
|
|
|
@ -4,6 +4,7 @@ const Tabs = ({
|
|||
tabs,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
}: {
|
||||
tabs: {
|
||||
id: string
|
||||
|
@ -11,9 +12,10 @@ const Tabs = ({
|
|||
}[]
|
||||
value: string
|
||||
onChange: (id: string) => void
|
||||
className?: string
|
||||
}) => {
|
||||
return (
|
||||
<div className='no-scrollbar flex overflow-y-auto'>
|
||||
<div className={cx('no-scrollbar flex overflow-y-auto', className)}>
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
key={tab.id}
|
||||
|
|
|
@ -4,7 +4,7 @@ import SettingsButton from './SettingsButton'
|
|||
|
||||
const TopbarMobile = () => {
|
||||
return (
|
||||
<div className='mb-5 mt-7 flex'>
|
||||
<div className='mb-5 mt-7 flex px-2.5'>
|
||||
<div className='flex-grow'>
|
||||
<SearchBox />
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { player } from '@/web/store'
|
|||
import { useSnapshot } from 'valtio'
|
||||
import Wave from './Wave'
|
||||
import Icon from '@/web/components/Icon'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const TrackList = ({
|
||||
tracks,
|
||||
|
@ -20,9 +21,14 @@ const TrackList = ({
|
|||
() => playerSnapshot.track,
|
||||
[playerSnapshot.track]
|
||||
)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLElement>, trackID: number) => {
|
||||
if (e.detail === 2) onPlay?.(trackID)
|
||||
if (isMobile) {
|
||||
onPlay?.(trackID)
|
||||
} else {
|
||||
if (e.detail === 2) onPlay?.(trackID)
|
||||
}
|
||||
}
|
||||
|
||||
const playing = useMemo(
|
||||
|
@ -38,7 +44,12 @@ const TrackList = ({
|
|||
onClick={e => handleClick(e, track.id)}
|
||||
className='group relative flex items-center py-2 text-16 font-medium text-neutral-200 transition duration-300 ease-in-out'
|
||||
>
|
||||
<div className='mr-6'>{String(track.no).padStart(2, '0')}</div>
|
||||
{/* Track no */}
|
||||
<div className='mr-3 lg:mr-6'>
|
||||
{String(track.no).padStart(2, '0')}
|
||||
</div>
|
||||
|
||||
{/* Track name */}
|
||||
<div className='flex flex-grow items-center'>
|
||||
{track.name}
|
||||
{playingTrack?.id === track.id && (
|
||||
|
@ -47,7 +58,9 @@ const TrackList = ({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mr-12 flex opacity-0 transition-opacity group-hover:opacity-100'>
|
||||
|
||||
{/* Desktop context menu */}
|
||||
<div className='mr-12 hidden opacity-0 transition-opacity group-hover:opacity-100 lg:flex'>
|
||||
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-brand-600 text-white/80'>
|
||||
{/* <Icon name='play' className='h-7 w-7' /> */}
|
||||
</div>
|
||||
|
@ -58,7 +71,14 @@ const TrackList = ({
|
|||
{/* <Icon name='play' className='h-7 w-7' /> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-right'>
|
||||
|
||||
{/* Mobile menu */}
|
||||
<div className='lg:hidden'>
|
||||
<div className='h-10 w-10 rounded-full bg-night-900'></div>
|
||||
</div>
|
||||
|
||||
{/* Track duration */}
|
||||
<div className='hidden text-right lg:block'>
|
||||
{formatDuration(track.dt, 'en', 'hh:mm:ss')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import Icon from '@/web/components/Icon'
|
|||
import dayjs from 'dayjs'
|
||||
import { useMemo } from 'react'
|
||||
import Image from './Image'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const TrackListHeader = ({
|
||||
album,
|
||||
|
@ -18,18 +19,22 @@ 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 isMobile = useIsMobile()
|
||||
|
||||
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'grid grid-rows-1 gap-10',
|
||||
css`
|
||||
grid-template-columns: 318px auto;
|
||||
`
|
||||
'mx-2.5 rounded-48 p-8 dark:bg-white/10',
|
||||
'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
|
||||
!isMobile &&
|
||||
css`
|
||||
grid-template-columns: 318px auto;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{/* Cover */}
|
||||
<Image
|
||||
className='z-10 aspect-square w-full rounded-24'
|
||||
src={resizeImage(cover, 'lg')}
|
||||
|
@ -37,57 +42,77 @@ const TrackListHeader = ({
|
|||
/>
|
||||
|
||||
{/* Blur bg */}
|
||||
<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')}
|
||||
/>
|
||||
{!isMobile && (
|
||||
<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')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='flex flex-col justify-between'>
|
||||
<div>
|
||||
<div className='text-36 font-medium dark:text-neutral-100'>
|
||||
{/* Name */}
|
||||
<div className='mt-2.5 text-28 font-semibold dark:text-night-50 lg:mt-0 lg:text-36 lg:font-medium'>
|
||||
{album?.name || playlist?.name}
|
||||
</div>
|
||||
<div className='mt-6 text-24 font-medium dark:text-neutral-600'>
|
||||
|
||||
{/* Creator */}
|
||||
<div className='mt-2.5 text-24 font-medium dark:text-night-400 lg:mt-6'>
|
||||
{album?.artist.name || playlist?.creator.nickname}
|
||||
</div>
|
||||
<div className='mt-1 flex items-center text-14 font-bold dark:text-neutral-600'>
|
||||
|
||||
{/* Extra info */}
|
||||
<div className='mt-1 flex items-center text-12 font-medium dark:text-night-400 lg:text-14 lg:font-bold'>
|
||||
{/* Album info */}
|
||||
{!!album && (
|
||||
<>
|
||||
{album?.mark === 1056768 && (
|
||||
<Icon name='explicit' className='mb-px mr-1 h-3.5 w-3.5 ' />
|
||||
<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()} · {album?.songs.length}{' '}
|
||||
Tracks, {albumDuration}
|
||||
tracks, {albumDuration}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Playlist info */}
|
||||
{!!playlist && (
|
||||
<>
|
||||
Updated at {formatDate(playlist?.updateTime || 0, 'en')} ·{' '}
|
||||
{playlist.trackCount} Tracks
|
||||
{playlist.trackCount} tracks
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-neutral-600'>
|
||||
{album?.description || playlist?.description}
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
|
||||
<div className='z-10 flex'>
|
||||
{/* Actions */}
|
||||
<div className='mt-11 flex items-end justify-between lg:z-10 lg:mt-4 lg:justify-start'>
|
||||
<div className='flex items-end'>
|
||||
<button className='mr-2.5 h-14 w-14 rounded-full dark:bg-white/10 lg:mr-6 lg:h-[72px] lg:w-[72px]'></button>
|
||||
<button className='h-14 w-14 rounded-full dark:bg-white/10 lg:mr-6 lg:h-[72px] lg:w-[72px]'></button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onPlay()}
|
||||
className='h-[72px] w-[170px] rounded-full dark:bg-brand-700'
|
||||
className='h-14 w-[125px] rounded-full dark:bg-brand-700 lg:h-[72px] lg:w-[170px]'
|
||||
></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-white/10'></button>
|
||||
<button className='ml-6 h-[72px] w-[72px] rounded-full dark:bg-white/10'></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { createBreakpoint } from 'react-use'
|
||||
|
||||
const useBreakpoint = createBreakpoint({
|
||||
sm: 767,
|
||||
md: 1023,
|
||||
lg: 1279,
|
||||
xl: 1535,
|
||||
sm: 640,
|
||||
md: 768,
|
||||
lg: 1024,
|
||||
xl: 1280,
|
||||
'2xl': 1536,
|
||||
}) as () => 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/public/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' www.googletagmanager.com;" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<title>YesPlayMusic</title>
|
||||
</head>
|
||||
|
||||
|
|
|
@ -25,7 +25,9 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
|
|||
album =>
|
||||
['专辑', 'EP/Single', 'EP'].includes(album.type) && album.size > 1
|
||||
)
|
||||
const singles = allReleases.filter(album => album.type === 'Single')
|
||||
const singles = allReleases.filter(
|
||||
album => album.type === 'Single' || album.size === 1
|
||||
)
|
||||
|
||||
const qualifiedAlbums = [...filteredAlbums, ...singles]
|
||||
|
||||
|
@ -63,17 +65,10 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
|
|||
return (
|
||||
<div>
|
||||
{/* Dividing line */}
|
||||
<div
|
||||
className={cx(
|
||||
'h-px bg-white/20',
|
||||
css`
|
||||
margin: 30px 0;
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
<div className={cx('mx-2.5 my-7.5 h-px bg-white/10 lg:mx-0')}></div>
|
||||
|
||||
{/* Title */}
|
||||
<div className='mb-5 text-14 font-bold text-neutral-300'>
|
||||
<div className='mx-2.5 mb-5 text-14 font-bold text-neutral-300 lg:mx-0'>
|
||||
MORE BY{' '}
|
||||
<NavLink
|
||||
to={`/artist/${album?.artist.id}`}
|
||||
|
@ -83,7 +78,7 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
|
|||
</NavLink>
|
||||
</div>
|
||||
|
||||
<CoverRow albums={filteredAlbums} />
|
||||
<CoverRow albums={filteredAlbums} className='mx-2.5 lg:mx-0' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -120,7 +115,7 @@ const Album = () => {
|
|||
<TrackListHeader album={album?.album} onPlay={onPlay} />
|
||||
<TrackList
|
||||
tracks={tracks?.songs || album?.songs || album?.album.songs}
|
||||
className='z-10 mt-10'
|
||||
className='z-10 mx-2.5 mt-3 lg:mx-0 lg:mt-10'
|
||||
onPlay={onPlay}
|
||||
/>
|
||||
<MoreByArtist album={album?.album} />
|
||||
|
|
|
@ -78,13 +78,14 @@ const My = () => {
|
|||
tabs={tabs}
|
||||
value={selectedTab}
|
||||
onChange={(id: string) => setSelectedTab(id)}
|
||||
className='px-2.5 lg:px-0'
|
||||
/>
|
||||
<CoverRow
|
||||
playlists={
|
||||
selectedTab === 'playlists' ? playlists?.playlist : undefined
|
||||
}
|
||||
albums={selectedTab === 'albums' ? albums?.data : undefined}
|
||||
className='mt-6'
|
||||
className='mt-6 px-2.5 lg:px-0'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -138,4 +138,5 @@ a {
|
|||
|
||||
html {
|
||||
background-color: black;
|
||||
min-height: calc(100% + env(safe-area-inset-top));
|
||||
}
|
||||
|
|
|
@ -28,18 +28,7 @@ module.exports = {
|
|||
900: '#7EB000',
|
||||
},
|
||||
day: {
|
||||
100: '#FCFCFC',
|
||||
200: '#F8F8F8',
|
||||
300: '#F4F4F4',
|
||||
400: '#F0F0F0',
|
||||
500: '#EDEDED',
|
||||
600: '#E9E9E9',
|
||||
700: '#E5E5E5',
|
||||
800: '#E2E2E2',
|
||||
900: '#DEDEDE',
|
||||
},
|
||||
night: {
|
||||
50: '#545454',
|
||||
50: '#B6B6B6',
|
||||
100: '#535353',
|
||||
200: '#505050',
|
||||
300: '#484848',
|
||||
|
@ -48,6 +37,17 @@ module.exports = {
|
|||
600: '#0E0E0E',
|
||||
700: '#060606',
|
||||
800: '#020202',
|
||||
},
|
||||
night: {
|
||||
50: '#BFBFBF',
|
||||
100: '#A8A8A8',
|
||||
200: '#7B7B7B',
|
||||
300: '#606060',
|
||||
400: '#585858',
|
||||
500: '#4A4A4A',
|
||||
600: '#464646',
|
||||
700: '#3F3F3F',
|
||||
800: '#373737',
|
||||
900: '#313131',
|
||||
},
|
||||
neutral: {
|
||||
|
@ -82,10 +82,14 @@ module.exports = {
|
|||
12: '12px',
|
||||
20: '20px',
|
||||
24: '24px',
|
||||
48: '48px',
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['Roboto Mono', 'ui-monospace'],
|
||||
},
|
||||
margin: {
|
||||
7.5: '1.875rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
|
|
Loading…
Reference in New Issue
Block a user