feat: updates

This commit is contained in:
qier222 2022-05-29 17:53:27 +08:00
parent ffcc60b793
commit dd5361b8c4
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
106 changed files with 11989 additions and 4143 deletions

View file

@ -0,0 +1,56 @@
import { fetchAlbum } from '@/web/api/album'
import reactQueryClient from '@/web/utils/reactQueryClient'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import {
FetchAlbumParams,
AlbumApiNames,
FetchAlbumResponse,
} from '@/shared/api/Album'
import { useQuery } from 'react-query'
const fetch = async (params: FetchAlbumParams, noCache?: boolean) => {
const album = await fetchAlbum(params, !!noCache)
if (album?.album?.songs) {
album.album.songs = album.songs
}
return album
}
export default function useAlbum(params: FetchAlbumParams, noCache?: boolean) {
return useQuery(
[AlbumApiNames.FetchAlbum, params.id],
() => fetch(params, noCache),
{
enabled: !!params.id,
staleTime: 24 * 60 * 60 * 1000, // 24 hours
placeholderData: (): FetchAlbumResponse =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Album,
query: {
id: params.id,
},
}),
}
)
}
export function fetchAlbumWithReactQuery(params: FetchAlbumParams) {
return reactQueryClient.fetchQuery(
[AlbumApiNames.FetchAlbum, params.id],
() => fetch(params),
{
staleTime: Infinity,
}
)
}
export async function prefetchAlbum(params: FetchAlbumParams) {
await reactQueryClient.prefetchQuery(
[AlbumApiNames.FetchAlbum, params.id],
() => fetch(params),
{
staleTime: Infinity,
}
)
}

View file

@ -0,0 +1,30 @@
import { fetchArtist } from '@/web/api/artist'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import {
FetchArtistParams,
ArtistApiNames,
FetchArtistResponse,
} from '@/shared/api/Artist'
import { useQuery } from 'react-query'
export default function useArtist(
params: FetchArtistParams,
noCache?: boolean
) {
return useQuery(
[ArtistApiNames.FetchArtist, params],
() => fetchArtist(params, !!noCache),
{
enabled: !!params.id && params.id > 0 && !isNaN(Number(params.id)),
staleTime: 5 * 60 * 1000, // 5 mins
placeholderData: (): FetchArtistResponse =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Artist,
query: {
id: params.id,
},
}),
}
)
}

View file

@ -0,0 +1,30 @@
import { fetchArtistAlbums } from '@/web/api/artist'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import {
FetchArtistAlbumsParams,
ArtistApiNames,
FetchArtistAlbumsResponse,
} from '@/shared/api/Artist'
import { useQuery } from 'react-query'
export default function useUserAlbums(params: FetchArtistAlbumsParams) {
return useQuery(
[ArtistApiNames.FetchArtistAlbums, params],
async () => {
const data = await fetchArtistAlbums(params)
return data
},
{
enabled: !!params.id && params.id !== 0,
staleTime: 3600000,
placeholderData: (): FetchArtistAlbumsResponse =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.ArtistAlbum,
query: {
id: params.id,
},
}),
}
)
}

View file

@ -0,0 +1,47 @@
import { fetchLyric } from '@/web/api/track'
import reactQueryClient from '@/web/utils/reactQueryClient'
import {
FetchLyricParams,
FetchLyricResponse,
TrackApiNames,
} from '@/shared/api/Track'
import { APIs } from '@/shared/CacheAPIs'
import { IpcChannels } from '@/shared/IpcChannels'
import { useQuery } from 'react-query'
export default function useLyric(params: FetchLyricParams) {
return useQuery(
[TrackApiNames.FetchLyric, params],
() => {
return fetchLyric(params)
},
{
enabled: !!params.id && params.id !== 0,
refetchInterval: false,
staleTime: Infinity,
initialData: (): FetchLyricResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Lyric,
query: {
id: params.id,
},
}),
}
)
}
export function fetchTracksWithReactQuery(params: FetchLyricParams) {
return reactQueryClient.fetchQuery(
[TrackApiNames.FetchLyric, params],
() => {
return fetchLyric(params)
},
{
retry: 4,
retryDelay: (retryCount: number) => {
return retryCount * 500
},
staleTime: Infinity,
}
)
}

