feat: updates

This commit is contained in:
qier222 2022-04-04 17:51:07 +08:00
parent 9971418b8c
commit bbcf1f9340
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D
19 changed files with 306 additions and 15 deletions

View File

@ -10,6 +10,7 @@
"repository": "github:qier222/YesPlayMusic",
"main": "dist/main/index.js",
"scripts": {
"install": "node scripts/build.sqlite3.mjs",
"dev": "concurrently -n=vite,main -c=#646cff,#74b1be \"npm run dev:renderer\" \"node scripts/build.main.mjs --watch\"",
"dev:renderer": "vite dev",
"build:main": "node scripts/build.main.mjs",

View File

@ -1,4 +1,9 @@
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)
contextBridge.exposeInMainWorld('isElectron', true)
contextBridge.exposeInMainWorld('env', {
isElectron: true,
isLinux: process.platform === 'linux',
isMac: process.platform === 'darwin',
isWin: process.platform === 'win32',
})

View File

@ -5,10 +5,13 @@ import Player from '@/components/Player'
import Sidebar from '@/components/Sidebar'
import reactQueryClient from '@/utils/reactQueryClient'
import Main from './components/Main'
import TitleBar from './components/Titlebar'
const App = () => {
return (
<QueryClientProvider client={reactQueryClient}>
{window.env?.isWin && <TitleBar />}
<div id='layout' className='grid select-none grid-cols-[16rem_auto]'>
<Sidebar />
<Main />

View File

@ -3,6 +3,7 @@ import request from '@/utils/request'
export enum TrackApiNames {
FETCH_TRACKS = 'fetchTracks',
FETCH_AUDIO_SOURCE = 'fetchAudioSource',
FETCH_LYRIC = 'fetchLyric',
}
// 获取歌曲详情
@ -71,3 +72,51 @@ export function fetchAudioSource(
params,
})
}
// 获取歌词
export interface FetchLyricParams {
id: number
}
export interface FetchLyricResponse {
code: number
sgc: boolean
sfy: boolean
qfy: boolean
lyricUser?: {
id: number
status: number
demand: number
userid: number
nickname: string
uptime: number
}
transUser?: {
id: number
status: number
demand: number
userid: number
nickname: string
uptime: number
}
lrc: {
version: number
lyric: string
}
klyric?: {
version: number
lyric: string
}
tlyric?: {
version: number
lyric: string
}
}
export function fetchLyric(
params: FetchLyricParams
): Promise<FetchLyricResponse> {
return request({
url: '/lyric',
method: 'get',
params,
})
}

View File

@ -1,4 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0656 5.86692L12 6.71938L10.9344 5.86692C8.73386 4.10647 5.47034 4.82325 4.21005 7.34384C3.35797 9.04799 3.69197 11.1062 5.03922 12.4534L10.9393 18.3536C11.5251 18.9393 12.4749 18.9393 13.0607 18.3536L18.9608 12.4534C20.308 11.1062 20.642 9.04799 19.79 7.34384C18.5297 4.82325 15.2661 4.10647 13.0656 5.86692Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6877 7.75013C11.8703 7.89621 12.1297 7.89621 12.3124 7.75013L13.6903 6.64779C15.3937 5.28505 17.92 5.8399 18.8955 7.79106C19.5551 9.11022 19.2966 10.7034 18.2537 11.7463L12.3536 17.6465C12.1583 17.8417 11.8417 17.8417 11.6465 17.6465L5.74633 11.7463C4.70344 10.7034 4.4449 9.11022 5.10448 7.79106C6.08006 5.8399 8.60631 5.28505 10.3097 6.64779L11.6877 7.75013ZM12 5.43875L12.4409 5.08605C15.1386 2.92789 19.1394 3.80661 20.6844 6.89663C21.729 8.98577 21.3195 11.5089 19.6679 13.1605L13.7678 19.0607C12.7915 20.037 11.2085 20.037 10.2322 19.0607L4.33212 13.1605C2.6805 11.5089 2.27106 8.98577 3.31562 6.89663C4.86063 3.80661 8.86143 2.92789 11.5591 5.08605L12 5.43875Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6963 7.8793C11.8739 8.02633 12.1261 8.02633 12.3037 7.8793L13.6433 6.76983C15.2993 5.39827 17.7553 5.95672 18.7038 7.9205C19.345 9.24819 19.0937 10.8517 18.0798 11.9014L12.3437 17.8397C12.1539 18.0362 11.8461 18.0362 11.6563 17.8397L5.92022 11.9014C4.90633 10.8517 4.65498 9.24819 5.29622 7.9205C6.24467 5.95672 8.70067 5.39827 10.3567 6.76983L11.6963 7.8793ZM12 5.55297L12.4286 5.19799C15.0513 3.02586 18.9408 3.91027 20.4429 7.02028C21.4584 9.12294 21.0603 11.6624 19.4547 13.3247L13.7186 19.263C12.7694 20.2457 11.2305 20.2457 10.2814 19.263L4.54533 13.3247C2.93965 11.6624 2.54158 9.12294 3.55711 7.02028C5.05915 3.91027 8.9487 3.02586 11.5714 5.19799L12 5.55297Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 847 B

