mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(hooks): co-locate feature-coupled hooks (playback server/navigate/timeline/audio-devices->playback, smart-collage/pending-polling->playlist, contextmenu rating/keyboard->contextMenu)
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { useCallback } from 'react';
|
||||
import { setRating } from '@/lib/api/subsonicStarRating';
|
||||
import { queueSongRating } from '@/features/playback/store/pendingStarSync';
|
||||
import type { SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
|
||||
type RatingKind = 'song' | 'album' | 'artist';
|
||||
|
||||
interface Args {
|
||||
type: string | null;
|
||||
item: unknown;
|
||||
userRatingOverrides: Record<string, number>;
|
||||
setUserRatingOverride: (id: string, rating: number) => void;
|
||||
entityRatingSupport: 'full' | 'track_only' | 'unknown';
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
applySongRating: (songId: string, rating: number) => void;
|
||||
applyAlbumRating: (album: SubsonicAlbum, rating: number) => void;
|
||||
applyArtistRating: (artist: SubsonicArtist, rating: number) => void;
|
||||
getRatingValueByKind: (kind: RatingKind, id: string) => number;
|
||||
commitRatingByKind: (kind: RatingKind, id: string, rating: number) => void;
|
||||
}
|
||||
|
||||
export function useContextMenuRating({
|
||||
type, item, userRatingOverrides, setUserRatingOverride, entityRatingSupport, t,
|
||||
}: Args): Result {
|
||||
const setEntityRatingSupport = useAuthStore(s => s.setEntityRatingSupport);
|
||||
const activeServerId = useAuthStore(s => s.activeServerId);
|
||||
|
||||
const applySongRating = useCallback((songId: string, rating: number) => {
|
||||
// F4: optimistic override + retry-with-backoff sync via the central helper.
|
||||
queueSongRating(songId, rating);
|
||||
}, []);
|
||||
|
||||
const applyAlbumRating = useCallback((album: SubsonicAlbum, rating: number) => {
|
||||
setUserRatingOverride(album.id, rating);
|
||||
if (entityRatingSupport !== 'full') return;
|
||||
setRating(album.id, rating).catch(err => {
|
||||
if (activeServerId) setEntityRatingSupport(activeServerId, 'track_only');
|
||||
showToast(
|
||||
typeof err === 'string' ? err : err instanceof Error ? err.message : t('entityRating.saveFailed'),
|
||||
4500,
|
||||
'error',
|
||||
);
|
||||
});
|
||||
}, [setUserRatingOverride, entityRatingSupport, activeServerId, setEntityRatingSupport, t]);
|
||||
|
||||
const applyArtistRating = useCallback((artist: SubsonicArtist, rating: number) => {
|
||||
setUserRatingOverride(artist.id, rating);
|
||||
if (entityRatingSupport !== 'full') return;
|
||||
setRating(artist.id, rating).catch(err => {
|
||||
if (activeServerId) setEntityRatingSupport(activeServerId, 'track_only');
|
||||
showToast(
|
||||
typeof err === 'string' ? err : err instanceof Error ? err.message : t('entityRating.saveFailed'),
|
||||
4500,
|
||||
'error',
|
||||
);
|
||||
});
|
||||
}, [setUserRatingOverride, entityRatingSupport, activeServerId, setEntityRatingSupport, t]);
|
||||
|
||||
const getRatingValueByKind = useCallback((kind: RatingKind, id: string): number => {
|
||||
if (kind === 'song' && (type === 'song' || type === 'album-song' || type === 'queue-item')) {
|
||||
const song = item as Track;
|
||||
if (song.id === id) return userRatingOverrides[id] ?? song.userRating ?? 0;
|
||||
}
|
||||
if (kind === 'album' && type === 'album') {
|
||||
const album = item as SubsonicAlbum;
|
||||
if (album.id === id) return userRatingOverrides[id] ?? album.userRating ?? 0;
|
||||
}
|
||||
if (kind === 'album' && type === 'multi-album') {
|
||||
const albums = item as SubsonicAlbum[];
|
||||
const compositeId = [...albums.map(a => a.id)].sort().join('\x1e');
|
||||
if (id !== compositeId) return userRatingOverrides[id] ?? 0;
|
||||
if (albums.length === 0) return 0;
|
||||
const vals = albums.map(a => userRatingOverrides[a.id] ?? a.userRating ?? 0);
|
||||
const first = vals[0];
|
||||
return vals.every(v => v === first) ? first : 0;
|
||||
}
|
||||
if (kind === 'artist' && type === 'artist') {
|
||||
const artist = item as SubsonicArtist;
|
||||
if (artist.id === id) return userRatingOverrides[id] ?? artist.userRating ?? 0;
|
||||
}
|
||||
if (kind === 'artist' && type === 'multi-artist') {
|
||||
const artists = item as SubsonicArtist[];
|
||||
const compositeId = [...artists.map(a => a.id)].sort().join('\x1e');
|
||||
if (id !== compositeId) return userRatingOverrides[id] ?? 0;
|
||||
if (artists.length === 0) return 0;
|
||||
const vals = artists.map(a => userRatingOverrides[a.id] ?? a.userRating ?? 0);
|
||||
const first = vals[0];
|
||||
return vals.every(v => v === first) ? first : 0;
|
||||
}
|
||||
return userRatingOverrides[id] ?? 0;
|
||||
}, [type, item, userRatingOverrides]);
|
||||
|
||||
const commitRatingByKind = useCallback((kind: RatingKind, id: string, rating: number) => {
|
||||
if (kind === 'song') {
|
||||
applySongRating(id, rating);
|
||||
return;
|
||||
}
|
||||
if (kind === 'album' && type === 'album') {
|
||||
applyAlbumRating(item as SubsonicAlbum, rating);
|
||||
return;
|
||||
}
|
||||
if (kind === 'album' && type === 'multi-album') {
|
||||
const albums = item as SubsonicAlbum[];
|
||||
const compositeId = [...albums.map(a => a.id)].sort().join('\x1e');
|
||||
if (id !== compositeId) return;
|
||||
for (const a of albums) applyAlbumRating(a, rating);
|
||||
return;
|
||||
}
|
||||
if (kind === 'artist' && type === 'artist') {
|
||||
applyArtistRating(item as SubsonicArtist, rating);
|
||||
return;
|
||||
}
|
||||
if (kind === 'artist' && type === 'multi-artist') {
|
||||
const artists = item as SubsonicArtist[];
|
||||
const compositeId = [...artists.map(a => a.id)].sort().join('\x1e');
|
||||
if (id !== compositeId) return;
|
||||
for (const a of artists) applyArtistRating(a, rating);
|
||||
}
|
||||
}, [applySongRating, applyAlbumRating, applyArtistRating, type, item]);
|
||||
|
||||
return { applySongRating, applyAlbumRating, applyArtistRating, getRatingValueByKind, commitRatingByKind };
|
||||
}
|
||||
Reference in New Issue
Block a user