View file

@ -0,0 +1,18 @@
import { fetchPersonalFM, PersonalFMApiNames } from '@/web/api/personalFM'
import reactQueryClient from '@/web/utils/reactQueryClient'
export function fetchPersonalFMWithReactQuery() {
return reactQueryClient.fetchQuery(
PersonalFMApiNames.FetchPersonalFm,
async () => {
const data = await fetchPersonalFM()
if (!data.data?.length) {
throw new Error('No data')
}
return data
},
{
retry: 3,
}
)
}

View file

@ -0,0 +1,55 @@
import { fetchPlaylist } from '@/web/api/playlist'
import reactQueryClient from '@/web/utils/reactQueryClient'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import {
FetchPlaylistParams,
PlaylistApiNames,
FetchPlaylistResponse,
} from '@/shared/api/Playlists'
import { useQuery } from 'react-query'
const fetch = (params: FetchPlaylistParams, noCache?: boolean) => {
return fetchPlaylist(params, !!noCache)
}
export default function usePlaylist(
params: FetchPlaylistParams,
noCache?: boolean
) {
return useQuery(
[PlaylistApiNames.FetchPlaylist, params],
() => fetch(params, noCache),
{
enabled: !!(params.id && params.id > 0 && !isNaN(Number(params.id))),
refetchOnWindowFocus: true,
placeholderData: (): FetchPlaylistResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Playlist,
query: {
id: params.id,
},
}),
}
)
}
export function fetchPlaylistWithReactQuery(params: FetchPlaylistParams) {
return reactQueryClient.fetchQuery(
[PlaylistApiNames.FetchPlaylist, params],
() => fetch(params),
{
staleTime: 3600000,
}
)
}
export async function prefetchPlaylist(params: FetchPlaylistParams) {
await reactQueryClient.prefetchQuery(
[PlaylistApiNames.FetchPlaylist, params],
() => fetch(params),
{
staleTime: 3600000,
}
)
}

View file

@ -0,0 +1,62 @@
import { fetchAudioSource, fetchTracks } from '@/web/api/track'
import type {} from '@/web/api/track'
import reactQueryClient from '@/web/utils/reactQueryClient'
import { IpcChannels } from '@/shared/IpcChannels'
import {
FetchAudioSourceParams,
FetchTracksParams,
FetchTracksResponse,
TrackApiNames,
} from '@/shared/api/Track'
import { APIs } from '@/shared/CacheAPIs'
import { useQuery } from 'react-query'
export default function useTracks(params: FetchTracksParams) {
return useQuery(
[TrackApiNames.FetchTracks, params],
() => {
return fetchTracks(params)
},
{
enabled: params.ids.length !== 0,
refetchInterval: false,
staleTime: Infinity,
initialData: (): FetchTracksResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Track,
query: {
ids: params.ids.join(','),
},
}),
}
)
}
export function fetchTracksWithReactQuery(params: FetchTracksParams) {
return reactQueryClient.fetchQuery(
[TrackApiNames.FetchTracks, params],
() => {
return fetchTracks(params)
},
{
retry: 4,
retryDelay: (retryCount: number) => {
return retryCount * 500
},
staleTime: 86400000,
}
)
}
export function fetchAudioSourceWithReactQuery(params: FetchAudioSourceParams) {
return reactQueryClient.fetchQuery(
[TrackApiNames.FetchAudioSource, params],
() => {
return fetchAudioSource(params)
},
{
retry: 3,
staleTime: 0, // TODO: Web版1小时缓存
}
)
}

View file

@ -0,0 +1,33 @@
import { FetchTracksParams, TrackApiNames } from '@/shared/api/Track'
import { useInfiniteQuery } from 'react-query'
import { fetchTracks } from '../track'
// 100 tracks each page
const offset = 100
export default function useTracksInfinite(params: FetchTracksParams) {
return useInfiniteQuery(
[TrackApiNames.FetchTracks, params],
({ pageParam = 0 }) => {
const cursorStart = pageParam * offset
const cursorEnd = cursorStart + offset
const ids = params.ids.slice(cursorStart, cursorEnd)
return fetchTracks({ ids })
},
{
enabled: params.ids.length !== 0,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: 0,
staleTime: Infinity,
getNextPageParam: (lastPage, pages) => {
// 当 return undefined 时hasNextPage会等于false
// 当 return 非 undefined 时return 的数据会传入上面的fetchTracks函数中
return pages.length * offset < params.ids.length // 判断是否还有下一页
? pages.length
: undefined
},
}
)
}

