mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
feat: updates
This commit is contained in:
parent
3b9d728410
commit
46349e8314
10 changed files with 117 additions and 39 deletions
|
|
@ -80,7 +80,7 @@ export function getCache(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'user/playlist': {
|
case 'user/playlist': {
|
||||||
if (!query.uid) return
|
if (isNaN(Number(query.uid))) return
|
||||||
const userPlaylists = db.get(
|
const userPlaylists = db.get(
|
||||||
ModelNames.USER_PLAYLISTS,
|
ModelNames.USER_PLAYLISTS,
|
||||||
Number(query?.uid)
|
Number(query?.uid)
|
||||||
|
|
@ -90,7 +90,16 @@ export function getCache(
|
||||||
}
|
}
|
||||||
case 'song/detail': {
|
case 'song/detail': {
|
||||||
const ids: string[] = query?.ids.split(',')
|
const ids: string[] = query?.ids.split(',')
|
||||||
|
if (ids.length === 0) return
|
||||||
|
|
||||||
|
let isIDsValid = true
|
||||||
|
ids.forEach(id => {
|
||||||
|
if (id === '' || isNaN(Number(id))) isIDsValid = false
|
||||||
|
})
|
||||||
|
if (!isIDsValid) return
|
||||||
|
|
||||||
const idsQuery = ids.map(id => `id = ${id}`).join(' OR ')
|
const idsQuery = ids.map(id => `id = ${id}`).join(' OR ')
|
||||||
|
console.log(idsQuery)
|
||||||
const tracksRaw = realm
|
const tracksRaw = realm
|
||||||
.objects(ModelNames.TRACK)
|
.objects(ModelNames.TRACK)
|
||||||
.filtered(`(${idsQuery})`)
|
.filtered(`(${idsQuery})`)
|
||||||
|
|
@ -109,28 +118,28 @@ export function getCache(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'album': {
|
case 'album': {
|
||||||
if (!query?.id) return
|
if (isNaN(Number(query?.id))) return
|
||||||
const album = db.get(ModelNames.ALBUM, Number(query?.id)) as any
|
const album = db.get(ModelNames.ALBUM, Number(query?.id)) as any
|
||||||
if (checkIsExpired && isCacheExpired(album?.updateAt, 24 * 60)) return
|
if (checkIsExpired && isCacheExpired(album?.updateAt, 24 * 60)) return
|
||||||
if (album?.json) return JSON.parse(album.json)
|
if (album?.json) return JSON.parse(album.json)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'playlist/detail': {
|
case 'playlist/detail': {
|
||||||
if (!query?.id) return
|
if (isNaN(Number(query?.id))) return
|
||||||
const playlist = db.get(ModelNames.PLAYLIST, Number(query?.id)) as any
|
const playlist = db.get(ModelNames.PLAYLIST, Number(query?.id)) as any
|
||||||
if (checkIsExpired && isCacheExpired(playlist?.updateAt, 10)) return
|
if (checkIsExpired && isCacheExpired(playlist?.updateAt, 10)) return
|
||||||
if (playlist?.json) return JSON.parse(playlist.json)
|
if (playlist?.json) return JSON.parse(playlist.json)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'artists': {
|
case 'artists': {
|
||||||
if (!query?.id) return
|
if (isNaN(Number(query?.id))) return
|
||||||
const artist = db.get(ModelNames.ARTIST, Number(query?.id)) as any
|
const artist = db.get(ModelNames.ARTIST, Number(query?.id)) as any
|
||||||
if (checkIsExpired && isCacheExpired(artist?.updateAt, 30)) return
|
if (checkIsExpired && isCacheExpired(artist?.updateAt, 30)) return
|
||||||
if (artist?.json) return JSON.parse(artist.json)
|
if (artist?.json) return JSON.parse(artist.json)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'artist/album': {
|
case 'artist/album': {
|
||||||
if (!query?.id) return
|
if (isNaN(Number(query?.id))) return
|
||||||
const artistAlbums = db.get(
|
const artistAlbums = db.get(
|
||||||
ModelNames.ARTIST_ALBUMS,
|
ModelNames.ARTIST_ALBUMS,
|
||||||
Number(query?.id)
|
Number(query?.id)
|
||||||
|
|
|
||||||
3
packages/renderer/src/assets/icons/explicit.svg
Normal file
3
packages/renderer/src/assets/icons/explicit.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2C3.34315 2 2 3.34315 2 5V19C2 20.6569 3.34315 22 5 22H19C20.6569 22 22 20.6569 22 19V5C22 3.34315 20.6569 2 19 2H5ZM9 6C8.44772 6 8 6.44772 8 7V12V17C8 17.5523 8.44772 18 9 18H15C15.5523 18 16 17.5523 16 17C16 16.4477 15.5523 16 15 16H10V13H15C15.5523 13 16 12.5523 16 12C16 11.4477 15.5523 11 15 11H10V8H15C15.5523 8 16 7.55228 16 7C16 6.44772 15.5523 6 15 6H9Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 542 B |
|
|
@ -170,6 +170,16 @@ const CoverRow = ({
|
||||||
className='mr-1 mb-1 inline-block h-3 w-3 text-gray-300'
|
className='mr-1 mb-1 inline-block h-3 w-3 text-gray-300'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Explicit icon */}
|
||||||
|
{(item as Album)?.mark === 1056768 && (
|
||||||
|
<SvgIcon
|
||||||
|
name='explicit'
|
||||||
|
className='float-right mt-[2px] h-4 w-4 text-gray-300'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Name */}
|
||||||
<span
|
<span
|
||||||
onClick={() => goTo(item.id)}
|
onClick={() => goTo(item.id)}
|
||||||
className='decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200'
|
className='decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200'
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@ interface PrimaryTab extends Tab {
|
||||||
|
|
||||||
const primaryTabs: PrimaryTab[] = [
|
const primaryTabs: PrimaryTab[] = [
|
||||||
{
|
{
|
||||||
name: 'Home',
|
name: '主页',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
route: '/',
|
route: '/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Podcast',
|
name: '播客',
|
||||||
icon: 'podcast',
|
icon: 'podcast',
|
||||||
route: '/podcast',
|
route: '/podcast',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Library',
|
name: '音乐库',
|
||||||
icon: 'music-library',
|
icon: 'music-library',
|
||||||
route: '/library',
|
route: '/library',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,15 @@ const Track = memo(
|
||||||
isHighlight ? 'text-brand-500' : 'text-black dark:text-white'
|
isHighlight ? 'text-brand-500' : 'text-black dark:text-white'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{track.name}</span>
|
<span className='flex items-center'>
|
||||||
|
{track.name}
|
||||||
|
{track.mark === 1318912 && (
|
||||||
|
<SvgIcon
|
||||||
|
name='explicit'
|
||||||
|
className='ml-1.5 mt-[2px] h-4 w-4 text-gray-300 dark:text-gray-500'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<span
|
<span
|
||||||
title={subtitle}
|
title={subtitle}
|
||||||
|
|
@ -227,10 +235,10 @@ const TracksAlbum = ({
|
||||||
<div className='mx-4 mt-10 mb-2 grid grid-cols-12 border-b border-gray-100 py-2.5 text-sm text-gray-400 dark:border-gray-800 dark:text-gray-500'>
|
<div className='mx-4 mt-10 mb-2 grid grid-cols-12 border-b border-gray-100 py-2.5 text-sm text-gray-400 dark:border-gray-800 dark:text-gray-500'>
|
||||||
<div className='col-span-6 grid grid-cols-[2rem_auto]'>
|
<div className='col-span-6 grid grid-cols-[2rem_auto]'>
|
||||||
<div>#</div>
|
<div>#</div>
|
||||||
<div>TITLE</div>
|
<div>标题</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='col-span-4'>ARTIST</div>
|
<div className='col-span-4'>艺人</div>
|
||||||
<div className='col-span-2 justify-self-end'>TIME</div>
|
<div className='col-span-2 justify-self-end'>时长</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tracks */}
|
{/* Tracks */}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import ArtistInline from '@/components/ArtistsInline'
|
import ArtistInline from '@/components/ArtistsInline'
|
||||||
import Skeleton from '@/components/Skeleton'
|
import Skeleton from '@/components/Skeleton'
|
||||||
import { resizeImage } from '@/utils/common'
|
import { resizeImage } from '@/utils/common'
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
import SvgIcon from './SvgIcon'
|
||||||
|
|
||||||
const Track = ({
|
const Track = ({
|
||||||
track,
|
track,
|
||||||
|
|
@ -52,11 +54,18 @@ const Track = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='text-xs text-gray-500 dark:text-gray-400'>
|
<div className='text-xs text-gray-500 dark:text-gray-400'>
|
||||||
{!isSkeleton && (
|
{isSkeleton ? (
|
||||||
<ArtistInline artists={track.ar} disableLink={true} />
|
|
||||||
)}
|
|
||||||
{isSkeleton && (
|
|
||||||
<Skeleton className='w-2/3 translate-y-px'>PLACE</Skeleton>
|
<Skeleton className='w-2/3 translate-y-px'>PLACE</Skeleton>
|
||||||
|
) : (
|
||||||
|
<span className='flex items-center'>
|
||||||
|
{track.mark === 1318912 && (
|
||||||
|
<SvgIcon
|
||||||
|
name='explicit'
|
||||||
|
className='mr-1 h-3 w-3 text-gray-300 dark:text-gray-500'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ArtistInline artists={track.ar} disableLink={true} />
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,15 @@ const Track = memo(
|
||||||
{isSkeleton ? (
|
{isSkeleton ? (
|
||||||
<Skeleton className='w-2/3 translate-y-px'>PLACE</Skeleton>
|
<Skeleton className='w-2/3 translate-y-px'>PLACE</Skeleton>
|
||||||
) : (
|
) : (
|
||||||
<ArtistInline artists={track.ar} />
|
<span className='inline-flex items-center'>
|
||||||
|
{track.mark === 1318912 && (
|
||||||
|
<SvgIcon
|
||||||
|
name='explicit'
|
||||||
|
className='mr-1 h-3.5 w-3.5 text-gray-300 dark:text-gray-500'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ArtistInline artists={track.ar} />
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -191,10 +199,10 @@ const TracksList = memo(
|
||||||
<div className='ml-2 mr-4 mt-10 mb-2 grid grid-cols-12 border-b border-gray-100 py-2.5 text-sm text-gray-400 dark:border-gray-800 dark:text-gray-500'>
|
<div className='ml-2 mr-4 mt-10 mb-2 grid grid-cols-12 border-b border-gray-100 py-2.5 text-sm text-gray-400 dark:border-gray-800 dark:text-gray-500'>
|
||||||
<div className='col-span-6 grid grid-cols-[4.2rem_auto]'>
|
<div className='col-span-6 grid grid-cols-[4.2rem_auto]'>
|
||||||
<div></div>
|
<div></div>
|
||||||
<div>TITLE</div>
|
<div>标题</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='col-span-4'>ALBUM</div>
|
<div className='col-span-4'>专辑</div>
|
||||||
<div className='col-span-2 justify-self-end'>TIME</div>
|
<div className='col-span-2 justify-self-end'>时长</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grid w-full'>
|
<div className='grid w-full'>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
resizeImage,
|
resizeImage,
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
} from '@/utils/common'
|
} from '@/utils/common'
|
||||||
|
import useTracks from '@/hooks/useTracks'
|
||||||
|
|
||||||
const PlayButton = ({
|
const PlayButton = ({
|
||||||
album,
|
album,
|
||||||
|
|
@ -95,7 +96,7 @@ const Header = ({
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
<div className='absolute top-0 h-full w-full bg-gradient-to-b from-white/[.84] to-white dark:from-black/[.5] dark:to-[#1d1d1d]'></div>
|
<div className='absolute top-0 h-full w-full bg-gradient-to-b from-white/75 to-white dark:from-black/50 dark:to-[#1d1d1d]'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grid grid-cols-[17rem_auto] items-center gap-9'>
|
<div className='grid grid-cols-[17rem_auto] items-center gap-9'>
|
||||||
|
|
@ -285,6 +286,10 @@ const Album = () => {
|
||||||
id: Number(params.id) || 0,
|
id: Number(params.id) || 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { data: tracks } = useTracks({
|
||||||
|
ids: album?.songs?.map(track => track.id) ?? [],
|
||||||
|
})
|
||||||
|
|
||||||
const handlePlay = async (trackID: number | null = null) => {
|
const handlePlay = async (trackID: number | null = null) => {
|
||||||
const realAlbum = album?.album
|
const realAlbum = album?.album
|
||||||
if (!realAlbum) {
|
if (!realAlbum) {
|
||||||
|
|
@ -302,7 +307,7 @@ const Album = () => {
|
||||||
handlePlay={handlePlay}
|
handlePlay={handlePlay}
|
||||||
/>
|
/>
|
||||||
<TracksAlbum
|
<TracksAlbum
|
||||||
tracks={album?.album.songs ?? []}
|
tracks={tracks?.songs ?? album?.album.songs ?? []}
|
||||||
onTrackDoubleClick={handlePlay}
|
onTrackDoubleClick={handlePlay}
|
||||||
isSkeleton={isLoading}
|
isSkeleton={isLoading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import TracksGrid from '@/components/TracksGrid'
|
||||||
import CoverRow, { Subtitle } from '@/components/CoverRow'
|
import CoverRow, { Subtitle } from '@/components/CoverRow'
|
||||||
import Skeleton from '@/components/Skeleton'
|
import Skeleton from '@/components/Skeleton'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
import useTracks from '@/hooks/useTracks'
|
||||||
|
|
||||||
const Artist = () => {
|
const Artist = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
@ -22,25 +23,48 @@ const Artist = () => {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { data: tracks, isLoading: isLoadingTracks } = useTracks({
|
||||||
|
ids: artist?.hotSongs?.slice(0, 10)?.map(t => t.id) ?? [],
|
||||||
|
})
|
||||||
|
|
||||||
const albums = useMemo(() => {
|
const albums = useMemo(() => {
|
||||||
if (!albumsRaw?.hotAlbums) return []
|
if (!albumsRaw?.hotAlbums) return []
|
||||||
return albumsRaw.hotAlbums.filter(
|
const albums: Album[] = []
|
||||||
album =>
|
albumsRaw.hotAlbums.forEach(album => {
|
||||||
album.type === '专辑' &&
|
if (album.type !== '专辑') return false
|
||||||
['混音版', '精选集', 'Remix'].includes(album.subType) === false &&
|
if (['混音版', '精选集', 'Remix'].includes(album.subType)) return false
|
||||||
album.size > 1
|
|
||||||
)
|
// No singles
|
||||||
|
if (album.size <= 1) return false
|
||||||
|
|
||||||
|
// No remixes
|
||||||
|
if (
|
||||||
|
/(\(|\[)(.*)(Remix|remix)(.*)(\)|\])/.test(
|
||||||
|
album.name.toLocaleLowerCase()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If have same name album only keep the Explicit version
|
||||||
|
const sameNameAlbumIndex = albums.findIndex(a => a.name === album.name)
|
||||||
|
if (sameNameAlbumIndex !== -1) {
|
||||||
|
if (album.mark === 1056768) albums[sameNameAlbumIndex] = album
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
albums.push(album)
|
||||||
|
})
|
||||||
|
return albums
|
||||||
}, [albumsRaw?.hotAlbums])
|
}, [albumsRaw?.hotAlbums])
|
||||||
|
|
||||||
const singles = useMemo(() => {
|
const singles = useMemo(() => {
|
||||||
if (!albumsRaw?.hotAlbums) return []
|
if (!albumsRaw?.hotAlbums) return []
|
||||||
|
const albumsIds = albums.map(album => album.id)
|
||||||
return albumsRaw.hotAlbums.filter(
|
return albumsRaw.hotAlbums.filter(
|
||||||
album =>
|
album => albumsIds.includes(album.id) === false
|
||||||
album.type !== '专辑' ||
|
|
||||||
['混音版', '精选集', 'Remix'].includes(album.subType) ||
|
|
||||||
album.size === 1
|
|
||||||
)
|
)
|
||||||
}, [albumsRaw?.hotAlbums])
|
}, [albums, albumsRaw?.hotAlbums])
|
||||||
|
|
||||||
const latestAlbum = useMemo(() => {
|
const latestAlbum = useMemo(() => {
|
||||||
if (!albumsRaw || !albumsRaw.hotAlbums) return
|
if (!albumsRaw || !albumsRaw.hotAlbums) return
|
||||||
|
|
@ -89,7 +113,7 @@ const Artist = () => {
|
||||||
<div className='mt-12 grid h-[20rem] grid-cols-[14rem,_auto] grid-rows-1 gap-16 px-2'>
|
<div className='mt-12 grid h-[20rem] grid-cols-[14rem,_auto] grid-rows-1 gap-16 px-2'>
|
||||||
{/* Latest release */}
|
{/* Latest release */}
|
||||||
<div>
|
<div>
|
||||||
<div className='mb-6 text-2xl font-semibold dark:text-white'>
|
<div className='mb-6 text-2xl font-semibold text-gray-800 dark:text-white'>
|
||||||
最新发行
|
最新发行
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-grow rounded-xl '>
|
<div className='flex-grow rounded-xl '>
|
||||||
|
|
@ -110,12 +134,12 @@ const Artist = () => {
|
||||||
|
|
||||||
{/* Popular tracks */}
|
{/* Popular tracks */}
|
||||||
<div>
|
<div>
|
||||||
<div className='mb-6 text-2xl font-semibold dark:text-white'>
|
<div className='mb-6 text-2xl font-semibold text-gray-800 dark:text-white'>
|
||||||
热门歌曲
|
热门歌曲
|
||||||
</div>
|
</div>
|
||||||
<div className='rounded-xl'>
|
<div className='rounded-xl'>
|
||||||
<TracksGrid
|
<TracksGrid
|
||||||
tracks={artist?.hotSongs.slice(0, 10) ?? []}
|
tracks={tracks?.songs ?? artist?.hotSongs.slice(0, 10) ?? []}
|
||||||
isSkeleton={isLoading}
|
isSkeleton={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -125,7 +149,7 @@ const Artist = () => {
|
||||||
{/* Albums */}
|
{/* Albums */}
|
||||||
{albums.length !== 0 && (
|
{albums.length !== 0 && (
|
||||||
<div className='mt-20 px-2'>
|
<div className='mt-20 px-2'>
|
||||||
<div className='mb-6 text-2xl font-semibold dark:text-white'>
|
<div className='mb-6 text-2xl font-semibold text-gray-800 dark:text-white'>
|
||||||
专辑
|
专辑
|
||||||
</div>
|
</div>
|
||||||
<CoverRow
|
<CoverRow
|
||||||
|
|
@ -137,7 +161,7 @@ const Artist = () => {
|
||||||
|
|
||||||
{/* Singles/EP */}
|
{/* Singles/EP */}
|
||||||
<div className='mt-16 px-2'>
|
<div className='mt-16 px-2'>
|
||||||
<div className='mb-6 text-2xl font-semibold dark:text-white'>
|
<div className='mb-6 text-2xl font-semibold text-gray-800 dark:text-white'>
|
||||||
单曲和EP
|
单曲和EP
|
||||||
</div>
|
</div>
|
||||||
<CoverRow
|
<CoverRow
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,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))
|
|
||||||
|
const id = this.trackID
|
||||||
|
_howler.once('load', () => this._cacheAudio(id, audio))
|
||||||
|
|
||||||
if (!this._progressInterval) {
|
if (!this._progressInterval) {
|
||||||
this._setupProgressInterval()
|
this._setupProgressInterval()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue