fix: better music modal design and favorite song

This commit is contained in:
Fredrik Burmester
2026-01-06 17:55:37 +01:00
parent 1c3369c61f
commit 3453fd22b8
2 changed files with 192 additions and 80 deletions

View File

@@ -1,22 +1,63 @@
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
import { useMutation } from "@tanstack/react-query";
import { useAtom } from "jotai";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { atom, useAtom } from "jotai";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
// Shared atom to store favorite status across all components
// Maps itemId -> isFavorite
const favoritesAtom = atom<Record<string, boolean>>({});
export const useFavorite = (item: BaseItemDto) => {
const queryClient = useNetworkAwareQueryClient();
const queryClient = useQueryClient();
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [isFavorite, setIsFavorite] = useState<boolean | undefined>(
item.UserData?.IsFavorite,
const [favorites, setFavorites] = useAtom(favoritesAtom);
const itemId = item.Id ?? "";
// Get current favorite status from shared state, falling back to item data
const isFavorite = itemId
? (favorites[itemId] ?? item.UserData?.IsFavorite)
: item.UserData?.IsFavorite;
// Update shared state when item data changes
useEffect(() => {
if (itemId && item.UserData?.IsFavorite !== undefined) {
setFavorites((prev) => ({
...prev,
[itemId]: item.UserData!.IsFavorite!,
}));
}
}, [itemId, item.UserData?.IsFavorite, setFavorites]);
// Helper to update favorite status in shared state
const setIsFavorite = useCallback(
(value: boolean | undefined) => {
if (itemId && value !== undefined) {
setFavorites((prev) => ({ ...prev, [itemId]: value }));
}
},
[itemId, setFavorites],
);
// Use refs to avoid stale closure issues in mutationFn
const itemRef = useRef(item);
const apiRef = useRef(api);
const userRef = useRef(user);
// Keep refs updated
useEffect(() => {
setIsFavorite(item.UserData?.IsFavorite);
}, [item.UserData?.IsFavorite]);
itemRef.current = item;
}, [item]);
useEffect(() => {
apiRef.current = api;
}, [api]);
useEffect(() => {
userRef.current = user;
}, [user]);
const itemQueryKeyPrefix = useMemo(
() => ["item", item.Id] as const,
@@ -42,18 +83,23 @@ export const useFavorite = (item: BaseItemDto) => {
const favoriteMutation = useMutation({
mutationFn: async (nextIsFavorite: boolean) => {
if (!api || !user || !item.Id) return;
if (nextIsFavorite) {
await getUserLibraryApi(api).markFavoriteItem({
userId: user.Id,
itemId: item.Id,
});
const currentApi = apiRef.current;
const currentUser = userRef.current;
const currentItem = itemRef.current;
if (!currentApi || !currentUser?.Id || !currentItem?.Id) {
return;
}
await getUserLibraryApi(api).unmarkFavoriteItem({
userId: user.Id,
itemId: item.Id,
});
// Use the same endpoint format as the web client:
// POST /Users/{userId}/FavoriteItems/{itemId} - add favorite
// DELETE /Users/{userId}/FavoriteItems/{itemId} - remove favorite
const path = `/Users/${currentUser.Id}/FavoriteItems/${currentItem.Id}`;
const response = nextIsFavorite
? await currentApi.post(path, {}, {})
: await currentApi.delete(path, {});
return response.data;
},
onMutate: async (nextIsFavorite: boolean) => {
await queryClient.cancelQueries({ queryKey: itemQueryKeyPrefix });