View file

@ -0,0 +1,15 @@
import { fetchUserAccount } from '@/web/api/user'
import { UserApiNames, FetchUserAccountResponse } from '@/shared/api/User'
import { APIs } from '@/shared/CacheAPIs'
import { IpcChannels } from '@/shared/IpcChannels'
import { useQuery } from 'react-query'
export default function useUser() {
return useQuery(UserApiNames.FetchUserAccount, fetchUserAccount, {
refetchOnWindowFocus: true,
placeholderData: (): FetchUserAccountResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.UserAccount,
}),
})
}

View file

@ -0,0 +1,80 @@
import { likeAAlbum } from '@/web/api/album'
import { useMutation, useQuery, useQueryClient } from 'react-query'
import useUser from './useUser'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import {
FetchUserAlbumsParams,
UserApiNames,
FetchUserAlbumsResponse,
} from '@/shared/api/User'
import { fetchUserAlbums } from '../user'
export default function useUserAlbums(params: FetchUserAlbumsParams = {}) {
const { data: user } = useUser()
return useQuery(
[UserApiNames.FetchUserAlbums, user?.profile?.userId ?? 0],
() => fetchUserAlbums(params),
{
refetchOnWindowFocus: true,
placeholderData: (): FetchUserAlbumsResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.UserAlbums,
query: params,
}),
}
)
}
export const useMutationLikeAAlbum = () => {
const queryClient = useQueryClient()
const { data: user } = useUser()
const { data: userAlbums } = useUserAlbums({ limit: 2000 })
const uid = user?.account?.id ?? 0
const key = [UserApiNames.FetchUserAlbums, uid]
return useMutation(
async (album: Album) => {
if (!album.id || userAlbums?.data === undefined) {
throw new Error('album id is required or userAlbums is undefined')
}
const response = await likeAAlbum({
id: album.id,
t: userAlbums?.data.findIndex(a => a.id === album.id) > -1 ? 2 : 1,
})
if (response.code !== 200) throw new Error((response as any).msg)
return response
},
{
onMutate: async album => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(key)
// Snapshot the previous value
const previousData = queryClient.getQueryData(key)
// Optimistically update to the new value
queryClient.setQueryData(key, old => {
const userAlbums = old as FetchUserAlbumsResponse
const albums = userAlbums.data
const newAlbums =
albums.findIndex(a => a.id === album.id) > -1
? albums.filter(a => a.id !== album.id)
: [...albums, album]
return {
...userAlbums,
data: newAlbums,
}
})
// Return a context object with the snapshotted value
return { previousData }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, trackID, context) => {
queryClient.setQueryData(key, (context as any).previousData)
toast((err as any).toString())
},
}
)
}

View file

@ -0,0 +1,15 @@
import { fetchUserArtists } from '@/web/api/user'
import { UserApiNames, FetchUserArtistsResponse } from '@/shared/api/User'
import { APIs } from '@/shared/CacheAPIs'
import { IpcChannels } from '@/shared/IpcChannels'
import { useQuery } from 'react-query'
export default function useUserArtists() {
return useQuery([UserApiNames.FetchUserArtist], fetchUserArtists, {
refetchOnWindowFocus: true,
placeholderData: (): FetchUserArtistsResponse =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.UserArtists,
}),
})
}

View file