View File

@ -1,5 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0656 5.86692L12 6.71938L10.9344 5.86692C8.73386 4.10647 5.47034 4.82325 4.21005 7.34384C3.35797 9.04799 3.69197 11.1062 5.03922 12.4534L10.9393 18.3536C11.5251 18.9393 12.4749 18.9393 13.0607 18.3536L18.9608 12.4534C20.308 11.1062 20.642 9.04799 19.79 7.34384C18.5297 4.82325 15.2661 4.10647 13.0656 5.86692Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M13.6903 6.64779L12.3124 7.75012C12.1297 7.89621 11.8703 7.89621 11.6877 7.75012L10.3097 6.64779C8.60632 5.28504 6.08006 5.8399 5.10448 7.79105C4.4449 9.11021 4.70345 10.7034 5.74633 11.7463L11.6465 17.6464C11.8417 17.8417 12.1583 17.8417 12.3536 17.6464L18.2537 11.7463C19.2966 10.7034 19.5551 9.11021 18.8955 7.79105C17.92 5.8399 15.3937 5.28504 13.6903 6.64779Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5.43875L12.4409 5.08605C15.1386 2.92789 19.1394 3.80661 20.6844 6.89663C21.729 8.98577 21.3195 11.5089 19.6679 13.1605L13.7678 19.0607C12.7915 20.037 11.2085 20.037 10.2322 19.0607L4.33212 13.1605C2.6805 11.5089 2.27106 8.98577 3.31562 6.89663C4.86063 3.80661 8.86143 2.92789 11.5591 5.08605L12 5.43875ZM12.3124 7.75013L13.6903 6.64779C15.3937 5.28505 17.92 5.8399 18.8955 7.79106C19.5551 9.11022 19.2966 10.7034 18.2537 11.7463L12.3536 17.6465C12.1583 17.8417 11.8417 17.8417 11.6465 17.6465L5.74633 11.7463C4.70344 10.7034 4.4449 9.11022 5.10448 7.79106C6.08006 5.8399 8.60631 5.28505 10.3097 6.64779L11.6877 7.75013C11.8703 7.89621 12.1297 7.89621 12.3124 7.75013Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5.55297L12.4286 5.19799C15.0513 3.02586 18.9408 3.91027 20.4429 7.02028C21.4584 9.12294 21.0603 11.6624 19.4547 13.3247L13.7186 19.263C12.7694 20.2457 11.2305 20.2457 10.2814 19.263L4.54533 13.3247C2.93965 11.6624 2.54158 9.12294 3.55711 7.02028C5.05915 3.91027 8.9487 3.02586 11.5714 5.19799L12 5.55297Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 484 B

View File

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 425 B

View File

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 5.75A2.75 2.75 0 0 1 5.75 3h12.5A2.75 2.75 0 0 1 21 5.75v12.5A2.75 2.75 0 0 1 18.25 21H5.75A2.75 2.75 0 0 1 3 18.25V5.75ZM5.75 4.5c-.69 0-1.25.56-1.25 1.25v12.5c0 .69.56 1.25 1.25 1.25h12.5c.69 0 1.25-.56 1.25-1.25V5.75c0-.69-.56-1.25-1.25-1.25H5.75Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 197 B

