diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index 7004eeb5..7a8bca07 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -195,7 +195,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const handleDeletePlaylist = useCallback(() => { for (const item of ctx.data) { deletePlaylistMutation?.mutate( - { query: { id: item.id } }, + { query: { id: item.id }, serverId: item.serverId }, { onError: (err) => { toast.error({ @@ -432,6 +432,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { id: ctx.context.playlistId, songId, }, + serverId: ctx.data?.[0]?.serverId, }, { onError: (err) => { @@ -465,6 +466,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { }, [ ctx.context?.playlistId, ctx.context?.tableRef, + ctx.data, ctx.dataNodes, removeFromPlaylistMutation, serverType, diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index 27a8288b..453ff0f6 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -213,7 +213,11 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data) return; handlePlayQueueAdd?.({ - byData: [e.data], + byItemType: { + id: [playlistId], + type: LibraryItem.PLAYLIST, + }, + initialSongId: e.data.id, playType: playButtonBehavior, }); }; diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx index f00a6739..68796259 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx @@ -2,6 +2,7 @@ import { useCallback, ChangeEvent, MutableRefObject, MouseEvent } from 'react'; import { IDatasource } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { Flex, Group, Stack } from '@mantine/core'; +import { closeAllModals, openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; import { RiSortAsc, @@ -18,7 +19,16 @@ import { import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; 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 { useContainerQuery } from '/@/renderer/hooks'; import { @@ -34,6 +44,8 @@ import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/type import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; import { useParams } from 'react-router'; 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 = { 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: ( + + Are you sure you want to delete this playlist? + + ), + title: 'Delete playlist(s)', + }); + }; + return ( } - onClick={() => handlePlay(Play.LAST)} + onClick={() => + openUpdatePlaylistModal({ + playlist: detailQuery.data!, + server: server!, + }) + } > Edit playlist } - onClick={() => handlePlay(Play.LAST)} + onClick={openDeletePlaylistModal} > Delete playlist diff --git a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx index aa1f44cf..33d31d7d 100644 --- a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx @@ -184,6 +184,10 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter setTable({ rowHeight: e }); }; + const handleRefresh = () => { + tableRef?.current?.api?.purgeInfiniteCache(); + }; + return ( - }>Refresh + } + onClick={handleRefresh} + > + Refresh + 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 81b3c909..4e6a892d 100644 --- a/src/renderer/features/playlists/components/save-as-playlist-form.tsx +++ b/src/renderer/features/playlists/components/save-as-playlist-form.tsx @@ -9,9 +9,15 @@ interface SaveAsPlaylistFormProps { body: Partial; onCancel: () => 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 server = useCurrentServer(); @@ -31,7 +37,7 @@ export const SaveAsPlaylistForm = ({ body, onSuccess, onCancel }: SaveAsPlaylist const handleSubmit = form.onSubmit((values) => { mutation.mutate( - { body: values }, + { body: values, serverId }, { onError: (err) => { toast.error({ message: err.message, title: 'Error creating playlist' }); diff --git a/src/renderer/features/playlists/components/update-playlist-form.tsx b/src/renderer/features/playlists/components/update-playlist-form.tsx index deaefd16..ac978ba7 100644 --- a/src/renderer/features/playlists/components/update-playlist-form.tsx +++ b/src/renderer/features/playlists/components/update-playlist-form.tsx @@ -1,8 +1,22 @@ import { Group, Stack } from '@mantine/core'; 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 { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation'; +import { queryClient } from '/@/renderer/lib/react-query'; import { useCurrentServer } from '/@/renderer/store'; interface UpdatePlaylistFormProps { @@ -103,3 +117,49 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl ); }; + +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: ( + + ), + title: 'Edit playlist', + }); +}; diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx index 4fa81d6e..2153e74e 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx @@ -57,6 +57,7 @@ const PlaylistDetailSongListRoute = () => { comment: detailQuery?.data?.description || '', name: detailQuery?.data?.name, }, + serverId: detailQuery?.data?.serverId, }, { onSuccess: (data) => { @@ -64,13 +65,19 @@ const PlaylistDetailSongListRoute = () => { navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }), { replace: true, }); - deletePlaylistMutation.mutate({ query: { id: playlistId } }); + deletePlaylistMutation.mutate({ + query: { id: playlistId }, + serverId: detailQuery?.data?.serverId, + }); }, }, ); }; - const handleSaveAs = (filter: Record) => { + const handleSaveAs = ( + filter: Record, + extraFilters: { limit?: number; sortBy?: string; sortOrder?: string }, + ) => { openModal({ children: ( { public: detailQuery?.data?.public || false, rules: { ...filter, - order: 'desc', - sort: 'year', + limit: extraFilters.limit || undefined, + order: extraFilters.sortOrder || 'desc', + sort: extraFilters.sortBy || 'dateAdded', }, sync: detailQuery?.data?.sync || false, }, @@ -91,6 +99,7 @@ const PlaylistDetailSongListRoute = () => { comment: detailQuery?.data?.description || '', name: detailQuery?.data?.name, }} + serverId={detailQuery?.data?.serverId} onCancel={closeAllModals} onSuccess={(data) => navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }))