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
@@ -490,6 +490,8 @@ const normalizePlaylist = (
item: z.infer<typeof ndType._response.playlist>,
server?: null | ServerListItem,
): Playlist => {
const imageId = !item.uploadedImage ? item.id : `pl-${item.id}&square=true&_=${item.updatedAt}`;
return {
_itemType: LibraryItem.PLAYLIST,
_serverId: server?.id || 'unknown',
@@ -498,7 +500,7 @@ const normalizePlaylist = (
duration: item.duration * 1000,
genres: [],
id: item.id,
imageId: item.id,
imageId,
imageUrl: null,
name: item.name,
owner: item.ownerName,
@@ -508,6 +510,7 @@ const normalizePlaylist = (
size: item.size,
songCount: item.songCount,
sync: item.sync,
uploadedImage: item.uploadedImage,
};
};
@@ -624,6 +624,7 @@ const playlist = z.object({
songCount: z.number(),
sync: z.boolean(),
updatedAt: z.string(),
uploadedImage: z.string().optional(),
});
const playlistList = z.array(playlist);
@@ -659,6 +660,18 @@ const updatePlaylist = playlist;
const updatePlaylistParameters = createPlaylistParameters.partial();
const uploadPlaylistImage = z.object({
status: z.string(),
});
const uploadPlaylistImageParameters = z.object({
image: z.instanceof(Uint8Array),
});
const deletePlaylistImage = z.object({
status: z.string(),
});
const deletePlaylist = z.null();
const addToPlaylist = z.object({
@@ -760,6 +773,7 @@ export const ndType = {
songList: songListParameters,
tagList: tagListParameters,
updatePlaylist: updatePlaylistParameters,
uploadPlaylistImage: uploadPlaylistImageParameters,
userList: userListParameters,
},
_response: {
@@ -771,6 +785,7 @@ export const ndType = {
authenticate,
createPlaylist,
deletePlaylist,
deletePlaylistImage,
error,
genre,
genreList,
@@ -787,6 +802,7 @@ export const ndType = {
songList,
tagList,
updatePlaylist,
uploadPlaylistImage,
user,
userList,
},
@@ -0,0 +1,12 @@
import {
FileButton as MantineFileButton,
FileButtonProps as MantineFileButtonProps,
} from '@mantine/core';
import { CSSProperties } from 'react';
export interface FileButtonProps extends MantineFileButtonProps {
maxWidth?: CSSProperties['maxWidth'];
width?: CSSProperties['width'];
}
export const FileButton = MantineFileButton;
+4 -2
View File
@@ -28,6 +28,7 @@ import {
LuArrowUpToLine,
LuBookOpen,
LuBraces,
LuCamera,
LuCheck,
LuChevronDown,
LuChevronLast,
@@ -41,7 +42,6 @@ import {
LuCloudDownload,
LuCornerDownRight,
LuCornerUpRight,
LuDelete,
LuDisc,
LuDisc3,
LuDownload,
@@ -117,6 +117,7 @@ import {
LuTable,
LuTimer,
LuTimerOff,
LuTrash,
LuTriangleAlert,
LuUpload,
LuUser,
@@ -248,7 +249,7 @@ export const AppIcon = {
check: LuCheck,
clipboardCopy: LuClipboardCopy,
collection: LuPackage2,
delete: LuDelete,
delete: LuTrash,
disc: LuDisc,
download: LuDownload,
dragHorizontal: LuGripHorizontal,
@@ -351,6 +352,7 @@ export const AppIcon = {
unfavorite: LuHeartCrack,
unpin: LuPinOff,
upload: LuUpload,
uploadImage: LuCamera,
user: LuUser,
userManage: LuUserRoundCog,
visibility: MdOutlineVisibility,
+34
View File
@@ -344,6 +344,7 @@ export type Playlist = {
size: null | number;
songCount: null | number;
sync?: boolean | null;
uploadedImage?: string;
};
export type RelatedAlbumArtist = {
@@ -968,6 +969,16 @@ export type DeletePlaylistArgs = BaseEndpointArgs & {
query: DeletePlaylistQuery;
};
export type DeletePlaylistImageArgs = BaseEndpointArgs & {
query: DeletePlaylistImageQuery;
};
export type DeletePlaylistImageQuery = {
id: string;
};
export type DeletePlaylistImageResponse = boolean;
export type DeletePlaylistQuery = { id: string };
// Delete Playlist
@@ -1106,6 +1117,21 @@ export type UpdatePlaylistQuery = {
// Update Playlist
export type UpdatePlaylistResponse = null | undefined;
export type UploadPlaylistImageArgs = BaseEndpointArgs & {
body: UploadPlaylistImageBody;
query: UploadPlaylistImageQuery;
};
export type UploadPlaylistImageBody = {
image: Uint8Array;
};
export type UploadPlaylistImageQuery = {
id: string;
};
export type UploadPlaylistImageResponse = boolean;
type PlaylistListSortMap = {
jellyfin: Record<PlaylistListSort, JFPlaylistListSort | undefined>;
navidrome: Record<PlaylistListSort, NDPlaylistListSort | undefined>;
@@ -1390,6 +1416,7 @@ export type ControllerEndpoint = {
args: DeleteInternetRadioStationArgs,
) => Promise<DeleteInternetRadioStationResponse>;
deletePlaylist: (args: DeletePlaylistArgs) => Promise<DeletePlaylistResponse>;
deletePlaylistImage?: (args: DeletePlaylistImageArgs) => Promise<DeletePlaylistImageResponse>;
getAlbumArtistDetail: (args: AlbumArtistDetailArgs) => Promise<AlbumArtistDetailResponse>;
getAlbumArtistInfo?: (args: AlbumArtistInfoArgs) => Promise<AlbumArtistInfoResponse | null>;
getAlbumArtistList: (args: AlbumArtistListArgs) => Promise<AlbumArtistListResponse>;
@@ -1443,6 +1470,7 @@ export type ControllerEndpoint = {
args: UpdateInternetRadioStationArgs,
) => Promise<UpdateInternetRadioStationResponse>;
updatePlaylist: (args: UpdatePlaylistArgs) => Promise<UpdatePlaylistResponse>;
uploadPlaylistImage?: (args: UploadPlaylistImageArgs) => Promise<UploadPlaylistImageResponse>;
};
export type DownloadArgs = BaseEndpointArgs & {
@@ -1515,6 +1543,9 @@ export type InternalControllerEndpoint = {
deletePlaylist: (
args: ReplaceApiClientProps<DeletePlaylistArgs>,
) => Promise<DeletePlaylistResponse>;
deletePlaylistImage?: (
args: ReplaceApiClientProps<DeletePlaylistImageArgs>,
) => Promise<DeletePlaylistImageResponse>;
getAlbumArtistDetail: (
args: ReplaceApiClientProps<AlbumArtistDetailArgs>,
) => Promise<AlbumArtistDetailResponse>;
@@ -1599,6 +1630,9 @@ export type InternalControllerEndpoint = {
updatePlaylist: (
args: ReplaceApiClientProps<UpdatePlaylistArgs>,
) => Promise<UpdatePlaylistResponse>;
uploadPlaylistImage?: (
args: ReplaceApiClientProps<UploadPlaylistImageArgs>,
) => Promise<UploadPlaylistImageResponse>;
};
export type LyricGetQuery = {
+1
View File
@@ -8,6 +8,7 @@ export enum ServerFeature {
MUSIC_FOLDER_MULTISELECT = 'musicFolderMultiselect',
OS_FORM_POST = 'osFormPost',
OS_TRANSCODE_DECISION = 'osTranscodeDecision',
PLAYLIST_IMAGE_UPLOAD = 'playlistImageUpload',
PLAYLISTS_SMART = 'playlistsSmart',
PUBLIC_PLAYLIST = 'publicPlaylist',
SERVER_PLAY_QUEUE = 'serverPlayQueue',