import { useMemo } from 'react'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { identityPersistMigrate, PERSIST_VERSION_INITIAL } from '/@/renderer/store/persist-migrate'; // Local favorites for media types the server does not expose a first-class // "favorite" concept for (long-form books/podcasts, playlists), or where we // deliberately keep them client-side (radio stations) so the home page surfaces // what *this user* cares about rather than the entire library. Music album / // artist / song favorites are server-side and go through the samo API. export type LibraryFavoriteType = 'audiobook' | 'playlist' | 'podcast' | 'radio'; const buildKey = (serverId: string, itemId: string) => `${serverId}:${itemId}`; interface LibraryFavoritesState { actions: { clear: (type: LibraryFavoriteType, serverId?: string) => void; isFavorite: (type: LibraryFavoriteType, serverId: string, itemId: string) => boolean; toggle: (type: LibraryFavoriteType, serverId: string, itemId: string) => boolean; }; audiobookKeys: Record; playlistKeys: Record; podcastKeys: Record; radioKeys: Record; } const bucketField = (type: LibraryFavoriteType) => type === 'audiobook' ? ('audiobookKeys' as const) : type === 'playlist' ? ('playlistKeys' as const) : type === 'podcast' ? ('podcastKeys' as const) : ('radioKeys' as const); export const useLibraryFavoritesStore = create()( persist( (set, get) => ({ actions: { clear: (type, serverId) => set((state) => { const field = bucketField(type); if (!serverId) { return { ...state, [field]: {} } as LibraryFavoritesState; } const next: Record = {}; const prefix = `${serverId}:`; for (const key of Object.keys(state[field])) { if (!key.startsWith(prefix)) next[key] = true; } return { ...state, [field]: next } as LibraryFavoritesState; }), isFavorite: (type, serverId, itemId) => { if (!serverId || !itemId) return false; return Boolean(get()[bucketField(type)][buildKey(serverId, itemId)]); }, toggle: (type, serverId, itemId) => { if (!serverId || !itemId) return false; const field = bucketField(type); const key = buildKey(serverId, itemId); const current = get()[field]; const isFav = Boolean(current[key]); if (isFav) { const next = { ...current }; delete next[key]; set({ [field]: next } as Partial); return false; } set({ [field]: { ...current, [key]: true }, } as Partial); return true; }, }, audiobookKeys: {}, playlistKeys: {}, podcastKeys: {}, radioKeys: {}, }), { migrate: identityPersistMigrate< Pick< LibraryFavoritesState, 'audiobookKeys' | 'playlistKeys' | 'podcastKeys' | 'radioKeys' > >, name: 'library-favorites-store', partialize: (state) => ({ audiobookKeys: state.audiobookKeys, playlistKeys: state.playlistKeys, podcastKeys: state.podcastKeys, radioKeys: state.radioKeys, }), version: PERSIST_VERSION_INITIAL, }, ), ); export const useLibraryFavoritesActions = () => useLibraryFavoritesStore((state) => state.actions); const useFavoriteIdSet = (type: LibraryFavoriteType, serverId: null | string | undefined) => { const field = bucketField(type); const bucket = useLibraryFavoritesStore((state) => state[field]); return useMemo(() => { if (!serverId) return new Set(); const ids = new Set(); const prefix = `${serverId}:`; for (const key of Object.keys(bucket)) { if (key.startsWith(prefix)) ids.add(key.slice(prefix.length)); } return ids; }, [bucket, serverId]); }; export const useFavoriteRadioStationIds = (serverId: null | string | undefined) => useFavoriteIdSet('radio', serverId); export const useFavoriteAudiobookIds = (serverId: null | string | undefined) => useFavoriteIdSet('audiobook', serverId); export const useFavoritePlaylistIds = (serverId: null | string | undefined) => useFavoriteIdSet('playlist', serverId); export const useFavoritePodcastIds = (serverId: null | string | undefined) => useFavoriteIdSet('podcast', serverId); export const useIsLibraryFavorite = ( type: LibraryFavoriteType, serverId: null | string | undefined, itemId: null | string | undefined, ) => { const field = bucketField(type); return useLibraryFavoritesStore((state) => serverId && itemId ? Boolean(state[field][buildKey(serverId, itemId)]) : false, ); };