diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 4d9428c5..7f05b9dd 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -249,6 +249,8 @@ "title": "delete $t(entity.playlist_one)" }, "editPlaylist": { + "publicJellyfinNote": "Jellyfin for some reason does not expose whether a playlist is public or not. If you wish for this to remain public, please have the following input selected", + "success": "$t(entity.playlist_one) updated successfully", "title": "edit $t(entity.playlist_one)" }, "lyricSearch": { diff --git a/src/renderer/api/features-types.ts b/src/renderer/api/features-types.ts index f1ccc3e7..fb5e1318 100644 --- a/src/renderer/api/features-types.ts +++ b/src/renderer/api/features-types.ts @@ -4,6 +4,7 @@ export enum ServerFeature { LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured', LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured', PLAYLISTS_SMART = 'playlistsSmart', + PUBLIC_PLAYLIST = 'publicPlaylist', SHARING_ALBUM_SONG = 'sharingAlbumSong', } diff --git a/src/renderer/api/jellyfin/jellyfin-api.ts b/src/renderer/api/jellyfin/jellyfin-api.ts index 06afc668..72bb21c8 100644 --- a/src/renderer/api/jellyfin/jellyfin-api.ts +++ b/src/renderer/api/jellyfin/jellyfin-api.ts @@ -292,8 +292,8 @@ export const contract = c.router({ }, updatePlaylist: { body: jfType._parameters.updatePlaylist, - method: 'PUT', - path: 'items/:id', + method: 'POST', + path: 'playlists/:id', responses: { 200: jfType._response.updatePlaylist, 400: jfType._response.error, diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index 8ad47305..4ce9b381 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -607,9 +607,9 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise ({ Id: item.id, Name: item.name })) || [], + IsPublic: body.public, MediaType: 'Audio', Name: body.name, - Overview: body.comment || '', PremiereDate: null, ProviderIds: {}, Tags: [], @@ -646,7 +646,7 @@ const updatePlaylist = async (args: UpdatePlaylistArgs): Promise return jfNormalize.song(res.body, apiClientProps.server, ''); }; -const VERSION_INFO: VersionInfo = [['10.9.0', { [ServerFeature.LYRICS_SINGLE_STRUCTURED]: [1] }]]; +const VERSION_INFO: VersionInfo = [ + [ + '10.9.0', + { [ServerFeature.LYRICS_SINGLE_STRUCTURED]: [1], [ServerFeature.PUBLIC_PLAYLIST]: [1] }, + ], +]; const getServerInfo = async (args: ServerInfoArgs): Promise => { const { apiClientProps } = args; diff --git a/src/renderer/api/jellyfin/jellyfin-types.ts b/src/renderer/api/jellyfin/jellyfin-types.ts index 15f70e1f..36a2764d 100644 --- a/src/renderer/api/jellyfin/jellyfin-types.ts +++ b/src/renderer/api/jellyfin/jellyfin-types.ts @@ -581,9 +581,9 @@ const playlistDetailParameters = baseParameters.extend({ }); const createPlaylistParameters = z.object({ + IsPublic: z.boolean().optional(), MediaType: z.literal('Audio'), Name: z.string(), - Overview: z.string(), UserId: z.string(), }); @@ -595,9 +595,9 @@ const updatePlaylist = z.null(); const updatePlaylistParameters = z.object({ Genres: z.array(genreItem), + IsPublic: z.boolean().optional(), MediaType: z.literal('Audio'), Name: z.string(), - Overview: z.string(), PremiereDate: z.null(), ProviderIds: z.object({}), Tags: z.array(genericItem), diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 1e9d9772..ad38468e 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -317,7 +317,7 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise => { const features: ServerFeatures = { lyricsMultipleStructured: !!navidromeFeatures[SubsonicExtensions.SONG_LYRICS], playlistsSmart: !!navidromeFeatures[ServerFeature.PLAYLISTS_SMART], + publicPlaylist: true, sharingAlbumSong: !!navidromeFeatures[ServerFeature.SHARING_ALBUM_SONG], }; diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index a96af4f8..b5f55fc4 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -818,13 +818,13 @@ export type CreatePlaylistBody = { navidrome?: { owner?: string; ownerId?: string; - public?: boolean; rules?: Record; sync?: boolean; }; }; comment?: string; name: string; + public?: boolean; }; export type CreatePlaylistArgs = { body: CreatePlaylistBody; serverId?: string } & BaseEndpointArgs; @@ -841,7 +841,6 @@ export type UpdatePlaylistBody = { navidrome?: { owner?: string; ownerId?: string; - public?: boolean; rules?: Record; sync?: boolean; }; @@ -849,6 +848,7 @@ export type UpdatePlaylistBody = { comment?: string; genres?: Genre[]; name: string; + public?: boolean; }; export type UpdatePlaylistArgs = { diff --git a/src/renderer/features/playlists/components/create-playlist-form.tsx b/src/renderer/features/playlists/components/create-playlist-form.tsx index 12fbbe08..13f8609b 100644 --- a/src/renderer/features/playlists/components/create-playlist-form.tsx +++ b/src/renderer/features/playlists/components/create-playlist-form.tsx @@ -28,7 +28,6 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { initialValues: { _custom: { navidrome: { - public: false, rules: undefined, }, }, @@ -88,7 +87,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { ); }); - const isPublicDisplayed = server?.type === ServerType.NAVIDROME; + const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST); const isSubmitDisabled = !form.values.name || mutation.isLoading; return ( @@ -103,13 +102,15 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { })} {...form.getInputProps('name')} /> - + {server?.type === ServerType.NAVIDROME && ( + + )} {isPublicDisplayed && ( { context: 'public', postProcess: 'titleCase', })} - {...form.getInputProps('_custom.navidrome.public', { + {...form.getInputProps('public', { type: 'checkbox', })} /> diff --git a/src/renderer/features/playlists/components/save-as-playlist-form.tsx b/src/renderer/features/playlists/components/save-as-playlist-form.tsx index 5196b245..8b1e3d96 100644 --- a/src/renderer/features/playlists/components/save-as-playlist-form.tsx +++ b/src/renderer/features/playlists/components/save-as-playlist-form.tsx @@ -5,6 +5,8 @@ import { Button, Switch, TextInput, toast } from '/@/renderer/components'; import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation'; import { useCurrentServer } from '/@/renderer/store'; import { useTranslation } from 'react-i18next'; +import { ServerFeature } from '/@/renderer/api/features-types'; +import { hasFeature } from '/@/renderer/api/utils'; interface SaveAsPlaylistFormProps { body: Partial; @@ -27,13 +29,13 @@ export const SaveAsPlaylistForm = ({ initialValues: { _custom: { navidrome: { - public: false, rules: undefined, ...body?._custom?.navidrome, }, }, comment: body.comment || '', name: body.name || '', + public: body.public, }, }); @@ -58,7 +60,7 @@ export const SaveAsPlaylistForm = ({ ); }); - const isPublicDisplayed = server?.type === ServerType.NAVIDROME; + const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST); const isSubmitDisabled = !form.values.name || mutation.isLoading; return ( @@ -73,20 +75,22 @@ export const SaveAsPlaylistForm = ({ })} {...form.getInputProps('name')} /> - + {server?.type === ServerType.NAVIDROME && ( + + )} {isPublicDisplayed && ( )} diff --git a/src/renderer/features/playlists/components/update-playlist-form.tsx b/src/renderer/features/playlists/components/update-playlist-form.tsx index 72e1d876..38b6f690 100644 --- a/src/renderer/features/playlists/components/update-playlist-form.tsx +++ b/src/renderer/features/playlists/components/update-playlist-form.tsx @@ -20,6 +20,8 @@ import { queryClient } from '/@/renderer/lib/react-query'; import { useCurrentServer } from '/@/renderer/store'; import { useTranslation } from 'react-i18next'; import i18n from '/@/i18n/i18n'; +import { hasFeature } from '/@/renderer/api/utils'; +import { ServerFeature } from '/@/renderer/api/features-types'; interface UpdatePlaylistFormProps { body: Partial; @@ -44,13 +46,13 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl navidrome: { owner: body?._custom?.navidrome?.owner || '', ownerId: body?._custom?.navidrome?.ownerId || '', - public: body?._custom?.navidrome?.public || false, rules: undefined, sync: body?._custom?.navidrome?.sync || false, }, }, comment: body?.comment || '', name: body?.name || '', + public: body.public, }, }); @@ -69,13 +71,16 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl }); }, onSuccess: () => { + toast.success({ + message: t('form.editPlaylist.success', { postProcess: 'sentenceCase' }), + }); onCancel(); }, }, ); }); - const isPublicDisplayed = server?.type === ServerType.NAVIDROME; + const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST); const isOwnerDisplayed = server?.type === ServerType.NAVIDROME && userList; const isSubmitDisabled = !form.values.name || mutation.isLoading; @@ -91,13 +96,15 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl })} {...form.getInputProps('name')} /> - + {server?.type === ServerType.NAVIDROME && ( + + )} {isOwnerDisplayed && (