@ -0,0 +1,85 @@
import { likeATrack } from '@/web/api/track'
import useUser from './useUser'
import { useMutation, useQueryClient } from 'react-query'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import { fetchUserLikedTracksIDs } from '../user'
import {
FetchUserLikedTracksIDsResponse,
UserApiNames,
} from '@/shared/api/User'
import { useQuery } from 'react-query'
import toast from 'react-hot-toast'
export default function useUserLikedTracksIDs() {
const { data: user } = useUser()
const uid = user?.account?.id ?? 0
return useQuery(
[UserApiNames.FetchUserLikedTracksIds, uid],
() => fetchUserLikedTracksIDs({ uid }),
{
enabled: !!(uid && uid !== 0),
refetchOnWindowFocus: true,
placeholderData: (): FetchUserLikedTracksIDsResponse | undefined =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.Likelist,
query: {
uid,
},
}),
}
)
}
export const useMutationLikeATrack = () => {
const queryClient = useQueryClient()
const { data: user } = useUser()
const { data: userLikedSongs } = useUserLikedTracksIDs()
const uid = user?.account?.id ?? 0
const key = [UserApiNames.FetchUserLikedTracksIds, uid]
return useMutation(
async (trackID: number) => {
if (!trackID || userLikedSongs?.ids === undefined) {
throw new Error('trackID is required or userLikedSongs is undefined')
}
const response = await likeATrack({
id: trackID,
like: !userLikedSongs.ids.includes(trackID),
})
if (response.code !== 200) throw new Error((response as any).msg)
return response
},
{
onMutate: async trackID => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(key)
// Snapshot the previous value
const previousData = queryClient.getQueryData(key)
// Optimistically update to the new value
queryClient.setQueryData(key, old => {
const likedSongs = old as FetchUserLikedTracksIDsResponse
const ids = likedSongs.ids
const newIds = ids.includes(trackID)
? ids.filter(id => id !== trackID)
: [...ids, trackID]
return {
...likedSongs,
ids: newIds,
}
})
// Return a context object with the snapshotted value
return { previousData }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, trackID, context) => {
queryClient.setQueryData(key, (context as any).previousData)
toast((err as any).toString())
},
}
)
}

View file

@ -0,0 +1,100 @@
import { likeAPlaylist } from '@/web/api/playlist'
import { useMutation, useQuery, useQueryClient } from 'react-query'
import useUser from './useUser'
import { IpcChannels } from '@/shared/IpcChannels'
import { APIs } from '@/shared/CacheAPIs'
import { fetchUserPlaylists } from '@/web/api/user'
import { FetchUserPlaylistsResponse, UserApiNames } from '@/shared/api/User'
export default function useUserPlaylists() {
const { data: user } = useUser()
const uid = user?.profile?.userId ?? 0
const params = {
uid: uid,
offset: 0,
limit: 2000,
}
return useQuery(
[UserApiNames.FetchUserPlaylists, uid],
async () => {
if (!params.uid) {
throw new Error('请登录后再请求用户收藏的歌单')
}
const data = await fetchUserPlaylists(params)
return data
},
{
enabled: !!(
!!params.uid &&
params.uid !== 0 &&
params.offset !== undefined
),
refetchOnWindowFocus: true,
placeholderData: (): FetchUserPlaylistsResponse =>
window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, {
api: APIs.UserPlaylist,
query: {
uid: params.uid,
},
}),
}
)
}
export const useMutationLikeAPlaylist = () => {
const queryClient = useQueryClient()
const { data: user } = useUser()
const { data: userPlaylists } = useUserPlaylists()
const uid = user?.account?.id ?? 0
const key = [UserApiNames.FetchUserPlaylists, uid]
return useMutation(
async (playlist: Playlist) => {
if (!playlist.id || userPlaylists?.playlist === undefined) {
throw new Error('playlist id is required or userPlaylists is undefined')
}
const response = await likeAPlaylist({
id: playlist.id,
t:
userPlaylists.playlist.findIndex(p => p.id === playlist.id) > -1
? 2
: 1,
})
if (response.code !== 200) throw new Error((response as any).msg)
return response
},
{
onMutate: async playlist => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(key)
// Snapshot the previous value
const previousData = queryClient.getQueryData(key)
// Optimistically update to the new value
queryClient.setQueryData(key, old => {
const userPlaylists = old as FetchUserPlaylistsResponse
const playlists = userPlaylists.playlist
const newPlaylists =
playlists.findIndex(p => p.id === playlist.id) > -1
? playlists.filter(p => p.id !== playlist.id)
: [...playlists, playlist]
return {
...userPlaylists,
playlist: newPlaylists,
}
})
// Return a context object with the snapshotted value
return { previousData }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, trackID, context) => {
queryClient.setQueryData(key, (context as any).previousData)
toast((err as any).toString())
},
}
)
}