Add update/delete playlist forms

This commit is contained in:
jeffvli 2023-01-03 00:28:09 -08:00
parent 5dd65b18b7
commit 52163534db
5 changed files with 182 additions and 4 deletions

View file

@ -213,6 +213,10 @@ const updatePlaylist = async (args: UpdatePlaylistArgs) => {
return (apiController('updatePlaylist') as ControllerEndpoint['updatePlaylist'])?.(args);
};
const deletePlaylist = async (args: DeletePlaylistArgs) => {
return (apiController('deletePlaylist') as ControllerEndpoint['deletePlaylist'])?.(args);
};
const getPlaylistDetail = async (args: PlaylistDetailArgs) => {
return (apiController('getPlaylistDetail') as ControllerEndpoint['getPlaylistDetail'])?.(args);
};
@ -225,6 +229,7 @@ const getPlaylistSongList = async (args: PlaylistSongListArgs) => {
export const controller = {
createPlaylist,
deletePlaylist,
getAlbumArtistList,
getAlbumDetail,
getAlbumList,

View file

@ -325,7 +325,7 @@ const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistR
};
const data = await api
.post(`api/playlist/${query.id}`, {
.put(`api/playlist/${query.id}`, {
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
json,
prefixUrl: server?.url,
@ -591,6 +591,7 @@ const normalizePlaylist = (
});
return {
description: item.comment,
duration: item.duration * 1000,
id: item.id,
imagePlaceholderUrl,

View file

@ -1,10 +1,13 @@
import { Group, Stack } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals';
import { forwardRef, Ref } from 'react';
import { RiMoreFill } from 'react-icons/ri';
import { generatePath, useParams } from 'react-router';
import { generatePath, useNavigate, useParams } from 'react-router';
import { Link } from 'react-router-dom';
import { DropdownMenu, Button } from '/@/renderer/components';
import { DropdownMenu, Button, ConfirmModal, toast } from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { UpdatePlaylistForm } from './update-playlist-form';
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
import { LibraryHeader, PlayButton, PLAY_TYPES } from '/@/renderer/features/shared';
import { AppRoute } from '/@/renderer/router/routes';
@ -22,6 +25,7 @@ export const PlaylistDetailHeader = forwardRef(
{ background, imageUrl, imagePlaceholderUrl }: PlaylistDetailHeaderProps,
ref: Ref<HTMLDivElement>,
) => {
const navigate = useNavigate();
const { playlistId } = useParams() as { playlistId: string };
const detailQuery = usePlaylistDetail({ id: playlistId });
const handlePlayQueueAdd = usePlayQueueAdd();
@ -37,6 +41,62 @@ export const PlaylistDetailHeader = forwardRef(
});
};
const openUpdatePlaylistModal = () => {
openModal({
children: (
<UpdatePlaylistForm
body={{
comment: detailQuery?.data?.description || undefined,
name: detailQuery?.data?.name,
public: detailQuery?.data?.public || false,
rules: detailQuery?.data?.rules || undefined,
}}
query={{ id: playlistId }}
onCancel={closeAllModals}
/>
),
title: 'Edit playlist',
});
};
const deletePlaylistMutation = useDeletePlaylist();
const handleDeletePlaylist = () => {
deletePlaylistMutation.mutate(
{ query: { id: playlistId } },
{
onError: (err) => {
toast.error({
message: err.message,
title: 'Error deleting playlist',
});
},
onSuccess: () => {
toast.success({
message: `${detailQuery?.data?.name} was successfully deleted`,
title: 'Playlist deleted',
});
closeAllModals();
navigate(AppRoute.PLAYLISTS);
},
},
);
};
const openDeletePlaylist = () => {
openModal({
children: (
<ConfirmModal
loading={deletePlaylistMutation.isLoading}
onConfirm={handleDeletePlaylist}
>
Are you sure you want to delete this playlist?
</ConfirmModal>
),
title: 'Delete playlist',
});
};
return (
<Stack>
<LibraryHeader
@ -84,7 +144,10 @@ export const PlaylistDetailHeader = forwardRef(
</DropdownMenu.Item>
))}
<DropdownMenu.Divider />
<DropdownMenu.Item disabled>Edit playlist</DropdownMenu.Item>
<DropdownMenu.Item onClick={openUpdatePlaylistModal}>
Edit playlist
</DropdownMenu.Item>
<DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>
</Group>

View file

@ -0,0 +1,84 @@
import { Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form';
import { ServerType, UpdatePlaylistBody, UpdatePlaylistQuery } from '/@/renderer/api/types';
import { Button, Switch, TextInput, toast } from '/@/renderer/components';
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
import { useCurrentServer } from '/@/renderer/store';
interface CreatePlaylistFormProps {
body: Partial<UpdatePlaylistBody>;
onCancel: () => void;
query: UpdatePlaylistQuery;
}
export const UpdatePlaylistForm = ({ query, body, onCancel }: CreatePlaylistFormProps) => {
const mutation = useUpdatePlaylist();
const server = useCurrentServer();
const form = useForm<UpdatePlaylistBody>({
initialValues: {
comment: '',
name: '',
public: false,
rules: undefined,
...body,
},
});
const handleSubmit = form.onSubmit((values) => {
mutation.mutate(
{ body: values, query },
{
onError: (err) => {
toast.error({ message: err.message, title: 'Error updating playlist' });
},
onSuccess: () => {
toast.success({ message: 'Playlist updated successfully' });
onCancel();
},
},
);
});
const isPublicDisplayed = server?.type === ServerType.NAVIDROME;
const isSubmitDisabled = !form.values.name || mutation.isLoading;
return (
<form onSubmit={handleSubmit}>
<Stack>
<TextInput
data-autofocus
required
label="Name"
{...form.getInputProps('name')}
/>
<TextInput
label="Description"
{...form.getInputProps('comment')}
/>
{isPublicDisplayed && (
<Switch
label="Is Public?"
{...form.getInputProps('public')}
/>
)}
<Group position="right">
<Button
variant="subtle"
onClick={onCancel}
>
Cancel
</Button>
<Button
disabled={isSubmitDisabled}
loading={mutation.isLoading}
type="submit"
variant="filled"
>
Save
</Button>
</Group>
</Stack>
</form>
);
};

View file

@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { HTTPError } from 'ky';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { DeletePlaylistArgs, RawDeletePlaylistResponse } from '/@/renderer/api/types';
import { MutationOptions } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store';
export const useDeletePlaylist = (options?: MutationOptions) => {
const queryClient = useQueryClient();
const server = useCurrentServer();
return useMutation<
RawDeletePlaylistResponse,
HTTPError,
Omit<DeletePlaylistArgs, 'server'>,
null
>({
mutationFn: (args) => api.controller.deletePlaylist({ ...args, server }),
onSuccess: () => {
queryClient.invalidateQueries(queryKeys.playlists.list(server?.id || ''));
},
...options,
});
};