mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 23:02:43 +01:00
reimpl all music state into zustand
This commit is contained in:
@@ -3,39 +3,101 @@ import {
|
||||
AlbumWithSongs,
|
||||
Artist,
|
||||
ArtistInfo,
|
||||
HomeLists,
|
||||
mapAlbumID3toAlbumListItem,
|
||||
mapAlbumID3WithSongstoAlbunWithSongs,
|
||||
mapArtistID3toArtist,
|
||||
mapArtistInfo,
|
||||
mapChildToSong,
|
||||
mapPlaylistListItem,
|
||||
mapPlaylistWithSongs,
|
||||
PlaylistListItem,
|
||||
PlaylistWithSongs,
|
||||
SearchResults,
|
||||
} from '@app/models/music'
|
||||
import { Store } from '@app/state/store'
|
||||
import { GetAlbumList2Type } from '@app/subsonic/params'
|
||||
import produce from 'immer'
|
||||
import { atom } from 'jotai'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
export type MusicSlice = {
|
||||
//
|
||||
// family-style state
|
||||
//
|
||||
cacheSize: number
|
||||
|
||||
artistInfo: { [id: string]: ArtistInfo | undefined }
|
||||
artistInfoCache: string[]
|
||||
albums: { [id: string]: AlbumWithSongs | undefined }
|
||||
albumsCache: string[]
|
||||
playlists: { [id: string]: PlaylistWithSongs | undefined }
|
||||
playlistsCache: string[]
|
||||
fetchArtistInfo: (id: string) => Promise<ArtistInfo | undefined>
|
||||
fetchAlbum: (id: string) => Promise<AlbumWithSongs | undefined>
|
||||
fetchPlaylist: (id: string) => Promise<PlaylistWithSongs | undefined>
|
||||
|
||||
albumsWithSongs: { [id: string]: AlbumWithSongs | undefined }
|
||||
albumsWithSongsCache: string[]
|
||||
fetchAlbumWithSongs: (id: string) => Promise<AlbumWithSongs | undefined>
|
||||
|
||||
playlistsWithSongs: { [id: string]: PlaylistWithSongs | undefined }
|
||||
playlistsWithSongsCache: string[]
|
||||
fetchPlaylistWithSongs: (id: string) => Promise<PlaylistWithSongs | undefined>
|
||||
|
||||
//
|
||||
// lists-style state
|
||||
//
|
||||
artists: Artist[]
|
||||
artistsUpdating: boolean
|
||||
fetchArtists: () => Promise<void>
|
||||
|
||||
playlists: PlaylistListItem[]
|
||||
playlistsUpdating: boolean
|
||||
fetchPlaylists: () => Promise<void>
|
||||
|
||||
albums: AlbumListItem[]
|
||||
albumsUpdating: boolean
|
||||
fetchAlbums: (size?: number, offset?: number) => Promise<void>
|
||||
|
||||
searchResults: SearchResults
|
||||
searchResultsUpdating: boolean
|
||||
fetchSearchResults: (query: string) => Promise<void>
|
||||
clearSearchResults: () => void
|
||||
|
||||
homeLists: HomeLists
|
||||
homeListsUpdating: boolean
|
||||
fetchHomeLists: () => Promise<void>
|
||||
clearHomeLists: () => void
|
||||
}
|
||||
|
||||
export const selectMusic = {
|
||||
fetchArtistInfo: (state: Store) => state.fetchArtistInfo,
|
||||
fetchAlbumWithSongs: (state: Store) => state.fetchAlbumWithSongs,
|
||||
fetchPlaylistWithSongs: (state: Store) => state.fetchPlaylistWithSongs,
|
||||
|
||||
artists: (store: MusicSlice) => store.artists,
|
||||
artistsUpdating: (store: MusicSlice) => store.artistsUpdating,
|
||||
fetchArtists: (store: MusicSlice) => store.fetchArtists,
|
||||
|
||||
playlists: (store: MusicSlice) => store.playlists,
|
||||
playlistsUpdating: (store: MusicSlice) => store.playlistsUpdating,
|
||||
fetchPlaylists: (store: MusicSlice) => store.fetchPlaylists,
|
||||
|
||||
albums: (store: MusicSlice) => store.albums,
|
||||
albumsUpdating: (store: MusicSlice) => store.albumsUpdating,
|
||||
fetchAlbums: (store: MusicSlice) => store.fetchAlbums,
|
||||
|
||||
searchResults: (store: MusicSlice) => store.searchResults,
|
||||
searchResultsUpdating: (store: MusicSlice) => store.searchResultsUpdating,
|
||||
fetchSearchResults: (store: MusicSlice) => store.fetchSearchResults,
|
||||
clearSearchResults: (store: MusicSlice) => store.clearSearchResults,
|
||||
|
||||
homeLists: (store: MusicSlice) => store.homeLists,
|
||||
homeListsUpdating: (store: MusicSlice) => store.homeListsUpdating,
|
||||
fetchHomeLists: (store: MusicSlice) => store.fetchHomeLists,
|
||||
clearHomeLists: (store: MusicSlice) => store.clearHomeLists,
|
||||
}
|
||||
|
||||
export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): MusicSlice => ({
|
||||
cacheSize: 100,
|
||||
|
||||
artistInfo: {},
|
||||
artistInfoCache: [],
|
||||
albums: {},
|
||||
albumsCache: [],
|
||||
playlists: {},
|
||||
playlistsCache: [],
|
||||
|
||||
fetchArtistInfo: async id => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
@@ -58,7 +120,7 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.artistInfoCache.length >= state.cacheSize) {
|
||||
delete state.albums[state.artistInfoCache.shift() as string]
|
||||
delete state.albumsWithSongs[state.artistInfoCache.shift() as string]
|
||||
}
|
||||
state.artistInfo[id] = artistInfo
|
||||
state.artistInfoCache.push(id)
|
||||
@@ -69,7 +131,11 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
fetchAlbum: async id => {
|
||||
|
||||
albumsWithSongs: {},
|
||||
albumsWithSongsCache: [],
|
||||
|
||||
fetchAlbumWithSongs: async id => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return undefined
|
||||
@@ -81,11 +147,11 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.albumsCache.length >= state.cacheSize) {
|
||||
delete state.albums[state.albumsCache.shift() as string]
|
||||
if (state.albumsWithSongsCache.length >= state.cacheSize) {
|
||||
delete state.albumsWithSongs[state.albumsWithSongsCache.shift() as string]
|
||||
}
|
||||
state.albums[id] = album
|
||||
state.albumsCache.push(id)
|
||||
state.albumsWithSongs[id] = album
|
||||
state.albumsWithSongsCache.push(id)
|
||||
}),
|
||||
)
|
||||
return album
|
||||
@@ -93,7 +159,11 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
fetchPlaylist: async id => {
|
||||
|
||||
playlistsWithSongs: {},
|
||||
playlistsWithSongsCache: [],
|
||||
|
||||
fetchPlaylistWithSongs: async id => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return undefined
|
||||
@@ -105,11 +175,11 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.playlistsCache.length >= state.cacheSize) {
|
||||
delete state.playlists[state.playlistsCache.shift() as string]
|
||||
if (state.playlistsWithSongsCache.length >= state.cacheSize) {
|
||||
delete state.playlistsWithSongs[state.playlistsWithSongsCache.shift() as string]
|
||||
}
|
||||
state.playlists[id] = playlist
|
||||
state.playlistsCache.push(id)
|
||||
state.playlistsWithSongs[id] = playlist
|
||||
state.playlistsWithSongsCache.push(id)
|
||||
}),
|
||||
)
|
||||
return playlist
|
||||
@@ -117,35 +187,155 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const artistsAtom = atom<Artist[]>([])
|
||||
export const artistsUpdatingAtom = atom(false)
|
||||
artists: [],
|
||||
artistsUpdating: false,
|
||||
|
||||
export type HomeLists = { [key: string]: AlbumListItem[] }
|
||||
fetchArtists: async () => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
export const homeListsUpdatingAtom = atom(false)
|
||||
export const homeListsAtom = atom<HomeLists>({})
|
||||
export const homeListsWriteAtom = atom<HomeLists, { type: string; albums: AlbumListItem[] }>(
|
||||
get => get(homeListsAtom),
|
||||
(get, set, { type, albums }) => {
|
||||
const lists = get(homeListsAtom)
|
||||
set(homeListsAtom, {
|
||||
...lists,
|
||||
[type]: albums,
|
||||
if (get().artistsUpdating) {
|
||||
return
|
||||
}
|
||||
set({ artistsUpdating: true })
|
||||
|
||||
try {
|
||||
const response = await client.getArtists()
|
||||
set({ artists: response.data.artists.map(mapArtistID3toArtist) })
|
||||
} finally {
|
||||
set({ artistsUpdating: false })
|
||||
}
|
||||
},
|
||||
|
||||
playlists: [],
|
||||
playlistsUpdating: false,
|
||||
|
||||
fetchPlaylists: async () => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
if (get().playlistsUpdating) {
|
||||
return
|
||||
}
|
||||
set({ playlistsUpdating: true })
|
||||
|
||||
try {
|
||||
const response = await client.getPlaylists()
|
||||
set({ playlists: response.data.playlists.map(mapPlaylistListItem) })
|
||||
} finally {
|
||||
set({ playlistsUpdating: false })
|
||||
}
|
||||
},
|
||||
|
||||
albums: [],
|
||||
albumsUpdating: false,
|
||||
|
||||
fetchAlbums: async (size = 500, offset = 0) => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
if (get().albumsUpdating) {
|
||||
return
|
||||
}
|
||||
set({ albumsUpdating: true })
|
||||
|
||||
try {
|
||||
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size, offset })
|
||||
set({ albums: response.data.albums.map(mapAlbumID3toAlbumListItem) })
|
||||
} finally {
|
||||
set({ albumsUpdating: false })
|
||||
}
|
||||
},
|
||||
|
||||
searchResults: {
|
||||
artists: [],
|
||||
albums: [],
|
||||
songs: [],
|
||||
},
|
||||
searchResultsUpdating: false,
|
||||
|
||||
fetchSearchResults: async query => {
|
||||
if (query.length < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
if (get().searchResultsUpdating) {
|
||||
return
|
||||
}
|
||||
|
||||
set({ searchResultsUpdating: true })
|
||||
|
||||
try {
|
||||
const response = await client.search3({ query })
|
||||
set({
|
||||
searchResults: {
|
||||
artists: response.data.artists.map(mapArtistID3toArtist),
|
||||
albums: response.data.albums.map(mapAlbumID3toAlbumListItem),
|
||||
songs: response.data.songs.map(a => mapChildToSong(a, client)),
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
set({ searchResultsUpdating: false })
|
||||
}
|
||||
},
|
||||
clearSearchResults: () => {
|
||||
set({
|
||||
searchResults: {
|
||||
artists: [],
|
||||
albums: [],
|
||||
songs: [],
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const searchResultsUpdatingAtom = atom(false)
|
||||
export const searchResultsAtom = atom<SearchResults>({
|
||||
artists: [],
|
||||
albums: [],
|
||||
songs: [],
|
||||
homeLists: {},
|
||||
homeListsUpdating: false,
|
||||
|
||||
fetchHomeLists: async () => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
if (get().homeListsUpdating) {
|
||||
return
|
||||
}
|
||||
set({ homeListsUpdating: true })
|
||||
|
||||
const types = get().settings.home.lists
|
||||
|
||||
try {
|
||||
const promises: Promise<any>[] = []
|
||||
for (const type of types) {
|
||||
promises.push(
|
||||
client.getAlbumList2({ type: type as GetAlbumList2Type, size: 20 }).then(response => {
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.homeLists[type] = response.data.albums.map(mapAlbumID3toAlbumListItem)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
await Promise.all(promises)
|
||||
} finally {
|
||||
set({ homeListsUpdating: false })
|
||||
}
|
||||
},
|
||||
|
||||
clearHomeLists: () => {
|
||||
set({ homeLists: {} })
|
||||
},
|
||||
})
|
||||
|
||||
export const playlistsUpdatingAtom = atom(false)
|
||||
export const playlistsAtom = atom<PlaylistListItem[]>([])
|
||||
|
||||
export const albumListUpdatingAtom = atom(false)
|
||||
export const albumListAtom = atom<AlbumListItem[]>([])
|
||||
|
||||
Reference in New Issue
Block a user