View File

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.518 5H6.009a3.25 3.25 0 0 1 3.24-3h8.001A4.75 4.75 0 0 1 22 6.75v8a3.25 3.25 0 0 1-3 3.24v-1.508a1.75 1.75 0 0 0 1.5-1.732v-8a3.25 3.25 0 0 0-3.25-3.25h-8A1.75 1.75 0 0 0 7.518 5ZM5.25 6A3.25 3.25 0 0 0 2 9.25v9.5A3.25 3.25 0 0 0 5.25 22h9.5A3.25 3.25 0 0 0 18 18.75v-9.5A3.25 3.25 0 0 0 14.75 6h-9.5ZM3.5 9.25c0-.966.784-1.75 1.75-1.75h9.5c.967 0 1.75.784 1.75 1.75v9.5a1.75 1.75 0 0 1-1.75 1.75h-9.5a1.75 1.75 0 0 1-1.75-1.75v-9.5Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@ -5,7 +5,6 @@ import { resizeImage } from '@/utils/common'
import SvgIcon from '@/components/SvgIcon'
import ArtistInline from '@/components/ArtistsInline'
import { State as PlayerState, Mode as PlayerMode } from '@/utils/player'
import Skeleton from './Skeleton'
const MediaControls = () => {
const classes =
@ -66,8 +65,9 @@ const FMCard = () => {
)
useEffect(() => {
if (coverUrl) {
average(coverUrl, { amount: 1, format: 'hex', sample: 1 }).then(color => {
const cover = resizeImage(playerSnapshot.fmTrack?.al?.picUrl ?? '', 'xs')
if (cover) {
average(cover, { amount: 1, format: 'hex', sample: 1 }).then(color => {
let c = colord(color as string)
if (c.isLight()) c = c.darken(0.15)
else if (c.isDark()) c = c.lighten(0.1)
@ -75,7 +75,7 @@ const FMCard = () => {
setBackground(`linear-gradient(to bottom right, ${c.toHex()}, ${to})`)
})
}
}, [coverUrl])
}, [playerSnapshot.fmTrack?.al?.picUrl])
return (
<div

View File

@ -67,7 +67,7 @@ const PlayingTrack = () => {
<IconButton onClick={() => toast('施工中...')}>
<SvgIcon
className='h-6 w-6 text-black dark:text-white'
className='h-5 w-5 text-black dark:text-white'
name={
track?.id && userLikedSongs?.ids?.includes(track.id)
? 'heart'

View File

@ -6,12 +6,17 @@ import Login from '@/pages/Login'
import Playlist from '@/pages/Playlist'
import Artist from '@/pages/Artist'
import Search from '@/pages/Search'
import Library from '@/pages/Library'
const routes: RouteObject[] = [
{
path: '/',
element: <Home />,
},
{
path: '/library',
element: <Library />,
},
{
path: '/login',
element: <Login />,

View File

@ -0,0 +1,22 @@
import SvgIcon from './SvgIcon'
const TitleBar = () => {
return (
<div className='flex h-8 w-screen items-center justify-between bg-gray-50'>
<div className='ml-2 text-sm text-gray-500'>YesPlayMusic</div>
<div className='flex h-full'>
<button className='flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'>
<SvgIcon className='h-3 w-3' name='windows-min' />
</button>
<button className='flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'>
<SvgIcon className='h-3 w-3' name='windows-max' />
</button>
<button className='flex w-[2.875rem] items-center justify-center hover:bg-[#c42b1c] hover:text-white'>
<SvgIcon className='h-3 w-3' name='windows-close' />
</button>
</div>
</div>
)
}
export default TitleBar

View File

@ -4,7 +4,12 @@ declare global {
interface Window {
// Expose some Api through preload script
ipcRenderer?: import('electron').IpcRenderer
isElectron?: boolean
env?: {
isElectron: boolean
isLinux: boolean
isMac: boolean
isWin: boolean
}
}
}

View File

@ -0,0 +1,40 @@
import { TrackApiNames, fetchLyric } from '@/api/track'
import type { FetchLyricParams, FetchLyricResponse } from '@/api/track'
import reactQueryClient from '@/utils/reactQueryClient'
export default function useLyric(params: FetchLyricParams) {
return useQuery(
[TrackApiNames.FETCH_LYRIC, params],
() => {
return fetchLyric(params)
},
{
enabled: !!params.id && params.id !== 0,
refetchInterval: false,
staleTime: Infinity,
initialData: (): FetchLyricResponse | undefined =>
window.ipcRenderer?.sendSync('getApiCacheSync', {
api: 'lyric',
query: {
id: params.id,
},
}),
}
)
}
export function fetchTracksWithReactQuery(params: FetchLyricParams) {
return reactQueryClient.fetchQuery(
[TrackApiNames.FETCH_LYRIC, params],
() => {
return fetchLyric(params)
},
{
retry: 4,
retryDelay: (retryCount: number) => {
return retryCount * 500
},
staleTime: Infinity,
}
)
}

View File

@ -40,9 +40,11 @@ export default function Home() {
)
const playlists = [
...(dailyRecommendPlaylists?.recommend ?? []),
...(dailyRecommendPlaylists?.recommend?.slice(1) ?? []),
...(recommendedPlaylists?.result ?? []),
].slice(0, 10)
]
.slice(0, 10)
.reverse()
return (
<div>

View File

@ -0,0 +1,155 @@
import SvgIcon from '@/components/SvgIcon'
import useLyric from '@/hooks/useLyric'
import usePlaylist from '@/hooks/usePlaylist'
import useUser from '@/hooks/useUser'
import useUserPlaylists from '@/hooks/useUserPlaylists'
import { player } from '@/store'
import { resizeImage } from '@/utils/common'
import { sample, chunk } from 'lodash-es'
const LikedTracksCard = ({ className }: { className?: string }) => {
const navigate = useNavigate()
const { data: user } = useUser()
const { data: playlists } = useUserPlaylists({
uid: user?.account?.id ?? 0,
offset: 0,
})
const { data: likedSongsPlaylist } = usePlaylist({
id: playlists?.playlist?.[0].id ?? 0,
})
// Lyric
const [trackID, setTrackID] = useState(0)
useEffect(() => {
if (trackID === 0) {
setTrackID(
sample(likedSongsPlaylist?.playlist.trackIds?.map(t => t.id) ?? []) ?? 0
)
}
}, [likedSongsPlaylist?.playlist.trackIds, trackID])
const { data: lyric } = useLyric({
id: trackID,
})
const lyricLines = useMemo(() => {
return (
sample(
chunk(
lyric?.lrc.lyric
?.split('\n')
?.map(l => l.split(']')[1]?.trim())
?.filter(
l =>
l &&
!l.includes('作词') &&
!l.includes('作曲') &&
!l.includes('纯音乐,请欣赏')
),
3
)
) ?? []
)
}, [lyric])
const handlePlay = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
if (!likedSongsPlaylist?.playlist.id) {
toast('无法播放歌单')
return
}
player.playPlaylist(likedSongsPlaylist.playlist.id)
},
[likedSongsPlaylist?.playlist.id]
)
return (
<div
onClick={() =>
likedSongsPlaylist?.playlist.id &&
navigate(`/playlist/${likedSongsPlaylist.playlist.id}`)
}
className={classNames(
'relative flex h-full w-full flex-col justify-between rounded-2xl bg-brand-50 py-5 px-6 text-brand-600 ',
className
)}
>
<div className='text-sm'>
{lyricLines.map((line, index) => (
<div key={`${index}-${line}`}>{line}</div>
))}
</div>
<div>
<div className='text-2xl font-bold'></div>
<div className='mt-0.5 text-[15px]'>
{likedSongsPlaylist?.playlist.trackCount ?? 0}
</div>
</div>
<button
onClick={handlePlay}
className='btn-pressed-animation absolute bottom-6 right-6 grid h-11 w-11 cursor-default place-content-center rounded-full bg-brand-600 text-brand-50 shadow-lg'
>
<SvgIcon name='play-fill' className='ml-0.5 h-6 w-6' />
</button>
</div>
)
}
const OtherCard = ({
name,
icon,
className,
}: {
name: string
icon: string
className?: string
}) => {
return (
<div
className={classNames(
'flex h-full w-full flex-col justify-between rounded-2xl bg-gray-100 text-lg font-bold',
className
)}
>
<SvgIcon name={icon} className='ml-3 mt-3 h-12 w-12' />
<span className='m-4'>{name}</span>
</div>
)
}
const Tabs = () => {
return <div></div>
}
const Library = () => {
const { data: user } = useUser()
const avatarUrl = useMemo(
() => resizeImage(user?.profile?.avatarUrl ?? '', 'sm'),
[user?.profile?.avatarUrl]
)
return (
<div className='mt-8'>
<div className='flex items-center text-[2.625rem] font-semibold'>
<img src={avatarUrl} className='mr-3 mt-1 h-12 w-12 rounded-full' />
{user?.profile?.nickname}
</div>
<div className='mt-8 grid grid-cols-[2fr_1fr_1fr] grid-rows-2 gap-4'>
<LikedTracksCard className='row-span-2' />
<OtherCard name='云盘' icon='fm' className='' />
<OtherCard name='本地音乐' icon='music-note' className='' />
<OtherCard name='最近播放' icon='playlist' className='' />
<OtherCard name='听歌排行' icon='music-library' className='' />
</div>
</div>
)
}
export default Library

View File

@ -244,7 +244,10 @@ export class Player {
const prefetchNextTrack = async () => {
const prefetchTrackID = this.fmTrackList[1]
const track = await this._fetchTrack(prefetchTrackID)
if (track?.al.picUrl) axios.get(resizeImage(track.al.picUrl, 'md'))
if (track?.al.picUrl) {
axios.get(resizeImage(track.al.picUrl, 'md'))
axios.get(resizeImage(track.al.picUrl, 'xs'))
}
}
if (this.fmTrackList.length === 0) await loadMoreTracks()