feat: updates

This commit is contained in:
qier222 2022-06-09 20:16:05 +08:00
parent d4d8dd817b
commit 4c90db789b
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
14 changed files with 144 additions and 82 deletions

View file

@ -38,19 +38,21 @@ const ArtistRow = ({
<div className={className}> <div className={className}>
{/* Title */} {/* Title */}
{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} {title}
</h4> </h4>
)} )}
{/* Artists */} {/* Artists */}
{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 => ( {artists.map(artist => (
<div <div className='snap-start px-2.5 lg:px-0' key={artist.id}>
className='mr-5 snap-start first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
key={artist.id}
>
<Artist artist={artist} key={artist.id} /> <Artist artist={artist} key={artist.id} />
</div> </div>
))} ))}
@ -59,12 +61,9 @@ const ArtistRow = ({
{/* Placeholder */} {/* Placeholder */}
{placeholderRow && !artists && ( {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 => ( {[...new Array(placeholderRow * 5).keys()].map(i => (
<div <div className='snap-start px-2.5 lg:px-0' key={i}>
className='mr-5 first-of-type:ml-2.5 last-of-type:mr-2.5 lg:mr-0'
key={i}
>
<div <div
className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800' className='aspect-square w-full rounded-full bg-white dark:bg-neutral-800'
style={{ style={{

View file

@ -1,9 +1,9 @@
import useBreakpoint from '@/web/hooks/useBreakpoint' import useBreakpoint from '@/web/hooks/useBreakpoint'
import { ReactQueryDevtools } from 'react-query/devtools' import { ReactQueryDevtools } from 'react-query/devtools'
import useIsMobile from '@/web/hooks/useIsMobile'
const Devtool = () => { const Devtool = () => {
const breakpoint = useBreakpoint() const isMobile = useIsMobile()
const isMobile = ['sm', 'md'].includes(breakpoint)
return ( return (
<ReactQueryDevtools <ReactQueryDevtools
initialIsOpen={false} initialIsOpen={false}
@ -12,7 +12,7 @@ const Devtool = () => {
position: 'fixed', position: 'fixed',
bottom: isMobile ? 'auto' : 0, bottom: isMobile ? 'auto' : 0,
right: 0, right: 0,
top: isMobile ? 0 : 1, top: isMobile ? 0 : 'auto',
left: 'auto', left: 'auto',
}, },
}} }}

View file

@ -14,7 +14,7 @@ const LayoutMobile = () => {
return ( return (
<div id='layout' className='select-none bg-white pb-32 pt-3 dark:bg-black'> <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 /> <TopbarMobile />
<Router /> <Router />
</main> </main>
@ -47,6 +47,19 @@ const LayoutMobile = () => {
<MenuBar /> <MenuBar />
</div> </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> </div>
) )
} }

View file

@ -67,7 +67,7 @@ const PlayLikedSongsCard = () => {
return ( return (
<div <div
className={cx( 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` css`
height: 322px; height: 322px;
` `

View file

@ -4,6 +4,7 @@ const Tabs = ({
tabs, tabs,
value, value,
onChange, onChange,
className,
}: { }: {
tabs: { tabs: {
id: string id: string
@ -11,9 +12,10 @@ const Tabs = ({
}[] }[]
value: string value: string
onChange: (id: string) => void onChange: (id: string) => void
className?: string
}) => { }) => {
return ( return (
<div className='no-scrollbar flex overflow-y-auto'> <div className={cx('no-scrollbar flex overflow-y-auto', className)}>
{tabs.map(tab => ( {tabs.map(tab => (
<div <div
key={tab.id} key={tab.id}

View file

@ -4,7 +4,7 @@ import SettingsButton from './SettingsButton'
const TopbarMobile = () => { const TopbarMobile = () => {
return ( return (
<div className='mb-5 mt-7 flex'> <div className='mb-5 mt-7 flex px-2.5'>
<div className='flex-grow'> <div className='flex-grow'>
<SearchBox /> <SearchBox />
</div> </div>

View file

@ -5,6 +5,7 @@ import { player } from '@/web/store'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import Wave from './Wave' import Wave from './Wave'
import Icon from '@/web/components/Icon' import Icon from '@/web/components/Icon'
import useIsMobile from '@/web/hooks/useIsMobile'
const TrackList = ({ const TrackList = ({
tracks, tracks,
@ -20,9 +21,14 @@ const TrackList = ({
() => playerSnapshot.track, () => playerSnapshot.track,
[playerSnapshot.track] [playerSnapshot.track]
) )
const isMobile = useIsMobile()
const handleClick = (e: React.MouseEvent<HTMLElement>, trackID: number) => { 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( const playing = useMemo(
@ -38,7 +44,12 @@ const TrackList = ({
onClick={e => handleClick(e, track.id)} 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' 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'> <div className='flex flex-grow items-center'>
{track.name} {track.name}
{playingTrack?.id === track.id && ( {playingTrack?.id === track.id && (
@ -47,7 +58,9 @@ const TrackList = ({
</div> </div>
)} )}
</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'> <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' /> */} {/* <Icon name='play' className='h-7 w-7' /> */}
</div> </div>
@ -58,7 +71,14 @@ const TrackList = ({
{/* <Icon name='play' className='h-7 w-7' /> */} {/* <Icon name='play' className='h-7 w-7' /> */}
</div> </div>
</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')} {formatDuration(track.dt, 'en', 'hh:mm:ss')}
</div> </div>
</div> </div>

View file

@ -4,6 +4,7 @@ import Icon from '@/web/components/Icon'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useMemo } from 'react' import { useMemo } from 'react'
import Image from './Image' import Image from './Image'
import useIsMobile from '@/web/hooks/useIsMobile'
const TrackListHeader = ({ const TrackListHeader = ({
album, album,
@ -18,18 +19,22 @@ const TrackListHeader = ({
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0 const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
return formatDuration(duration, 'en', 'hh[hr] mm[min]') return formatDuration(duration, 'en', 'hh[hr] mm[min]')
}, [album?.songs]) }, [album?.songs])
const isMobile = useIsMobile()
const cover = album?.picUrl || playlist?.coverImgUrl || '' const cover = album?.picUrl || playlist?.coverImgUrl || ''
return ( return (
<div <div
className={cx( className={cx(
'grid grid-rows-1 gap-10', 'mx-2.5 rounded-48 p-8 dark:bg-white/10',
css` 'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
grid-template-columns: 318px auto; !isMobile &&
` css`
grid-template-columns: 318px auto;
`
)} )}
> >
{/* Cover */}
<Image <Image
className='z-10 aspect-square w-full rounded-24' className='z-10 aspect-square w-full rounded-24'
src={resizeImage(cover, 'lg')} src={resizeImage(cover, 'lg')}
@ -37,57 +42,77 @@ const TrackListHeader = ({
/> />
{/* Blur bg */} {/* Blur bg */}
<img {!isMobile && (
className={cx( <img
'absolute z-0 object-cover opacity-70', className={cx(
css` 'absolute z-0 object-cover opacity-70',
top: -400px; css`
left: -370px; top: -400px;
width: 1572px; left: -370px;
height: 528px; width: 1572px;
filter: blur(256px) saturate(1.2); height: 528px;
` filter: blur(256px) saturate(1.2);
)} `
src={resizeImage(cover, 'sm')} )}
/> src={resizeImage(cover, 'sm')}
/>
)}
<div className='flex flex-col justify-between'> <div className='flex flex-col justify-between'>
<div> <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} {album?.name || playlist?.name}
</div> </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} {album?.artist.name || playlist?.creator.nickname}
</div> </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 && (
<> <>
{album?.mark === 1056768 && ( {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}{' '} {dayjs(album?.publishTime || 0).year()} · {album?.songs.length}{' '}
Tracks, {albumDuration} tracks, {albumDuration}
</> </>
)} )}
{/* Playlist info */}
{!!playlist && ( {!!playlist && (
<> <>
Updated at {formatDate(playlist?.updateTime || 0, 'en')} ·{' '} Updated at {formatDate(playlist?.updateTime || 0, 'en')} ·{' '}
{playlist.trackCount} Tracks {playlist.trackCount} tracks
</> </>
)} )}
</div> </div>
<div className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-neutral-600'>
{album?.description || playlist?.description} {/* Description */}
</div> {!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>
<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 <button
onClick={() => onPlay()} 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>
<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> </div>
</div> </div>

View file

@ -1,10 +1,10 @@
import { createBreakpoint } from 'react-use' import { createBreakpoint } from 'react-use'
const useBreakpoint = createBreakpoint({ const useBreakpoint = createBreakpoint({
sm: 767, sm: 640,
md: 1023, md: 768,
lg: 1279, lg: 1024,
xl: 1535, xl: 1280,
'2xl': 1536, '2xl': 1536,
}) as () => 'sm' | 'md' | 'lg' | 'xl' | '2xl' }) as () => 'sm' | 'md' | 'lg' | 'xl' | '2xl'

View file

@ -4,8 +4,10 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/public/favicon.svg" /> <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 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> <title>YesPlayMusic</title>
</head> </head>

View file

@ -25,7 +25,9 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
album => album =>
['专辑', 'EP/Single', 'EP'].includes(album.type) && album.size > 1 ['专辑', '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] const qualifiedAlbums = [...filteredAlbums, ...singles]
@ -63,17 +65,10 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
return ( return (
<div> <div>
{/* Dividing line */} {/* Dividing line */}
<div <div className={cx('mx-2.5 my-7.5 h-px bg-white/10 lg:mx-0')}></div>
className={cx(
'h-px bg-white/20',
css`
margin: 30px 0;
`
)}
></div>
{/* Title */} {/* 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{' '} MORE BY{' '}
<NavLink <NavLink
to={`/artist/${album?.artist.id}`} to={`/artist/${album?.artist.id}`}
@ -83,7 +78,7 @@ const MoreByArtist = ({ album }: { album?: Album }) => {
</NavLink> </NavLink>
</div> </div>
<CoverRow albums={filteredAlbums} /> <CoverRow albums={filteredAlbums} className='mx-2.5 lg:mx-0' />
</div> </div>
) )
} }
@ -120,7 +115,7 @@ const Album = () => {
<TrackListHeader album={album?.album} onPlay={onPlay} /> <TrackListHeader album={album?.album} onPlay={onPlay} />
<TrackList <TrackList
tracks={tracks?.songs || album?.songs || album?.album.songs} 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} onPlay={onPlay}
/> />
<MoreByArtist album={album?.album} /> <MoreByArtist album={album?.album} />

View file

@ -78,13 +78,14 @@ const My = () => {
tabs={tabs} tabs={tabs}
value={selectedTab} value={selectedTab}
onChange={(id: string) => setSelectedTab(id)} onChange={(id: string) => setSelectedTab(id)}
className='px-2.5 lg:px-0'
/> />
<CoverRow <CoverRow
playlists={ playlists={
selectedTab === 'playlists' ? playlists?.playlist : undefined selectedTab === 'playlists' ? playlists?.playlist : undefined
} }
albums={selectedTab === 'albums' ? albums?.data : undefined} albums={selectedTab === 'albums' ? albums?.data : undefined}
className='mt-6' className='mt-6 px-2.5 lg:px-0'
/> />
</div> </div>
</div> </div>

View file

@ -138,4 +138,5 @@ a {
html { html {
background-color: black; background-color: black;
min-height: calc(100% + env(safe-area-inset-top));
} }

View file

@ -28,18 +28,7 @@ module.exports = {
900: '#7EB000', 900: '#7EB000',
}, },
day: { day: {
100: '#FCFCFC', 50: '#B6B6B6',
200: '#F8F8F8',
300: '#F4F4F4',
400: '#F0F0F0',
500: '#EDEDED',
600: '#E9E9E9',
700: '#E5E5E5',
800: '#E2E2E2',
900: '#DEDEDE',
},
night: {
50: '#545454',
100: '#535353', 100: '#535353',
200: '#505050', 200: '#505050',
300: '#484848', 300: '#484848',
@ -48,6 +37,17 @@ module.exports = {
600: '#0E0E0E', 600: '#0E0E0E',
700: '#060606', 700: '#060606',
800: '#020202', 800: '#020202',
},
night: {
50: '#BFBFBF',
100: '#A8A8A8',
200: '#7B7B7B',
300: '#606060',
400: '#585858',
500: '#4A4A4A',
600: '#464646',
700: '#3F3F3F',
800: '#373737',
900: '#313131', 900: '#313131',
}, },
neutral: { neutral: {
@ -82,10 +82,14 @@ module.exports = {
12: '12px', 12: '12px',
20: '20px', 20: '20px',
24: '24px', 24: '24px',
48: '48px',
}, },
fontFamily: { fontFamily: {
mono: ['Roboto Mono', 'ui-monospace'], mono: ['Roboto Mono', 'ui-monospace'],
}, },
margin: {
7.5: '1.875rem',
},
}, },
}, },
variants: {}, variants: {},