mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 21:28:06 +00:00
feat: updates
This commit is contained in:
parent
719a3a60d4
commit
86f088e5c9
9 changed files with 87 additions and 69 deletions
|
|
@ -14,7 +14,8 @@
|
||||||
"dev:renderer": "vite dev",
|
"dev:renderer": "vite dev",
|
||||||
"build:main": "node scripts/build.main.mjs",
|
"build:main": "node scripts/build.main.mjs",
|
||||||
"build:renderer": "vite build",
|
"build:renderer": "vite build",
|
||||||
"build": "npm run typecheck && npm run build:renderer && npm run build:main && electron-builder --config .electron-builder.config.js --dir",
|
"build": "npm run typecheck && cross-env-shell IS_ELECTRON=true npm run build:renderer && npm run build:main && electron-builder --config .electron-builder.config.js",
|
||||||
|
"build-dir": "npm run typecheck && cross-env-shell IS_ELECTRON=true npm run build:renderer && npm run build:main && electron-builder --config .electron-builder.config.js --dir",
|
||||||
"typecheck": "tsc --noEmit --project src/renderer/tsconfig.json",
|
"typecheck": "tsc --noEmit --project src/renderer/tsconfig.json",
|
||||||
"debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./src/renderer\"",
|
"debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./src/renderer\"",
|
||||||
"eslint": "eslint --ext .ts,.js ./",
|
"eslint": "eslint --ext .ts,.js ./",
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const PlayingTrack = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IconButton onClick={() => toast('Work in progress')}>
|
<IconButton onClick={() => toast('施工中...')}>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
className='h-6 w-6 text-black dark:text-white'
|
className='h-6 w-6 text-black dark:text-white'
|
||||||
name='heart-outline'
|
name='heart-outline'
|
||||||
|
|
@ -115,23 +115,30 @@ const Others = () => {
|
||||||
const playerSnapshot = useSnapshot(player)
|
const playerSnapshot = useSnapshot(player)
|
||||||
const mode = useMemo(() => playerSnapshot.mode, [playerSnapshot.mode])
|
const mode = useMemo(() => playerSnapshot.mode, [playerSnapshot.mode])
|
||||||
|
|
||||||
const isFM = () => mode === PlayerMode.FM
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-end gap-2 pr-2 text-black dark:text-white'>
|
<div className='flex items-center justify-end gap-2 pr-2 text-black dark:text-white'>
|
||||||
<IconButton onClick={() => toast('Work in progress')} disabled={isFM()}>
|
<IconButton
|
||||||
|
onClick={() => toast('Work in progress')}
|
||||||
|
disabled={mode === PlayerMode.FM}
|
||||||
|
>
|
||||||
<SvgIcon className='h-6 w-6' name='playlist' />
|
<SvgIcon className='h-6 w-6' name='playlist' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={() => toast('Work in progress')} disabled={isFM()}>
|
<IconButton
|
||||||
|
onClick={() => toast('施工中...')}
|
||||||
|
disabled={mode === PlayerMode.FM}
|
||||||
|
>
|
||||||
<SvgIcon className='h-6 w-6' name='repeat' />
|
<SvgIcon className='h-6 w-6' name='repeat' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={() => toast('Work in progress')} disabled={isFM()}>
|
<IconButton
|
||||||
|
onClick={() => toast('施工中...')}
|
||||||
|
disabled={mode === PlayerMode.FM}
|
||||||
|
>
|
||||||
<SvgIcon className='h-6 w-6' name='shuffle' />
|
<SvgIcon className='h-6 w-6' name='shuffle' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={() => toast('Work in progress')}>
|
<IconButton onClick={() => toast('施工中...')}>
|
||||||
<SvgIcon className='h-6 w-6' name='volume' />
|
<SvgIcon className='h-6 w-6' name='volume' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={() => toast('Work in progress')}>
|
<IconButton onClick={() => toast('施工中...')}>
|
||||||
<SvgIcon className='h-6 w-6' name='lyrics' />
|
<SvgIcon className='h-6 w-6' name='lyrics' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,10 @@ const SearchBox = () => {
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
return (
|
return (
|
||||||
<div className='app-region-no-drag btn-hover-animation rounded-lg p-2.5 text-gray-500 transition duration-300 after:rounded-full after:bg-black/[.06] hover:text-gray-900 dark:text-gray-300 dark:after:bg-white/10 dark:hover:text-gray-200'>
|
<div
|
||||||
|
onClick={() => toast('施工中...')}
|
||||||
|
className='app-region-no-drag btn-hover-animation rounded-lg p-2.5 text-gray-500 transition duration-300 after:rounded-full after:bg-black/[.06] hover:text-gray-900 dark:text-gray-300 dark:after:bg-white/10 dark:hover:text-gray-200'
|
||||||
|
>
|
||||||
<SvgIcon className='h-[1.125rem] w-[1.125rem]' name='settings' />
|
<SvgIcon className='h-[1.125rem] w-[1.125rem]' name='settings' />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -78,20 +81,27 @@ const Avatar = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { data: user } = useUser()
|
const { data: user } = useUser()
|
||||||
|
|
||||||
const avatarUrl = resizeImage(user?.profile?.avatarUrl ?? '', 'sm')
|
const avatarUrl = user?.profile?.avatarUrl
|
||||||
|
? resizeImage(user?.profile?.avatarUrl ?? '', 'sm')
|
||||||
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{avatarUrl ? (
|
||||||
<img
|
<img
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
onClick={() => navigate('/login')}
|
onClick={() => navigate('/login')}
|
||||||
className='app-region-no-drag h-9 w-9 rounded-full bg-gray-100 dark:bg-gray-700'
|
className='app-region-no-drag h-9 w-9 rounded-full bg-gray-100 dark:bg-gray-700'
|
||||||
/>
|
/>
|
||||||
// <div onClick={() => navigate('/login')}>
|
) : (
|
||||||
// <SvgIcon
|
<div onClick={() => navigate('/login')}>
|
||||||
// name='user'
|
<SvgIcon
|
||||||
// className='h-9 w-9 rounded-full bg-gray-400/10 p-1 text-gray-400'
|
name='user'
|
||||||
// />
|
className='h-9 w-9 rounded-full bg-black/[.06] p-1 text-gray-500'
|
||||||
// </div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ const Header = ({
|
||||||
color={ButtonColor.Gray}
|
color={ButtonColor.Gray}
|
||||||
iconColor={ButtonColor.Gray}
|
iconColor={ButtonColor.Gray}
|
||||||
isSkelton={isLoading}
|
isSkelton={isLoading}
|
||||||
onClick={() => toast('Work in progress')}
|
onClick={() => toast('施工中...')}
|
||||||
>
|
>
|
||||||
<SvgIcon name='heart-outline' className='h-6 w-6' />
|
<SvgIcon name='heart-outline' className='h-6 w-6' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -200,7 +200,7 @@ const Header = ({
|
||||||
color={ButtonColor.Gray}
|
color={ButtonColor.Gray}
|
||||||
iconColor={ButtonColor.Gray}
|
iconColor={ButtonColor.Gray}
|
||||||
isSkelton={isLoading}
|
isSkelton={isLoading}
|
||||||
onClick={() => toast('Work in progress')}
|
onClick={() => toast('施工中...')}
|
||||||
>
|
>
|
||||||
<SvgIcon name='more' className='h-6 w-6' />
|
<SvgIcon name='more' className='h-6 w-6' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@ import DailyTracksCard from '@/components/DailyTracksCard'
|
||||||
import FMCard from '@/components/FMCard'
|
import FMCard from '@/components/FMCard'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const {
|
||||||
|
data: dailyRecommendPlaylists,
|
||||||
|
isLoading: isLoadingDailyRecommendPlaylists,
|
||||||
|
} = useQuery(
|
||||||
|
PlaylistApiNames.FETCH_DAILY_RECOMMEND_PLAYLISTS,
|
||||||
|
fetchDailyRecommendPlaylists,
|
||||||
|
{
|
||||||
|
retry: false,
|
||||||
|
placeholderData: () =>
|
||||||
|
window.ipcRenderer?.sendSync('getApiCacheSync', {
|
||||||
|
api: 'recommend/resource',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: recommendedPlaylists,
|
data: recommendedPlaylists,
|
||||||
isLoading: isLoadingRecommendedPlaylists,
|
isLoading: isLoadingRecommendedPlaylists,
|
||||||
|
|
@ -24,20 +39,6 @@ export default function Home() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
|
||||||
data: dailyRecommendPlaylists,
|
|
||||||
isLoading: isLoadingDailyRecommendPlaylists,
|
|
||||||
} = useQuery(
|
|
||||||
PlaylistApiNames.FETCH_DAILY_RECOMMEND_PLAYLISTS,
|
|
||||||
fetchDailyRecommendPlaylists,
|
|
||||||
{
|
|
||||||
placeholderData: () =>
|
|
||||||
window.ipcRenderer?.sendSync('getApiCacheSync', {
|
|
||||||
api: 'recommend/resource',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const playlists = [
|
const playlists = [
|
||||||
...(dailyRecommendPlaylists?.recommend ?? []),
|
...(dailyRecommendPlaylists?.recommend ?? []),
|
||||||
...(recommendedPlaylists?.result ?? []),
|
...(recommendedPlaylists?.result ?? []),
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const EmailInput = ({
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
Email
|
邮箱
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
value={email}
|
value={email}
|
||||||
|
|
@ -54,7 +54,7 @@ const PhoneInput = ({
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
Phone
|
手机
|
||||||
</div>
|
</div>
|
||||||
<div className='flex w-full'>
|
<div className='flex w-full'>
|
||||||
<input
|
<input
|
||||||
|
|
@ -91,7 +91,7 @@ const PasswordInput = ({
|
||||||
return (
|
return (
|
||||||
<div className='mt-3 flex w-full flex-col'>
|
<div className='mt-3 flex w-full flex-col'>
|
||||||
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
<div className='mb-1 text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
Password
|
密码
|
||||||
</div>
|
</div>
|
||||||
<div className='flex w-full'>
|
<div className='flex w-full'>
|
||||||
<input
|
<input
|
||||||
|
|
@ -135,7 +135,7 @@ const LoginButton = ({
|
||||||
'bg-brand-100 text-brand-300 dark:bg-brand-700 dark:text-white/50'
|
'bg-brand-100 text-brand-300 dark:bg-brand-700 dark:text-white/50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Login
|
登录
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -153,15 +153,15 @@ const OtherLoginMethods = ({
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
id: Method.QRCODE,
|
id: Method.QRCODE,
|
||||||
name: 'QR Code',
|
name: '二维码',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: Method.EMAIL,
|
id: Method.EMAIL,
|
||||||
name: 'Email',
|
name: '邮箱',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: Method.PHONE,
|
id: Method.PHONE,
|
||||||
name: 'Phone',
|
name: '手机',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
|
|
@ -178,7 +178,7 @@ const OtherLoginMethods = ({
|
||||||
<button
|
<button
|
||||||
key={id}
|
key={id}
|
||||||
onClick={() => setMethod(id)}
|
onClick={() => setMethod(id)}
|
||||||
className='flex w-full cursor-default items-center justify-center rounded-lg bg-gray-100 py-2 font-medium text-gray-600 transition duration-300 hover:bg-gray-200 hover:text-gray-800 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500 dark:hover:text-gray-100'
|
className='flex w-full cursor-default items-center justify-center rounded-lg bg-gray-100 py-2 text-gray-600 transition duration-300 hover:bg-gray-200 hover:text-gray-800 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500 dark:hover:text-gray-100'
|
||||||
>
|
>
|
||||||
<SvgIcon className='mr-2 h-5 w-5' name={id} />
|
<SvgIcon className='mr-2 h-5 w-5' name={id} />
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
|
|
@ -312,7 +312,7 @@ const LoginWithPhone = () => {
|
||||||
|
|
||||||
// Login with QRCode
|
// Login with QRCode
|
||||||
const LoginWithQRCode = () => {
|
const LoginWithQRCode = () => {
|
||||||
const [qrCodeMessage, setQrCodeMessage] = useState('扫码登录')
|
const [qrCodeMessage, setQrCodeMessage] = useState('打开网易云音乐,扫码登录')
|
||||||
const [qrCodeImage, setQrCodeImage] = useState('')
|
const [qrCodeImage, setQrCodeImage] = useState('')
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -344,7 +344,7 @@ const LoginWithQRCode = () => {
|
||||||
refetchKey()
|
refetchKey()
|
||||||
break
|
break
|
||||||
case 801:
|
case 801:
|
||||||
setQrCodeMessage('等待扫码')
|
setQrCodeMessage('打开网易云音乐,扫码登录')
|
||||||
break
|
break
|
||||||
case 802:
|
case 802:
|
||||||
setQrCodeMessage('等待确认')
|
setQrCodeMessage('等待确认')
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ const Header = memo(
|
||||||
color={ButtonColor.Gray}
|
color={ButtonColor.Gray}
|
||||||
iconColor={ButtonColor.Gray}
|
iconColor={ButtonColor.Gray}
|
||||||
isSkelton={isLoading}
|
isSkelton={isLoading}
|
||||||
onClick={() => toast('Work in progress')}
|
onClick={() => toast('施工中...')}
|
||||||
>
|
>
|
||||||
<SvgIcon name='heart-outline' className='h-6 w-6' />
|
<SvgIcon name='heart-outline' className='h-6 w-6' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -127,7 +127,7 @@ const Header = memo(
|
||||||
color={ButtonColor.Gray}
|
color={ButtonColor.Gray}
|
||||||
iconColor={ButtonColor.Gray}
|
iconColor={ButtonColor.Gray}
|
||||||
isSkelton={isLoading}
|
isSkelton={isLoading}
|
||||||
onClick={() => toast('Work in progress')}
|
onClick={() => toast('施工中...')}
|
||||||
>
|
>
|
||||||
<SvgIcon name='more' className='h-6 w-6' />
|
<SvgIcon name='more' className='h-6 w-6' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,10 @@ export class Player {
|
||||||
*/
|
*/
|
||||||
private async _fetchAudioSource(trackID: TrackID) {
|
private async _fetchAudioSource(trackID: TrackID) {
|
||||||
const response = await fetchAudioSourceWithReactQuery({ id: trackID })
|
const response = await fetchAudioSourceWithReactQuery({ id: trackID })
|
||||||
if (response.data?.[0]?.url) return response.data[0].url
|
return {
|
||||||
|
audio: response.data?.[0]?.url,
|
||||||
|
id: trackID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -174,14 +177,14 @@ export class Player {
|
||||||
* Play audio via howler
|
* Play audio via howler
|
||||||
*/
|
*/
|
||||||
private async _playAudio() {
|
private async _playAudio() {
|
||||||
const audio = await this._fetchAudioSource(this.trackID)
|
const { audio, id } = await this._fetchAudioSource(this.trackID)
|
||||||
if (!audio) {
|
if (!audio) {
|
||||||
toast('Failed to load audio source')
|
toast('Failed to load audio source')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Howler.unload()
|
Howler.unload()
|
||||||
const howler = new Howl({
|
const howler = new Howl({
|
||||||
src: [audio],
|
src: [`${audio}?id=${id}`],
|
||||||
format: ['mp3', 'flac'],
|
format: ['mp3', 'flac'],
|
||||||
html5: true,
|
html5: true,
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
|
|
@ -191,8 +194,9 @@ export class Player {
|
||||||
_howler = howler
|
_howler = howler
|
||||||
this.play()
|
this.play()
|
||||||
this.state = State.PLAYING
|
this.state = State.PLAYING
|
||||||
|
_howler.once('load', () => {
|
||||||
this._cacheAudio(this.trackID, audio)
|
this._cacheAudio(_howler._src)
|
||||||
|
})
|
||||||
|
|
||||||
if (!this._progressInterval) {
|
if (!this._progressInterval) {
|
||||||
this._setupProgressInterval()
|
this._setupProgressInterval()
|
||||||
|
|
@ -209,8 +213,10 @@ export class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _cacheAudio(id: number, audio: string) {
|
private _cacheAudio(audio: string) {
|
||||||
if (audio.includes('yesplaymusic')) return
|
if (audio.includes('yesplaymusic')) return
|
||||||
|
const id = Number(audio.split('?id=')[1])
|
||||||
|
if (isNaN(id) || !id) return
|
||||||
cacheAudio(id, audio)
|
cacheAudio(id, audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,18 +248,13 @@ export class Player {
|
||||||
play(fade: boolean = false) {
|
play(fade: boolean = false) {
|
||||||
if (_howler.playing()) return
|
if (_howler.playing()) return
|
||||||
|
|
||||||
const setPlayState = () => {
|
|
||||||
this.state = State.PLAYING
|
|
||||||
}
|
|
||||||
|
|
||||||
_howler.play()
|
_howler.play()
|
||||||
if (fade) {
|
if (fade) {
|
||||||
_howler.once('play', () => {
|
_howler.once('play', () => {
|
||||||
_howler.fade(0, this._volume, PLAY_PAUSE_FADE_DURATION)
|
_howler.fade(0, this._volume, PLAY_PAUSE_FADE_DURATION)
|
||||||
setPlayState()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setPlayState()
|
this.state = State.PLAYING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,18 +263,15 @@ export class Player {
|
||||||
* @param {boolean} fade fade out
|
* @param {boolean} fade fade out
|
||||||
*/
|
*/
|
||||||
pause(fade: boolean = false) {
|
pause(fade: boolean = false) {
|
||||||
const setPauseState = () => {
|
|
||||||
_howler.pause()
|
|
||||||
this.state = State.PAUSED
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fade) {
|
if (fade) {
|
||||||
_howler.fade(this._volume, 0, PLAY_PAUSE_FADE_DURATION)
|
_howler.fade(this._volume, 0, PLAY_PAUSE_FADE_DURATION)
|
||||||
|
this.state = State.PAUSED
|
||||||
_howler.once('fade', () => {
|
_howler.once('fade', () => {
|
||||||
setPauseState()
|
_howler.pause()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setPauseState()
|
this.state = State.PAUSED
|
||||||
|
_howler.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
base: './',
|
base: './',
|
||||||
build: {
|
build: {
|
||||||
|
target: process.env.IS_ELECTRON ? 'esnext' : 'modules',
|
||||||
sourcemap: process.env.NODE_ENV === 'debug',
|
sourcemap: process.env.NODE_ENV === 'debug',
|
||||||
outDir: '../../dist/renderer',
|
outDir: '../../dist/renderer',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue