mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-03-22 09:16:38 +00:00
fix: better music modal design and favorite song
This commit is contained in:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user