support navidrome playlist image upload

This commit is contained in:
jeffvli
2026-04-02 01:23:09 -07:00
parent 68dacea228
commit 7442f9d3ca
14 changed files with 347 additions and 4 deletions
@@ -8,6 +8,8 @@ import { useListContext } from '/@/renderer/context/list-context';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
import { useDeletePlaylistImage } from '/@/renderer/features/playlists/mutations/delete-playlist-image-mutation';
import { useUploadPlaylistImage } from '/@/renderer/features/playlists/mutations/upload-playlist-image-mutation';
import { FilterBar } from '/@/renderer/features/shared/components/filter-bar';
import {
LibraryHeader,
@@ -18,9 +20,14 @@ import { ListSearchInput } from '/@/renderer/features/shared/components/list-sea
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store';
import { formatDurationString } from '/@/renderer/utils';
import { hasFeature } from '/@/shared/api/utils';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { FileButton } from '/@/shared/components/file-button/file-button';
import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack';
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
import { LibraryItem, Song } from '/@/shared/types/domain-types';
import { LibraryItem, Playlist, Song } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
import { Play } from '/@/shared/types/types';
interface PlaylistDetailSongListHeaderProps {
@@ -30,6 +37,64 @@ interface PlaylistDetailSongListHeaderProps {
onToggleQueryBuilder?: () => void;
}
function ImageUploadOverlay({ data }: { data?: Playlist }) {
const uploadPlaylistImageMutation = useUploadPlaylistImage({});
const deletePlaylistImageMutation = useDeletePlaylistImage({});
const server = useCurrentServer();
if (!data) return null;
if (!hasFeature(server, ServerFeature.PLAYLIST_IMAGE_UPLOAD)) return null;
return (
<Group gap="xs">
<FileButton
accept="image/*"
onChange={async (file) => {
if (!file || !data?._serverId) return;
const buffer = await file.arrayBuffer();
uploadPlaylistImageMutation.mutate({
apiClientProps: {
serverId: data._serverId,
},
body: { image: new Uint8Array(buffer) },
query: { id: data.id },
});
}}
>
{(props) => (
<ActionIcon
icon="uploadImage"
iconProps={{ size: 'lg' }}
radius="xl"
size="xs"
variant="default"
{...props}
/>
)}
</FileButton>
<ActionIcon
disabled={!data?.uploadedImage}
icon="delete"
iconProps={{ size: 'lg' }}
onClick={(e) => {
e.stopPropagation();
if (!data?._serverId) return;
deletePlaylistImageMutation.mutate({
apiClientProps: {
serverId: data._serverId,
},
query: { id: data.id },
});
}}
radius="xl"
size="xs"
variant="default"
/>
</Group>
);
}
export const PlaylistDetailSongListHeader = ({
isSmartPlaylist,
}: PlaylistDetailSongListHeaderProps) => {
@@ -94,6 +159,7 @@ export const PlaylistDetailSongListHeader = ({
) : (
<LibraryHeader
compact
imageOverlay={<ImageUploadOverlay data={detailQuery?.data} />}
imageUrl={imageUrl}
item={{
imageId: detailQuery?.data?.imageId,