Add update playlist for jellyfin

This commit is contained in:
jeffvli 2023-01-03 03:25:21 -08:00
parent 0ed13c75af
commit 633c9f59d9
5 changed files with 74 additions and 18 deletions

View file

@ -96,7 +96,7 @@ const endpoints: ApiController = {
getPlaylistSongList: jellyfinApi.getPlaylistSongList, getPlaylistSongList: jellyfinApi.getPlaylistSongList,
getSongDetail: undefined, getSongDetail: undefined,
getSongList: jellyfinApi.getSongList, getSongList: jellyfinApi.getSongList,
updatePlaylist: undefined, updatePlaylist: jellyfinApi.updatePlaylist,
updateRating: undefined, updateRating: undefined,
}, },
navidrome: { navidrome: {

View file

@ -61,6 +61,8 @@ import {
artistListSortMap, artistListSortMap,
sortOrderMap, sortOrderMap,
albumArtistListSortMap, albumArtistListSortMap,
UpdatePlaylistArgs,
UpdatePlaylistResponse,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { useAuthStore } from '/@/renderer/store'; import { useAuthStore } from '/@/renderer/store';
import { ServerListItem, ServerType } from '/@/renderer/types'; import { ServerListItem, ServerType } from '/@/renderer/types';
@ -312,9 +314,6 @@ const getSongList = async (args: SongListArgs): Promise<JFSongList> => {
} }
} }
console.log('yearsGroup :>> ', yearsGroup);
console.log('albumIds', query.albumIds);
console.log('artistIds :>> ', query.artistIds);
const yearsFilter = yearsGroup.length ? getCommaDelimitedString(yearsGroup) : undefined; const yearsFilter = yearsGroup.length ? getCommaDelimitedString(yearsGroup) : undefined;
const albumIdsFilter = query.albumIds ? getCommaDelimitedString(query.albumIds) : undefined; const albumIdsFilter = query.albumIds ? getCommaDelimitedString(query.albumIds) : undefined;
const artistIdsFilter = query.artistIds ? getCommaDelimitedString(query.artistIds) : undefined; const artistIdsFilter = query.artistIds ? getCommaDelimitedString(query.artistIds) : undefined;
@ -457,6 +456,33 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistR
}; };
}; };
const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistResponse> => {
const { query, body, server } = args;
const json = {
Genres: body.genres?.map((item) => ({ Id: item.id, Name: item.name })) || [],
MediaType: 'Audio',
Name: body.name,
Overview: body.comment || '',
PremiereDate: null,
ProviderIds: {},
Tags: [],
UserId: server?.userId, // Required
};
await api
.post(`items/${query.id}`, {
headers: { 'X-MediaBrowser-Token': server?.credential },
json,
prefixUrl: server?.url,
})
.json<null>();
return {
id: query.id,
};
};
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<null> => { const deletePlaylist = async (args: DeletePlaylistArgs): Promise<null> => {
const { query, server } = args; const { query, server } = args;
@ -728,10 +754,11 @@ const normalizePlaylist = (
return { return {
description: item.Overview || null, description: item.Overview || null,
duration: item.RunTimeTicks / 10000000, duration: item.RunTimeTicks / 10000,
genres: item.GenreItems?.map((entry) => ({ id: entry.Id, name: entry.Name })),
id: item.Id, id: item.Id,
imagePlaceholderUrl, imagePlaceholderUrl,
imageUrl, imageUrl: imageUrl || null,
name: item.Name, name: item.Name,
public: null, public: null,
rules: null, rules: null,
@ -809,6 +836,7 @@ export const jellyfinApi = {
getPlaylistList, getPlaylistList,
getPlaylistSongList, getPlaylistSongList,
getSongList, getSongList,
updatePlaylist,
}; };
export const jfNormalize = { export const jfNormalize = {

View file

@ -578,6 +578,7 @@ const normalizePlaylist = (
return { return {
description: item.comment, description: item.comment,
duration: item.duration * 1000, duration: item.duration * 1000,
genres: [],
id: item.id, id: item.id,
imagePlaceholderUrl, imagePlaceholderUrl,
imageUrl, imageUrl,

View file

@ -237,6 +237,7 @@ export type MusicFolder = {
export type Playlist = { export type Playlist = {
description: string | null; description: string | null;
duration: number | null; duration: number | null;
genres: Genre[];
id: string; id: string;
imagePlaceholderUrl: string | null; imagePlaceholderUrl: string | null;
imageUrl: string | null; imageUrl: string | null;
@ -758,6 +759,7 @@ export type UpdatePlaylistQuery = {
export type UpdatePlaylistBody = { export type UpdatePlaylistBody = {
comment?: string; comment?: string;
genres?: Genre[];
name: string; name: string;
public?: boolean; public?: boolean;
rules?: Record<string, any>; rules?: Record<string, any>;

View file

@ -1,10 +1,10 @@
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals'; import { closeAllModals, openModal } from '@mantine/modals';
import { forwardRef, Ref } from 'react'; import { forwardRef, Fragment, Ref } from 'react';
import { RiMoreFill } from 'react-icons/ri'; import { RiMoreFill } from 'react-icons/ri';
import { generatePath, useNavigate, useParams } from 'react-router'; import { generatePath, useNavigate, useParams } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { DropdownMenu, Button, ConfirmModal, toast } from '/@/renderer/components'; import { DropdownMenu, Button, ConfirmModal, toast, Text } from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { UpdatePlaylistForm } from './update-playlist-form'; import { UpdatePlaylistForm } from './update-playlist-form';
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
@ -13,6 +13,7 @@ import { LibraryHeader, PlayButton, PLAY_TYPES } from '/@/renderer/features/shar
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, Play } from '/@/renderer/types'; import { LibraryItem, Play } from '/@/renderer/types';
import { formatDurationString } from '/@/renderer/utils';
interface PlaylistDetailHeaderProps { interface PlaylistDetailHeaderProps {
background: string; background: string;
@ -47,6 +48,7 @@ export const PlaylistDetailHeader = forwardRef(
<UpdatePlaylistForm <UpdatePlaylistForm
body={{ body={{
comment: detailQuery?.data?.description || undefined, comment: detailQuery?.data?.description || undefined,
genres: detailQuery?.data?.genres,
name: detailQuery?.data?.name, name: detailQuery?.data?.name,
public: detailQuery?.data?.public || false, public: detailQuery?.data?.public || false,
rules: detailQuery?.data?.rules || undefined, rules: detailQuery?.data?.rules || undefined,
@ -97,6 +99,19 @@ export const PlaylistDetailHeader = forwardRef(
}); });
}; };
const metadataItems = [
{
id: 'songCount',
secondary: false,
value: `${detailQuery?.data?.songCount || 0} songs`,
},
{
id: 'duration',
secondary: true,
value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
},
];
return ( return (
<Stack> <Stack>
<LibraryHeader <LibraryHeader
@ -107,16 +122,17 @@ export const PlaylistDetailHeader = forwardRef(
item={{ route: AppRoute.PLAYLISTS, type: LibraryItem.PLAYLIST }} item={{ route: AppRoute.PLAYLISTS, type: LibraryItem.PLAYLIST }}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack mt="1rem">
<Group> <Group>
<Button {metadataItems.map((item, index) => (
compact <Fragment key={`item-${item.id}-${index}`}>
component={Link} {index > 0 && <Text $noSelect></Text>}
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })} <Text $secondary={item.secondary}>{item.value}</Text>
variant="subtle" </Fragment>
> ))}
View full playlist
</Button>
</Group> </Group>
<Text lineClamp={3}>{detailQuery?.data?.description}</Text>
</Stack>
</LibraryHeader> </LibraryHeader>
<Group <Group
maw="1920px" maw="1920px"
@ -150,6 +166,15 @@ export const PlaylistDetailHeader = forwardRef(
<DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item> <DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
<Button
compact
component={Link}
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
variant="subtle"
>
View full playlist
</Button>
</Group> </Group>
</Group> </Group>
</Stack> </Stack>