Fix all playlist actions

This commit is contained in:
jeffvli 2023-05-21 18:20:46 -07:00
parent 07d4dc37b5
commit 3c62de8347
7 changed files with 154 additions and 14 deletions

View file

@ -195,7 +195,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const handleDeletePlaylist = useCallback(() => { const handleDeletePlaylist = useCallback(() => {
for (const item of ctx.data) { for (const item of ctx.data) {
deletePlaylistMutation?.mutate( deletePlaylistMutation?.mutate(
{ query: { id: item.id } }, { query: { id: item.id }, serverId: item.serverId },
{ {
onError: (err) => { onError: (err) => {
toast.error({ toast.error({
@ -432,6 +432,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
id: ctx.context.playlistId, id: ctx.context.playlistId,
songId, songId,
}, },
serverId: ctx.data?.[0]?.serverId,
}, },
{ {
onError: (err) => { onError: (err) => {
@ -465,6 +466,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
}, [ }, [
ctx.context?.playlistId, ctx.context?.playlistId,
ctx.context?.tableRef, ctx.context?.tableRef,
ctx.data,
ctx.dataNodes, ctx.dataNodes,
removeFromPlaylistMutation, removeFromPlaylistMutation,
serverType, serverType,

View file

@ -213,7 +213,11 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => { const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
if (!e.data) return; if (!e.data) return;
handlePlayQueueAdd?.({ handlePlayQueueAdd?.({
byData: [e.data], byItemType: {
id: [playlistId],
type: LibraryItem.PLAYLIST,
},
initialSongId: e.data.id,
playType: playButtonBehavior, playType: playButtonBehavior,
}); });
}; };

View file

@ -2,6 +2,7 @@ import { useCallback, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
import { IDatasource } from '@ag-grid-community/core'; import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core'; import { Flex, Group, Stack } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { import {
RiSortAsc, RiSortAsc,
@ -18,7 +19,16 @@ import {
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { LibraryItem, PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types'; import { LibraryItem, PlaylistSongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
import { DropdownMenu, Button, Slider, MultiSelect, Switch, Text } from '/@/renderer/components'; import {
DropdownMenu,
Button,
Slider,
MultiSelect,
Switch,
Text,
ConfirmModal,
toast,
} from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { import {
@ -34,6 +44,8 @@ import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/type
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
@ -238,6 +250,40 @@ export const PlaylistDetailSongListHeaderFilters = ({
}); });
}; };
const deletePlaylistMutation = useDeletePlaylist({});
const handleDeletePlaylist = useCallback(() => {
if (!detailQuery.data) return;
deletePlaylistMutation?.mutate(
{ query: { id: detailQuery.data.id }, serverId: detailQuery.data.id },
{
onError: (err) => {
toast.error({
message: err.message,
title: 'Error deleting playlist',
});
},
onSuccess: () => {
toast.success({
message: `Playlist has been deleted`,
});
},
},
);
closeAllModals();
}, [deletePlaylistMutation, detailQuery.data]);
const openDeletePlaylistModal = () => {
openModal({
children: (
<ConfirmModal onConfirm={handleDeletePlaylist}>
<Text>Are you sure you want to delete this playlist?</Text>
</ConfirmModal>
),
title: 'Delete playlist(s)',
});
};
return ( return (
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
@ -320,16 +366,20 @@ export const PlaylistDetailSongListHeaderFilters = ({
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Item <DropdownMenu.Item
disabled
icon={<RiEditFill />} icon={<RiEditFill />}
onClick={() => handlePlay(Play.LAST)} onClick={() =>
openUpdatePlaylistModal({
playlist: detailQuery.data!,
server: server!,
})
}
> >
Edit playlist Edit playlist
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
disabled disabled
icon={<RiDeleteBinFill />} icon={<RiDeleteBinFill />}
onClick={() => handlePlay(Play.LAST)} onClick={openDeletePlaylistModal}
> >
Delete playlist Delete playlist
</DropdownMenu.Item> </DropdownMenu.Item>

View file

@ -184,6 +184,10 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
setTable({ rowHeight: e }); setTable({ rowHeight: e });
}; };
const handleRefresh = () => {
tableRef?.current?.api?.purgeInfiniteCache();
};
return ( return (
<Flex justify="space-between"> <Flex justify="space-between">
<Group <Group
@ -246,7 +250,12 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Item icon={<RiRefreshLine />}>Refresh</DropdownMenu.Item> <DropdownMenu.Item
icon={<RiRefreshLine />}
onClick={handleRefresh}
>
Refresh
</DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
</Group> </Group>

View file

@ -9,9 +9,15 @@ interface SaveAsPlaylistFormProps {
body: Partial<CreatePlaylistBody>; body: Partial<CreatePlaylistBody>;
onCancel: () => void; onCancel: () => void;
onSuccess: (data: CreatePlaylistResponse) => void; onSuccess: (data: CreatePlaylistResponse) => void;
serverId: string | undefined;
} }
export const SaveAsPlaylistForm = ({ body, onSuccess, onCancel }: SaveAsPlaylistFormProps) => { export const SaveAsPlaylistForm = ({
body,
serverId,
onSuccess,
onCancel,
}: SaveAsPlaylistFormProps) => {
const mutation = useCreatePlaylist({}); const mutation = useCreatePlaylist({});
const server = useCurrentServer(); const server = useCurrentServer();
@ -31,7 +37,7 @@ export const SaveAsPlaylistForm = ({ body, onSuccess, onCancel }: SaveAsPlaylist
const handleSubmit = form.onSubmit((values) => { const handleSubmit = form.onSubmit((values) => {
mutation.mutate( mutation.mutate(
{ body: values }, { body: values, serverId },
{ {
onError: (err) => { onError: (err) => {
toast.error({ message: err.message, title: 'Error creating playlist' }); toast.error({ message: err.message, title: 'Error creating playlist' });

View file

@ -1,8 +1,22 @@
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { ServerType, UpdatePlaylistBody, UpdatePlaylistQuery, User } from '/@/renderer/api/types'; import { openModal, closeAllModals } from '@mantine/modals';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
PlaylistDetailResponse,
ServerListItem,
ServerType,
SortOrder,
UpdatePlaylistBody,
UpdatePlaylistQuery,
User,
UserListQuery,
UserListSort,
} from '/@/renderer/api/types';
import { Button, Select, Switch, TextInput, toast } from '/@/renderer/components'; import { Button, Select, Switch, TextInput, toast } from '/@/renderer/components';
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation'; import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
import { queryClient } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
interface UpdatePlaylistFormProps { interface UpdatePlaylistFormProps {
@ -103,3 +117,49 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
</form> </form>
); );
}; };
export const openUpdatePlaylistModal = async (args: {
playlist: PlaylistDetailResponse;
server: ServerListItem;
}) => {
const { playlist, server } = args;
const query: UserListQuery = {
sortBy: UserListSort.NAME,
sortOrder: SortOrder.ASC,
startIndex: 0,
};
if (!server) return;
const users = await queryClient.fetchQuery({
queryFn: ({ signal }) =>
api.controller.getUserList({ apiClientProps: { server, signal }, query }),
queryKey: queryKeys.users.list(server?.id || '', query),
});
openModal({
children: (
<UpdatePlaylistForm
body={{
_custom: {
navidrome: {
owner: playlist?.owner || undefined,
ownerId: playlist?.ownerId || undefined,
public: playlist?.public || false,
rules: playlist?.rules || undefined,
sync: playlist?.sync || undefined,
},
},
comment: playlist?.description || undefined,
genres: playlist?.genres,
name: playlist?.name,
}}
query={{ id: playlist?.id }}
users={users?.items}
onCancel={closeAllModals}
/>
),
title: 'Edit playlist',
});
};

View file

@ -57,6 +57,7 @@ const PlaylistDetailSongListRoute = () => {
comment: detailQuery?.data?.description || '', comment: detailQuery?.data?.description || '',
name: detailQuery?.data?.name, name: detailQuery?.data?.name,
}, },
serverId: detailQuery?.data?.serverId,
}, },
{ {
onSuccess: (data) => { onSuccess: (data) => {
@ -64,13 +65,19 @@ const PlaylistDetailSongListRoute = () => {
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }), { navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }), {
replace: true, replace: true,
}); });
deletePlaylistMutation.mutate({ query: { id: playlistId } }); deletePlaylistMutation.mutate({
query: { id: playlistId },
serverId: detailQuery?.data?.serverId,
});
}, },
}, },
); );
}; };
const handleSaveAs = (filter: Record<string, any>) => { const handleSaveAs = (
filter: Record<string, any>,
extraFilters: { limit?: number; sortBy?: string; sortOrder?: string },
) => {
openModal({ openModal({
children: ( children: (
<SaveAsPlaylistForm <SaveAsPlaylistForm
@ -82,8 +89,9 @@ const PlaylistDetailSongListRoute = () => {
public: detailQuery?.data?.public || false, public: detailQuery?.data?.public || false,
rules: { rules: {
...filter, ...filter,
order: 'desc', limit: extraFilters.limit || undefined,
sort: 'year', order: extraFilters.sortOrder || 'desc',
sort: extraFilters.sortBy || 'dateAdded',
}, },
sync: detailQuery?.data?.sync || false, sync: detailQuery?.data?.sync || false,
}, },
@ -91,6 +99,7 @@ const PlaylistDetailSongListRoute = () => {
comment: detailQuery?.data?.description || '', comment: detailQuery?.data?.description || '',
name: detailQuery?.data?.name, name: detailQuery?.data?.name,
}} }}
serverId={detailQuery?.data?.serverId}
onCancel={closeAllModals} onCancel={closeAllModals}
onSuccess={(data) => onSuccess={(data) =>
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' })) navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }))