From 20830cb97916379e6d7b9aa0f35864002d08bdf8 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 3 Dec 2025 22:25:41 -0800 Subject: [PATCH] fix playlist song list context menu showing remove item --- .../item-list/helpers/item-list-controls.ts | 22 +++++--- .../actions/remove-from-playlist-action.tsx | 54 ++++++++++++------- .../menus/playlist-song-context-menu.tsx | 2 + 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/renderer/components/item-list/helpers/item-list-controls.ts b/src/renderer/components/item-list/helpers/item-list-controls.ts index fa18dc52f..42372ac68 100644 --- a/src/renderer/components/item-list/helpers/item-list-controls.ts +++ b/src/renderer/components/item-list/helpers/item-list-controls.ts @@ -295,10 +295,15 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs return; } - // Use the item's _itemType if available, otherwise fall back to the prop itemType - // This allows mixed lists (e.g., folders + songs) to show the correct context menu + console.log(item, itemType); + + // For context menus, prioritize the itemType prop when it's PLAYLIST_SONG or QUEUE_SONG + // This is because playlist/queue songs are Song objects (_itemType: SONG) but need special context menus + // Otherwise, use the item's _itemType if available, or fall back to the mapped itemType const actualItemType = - (item as any)?._itemType || itemTypeMapping[itemType] || itemType; + itemType === LibraryItem.PLAYLIST_SONG || itemType === LibraryItem.QUEUE_SONG + ? itemType + : (item as any)?._itemType || itemTypeMapping[itemType] || itemType; // If no internalState, call ContextMenuController directly if (!internalState) { @@ -331,11 +336,14 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs const selectedItems = internalState.getSelected(); - // For multiple selected items, use the itemType prop (assumes all selected items are of the same type) + // For multiple selected items, prioritize the itemType prop for PLAYLIST_SONG/QUEUE_SONG + // Otherwise use the first item's _itemType or the mapped type const selectedItemType = - selectedItems.length > 0 && (selectedItems[0] as any)?._itemType - ? (selectedItems[0] as any)._itemType - : actualItemType; + itemType === LibraryItem.PLAYLIST_SONG || itemType === LibraryItem.QUEUE_SONG + ? itemType + : selectedItems.length > 0 && (selectedItems[0] as any)?._itemType + ? (selectedItems[0] as any)._itemType + : itemTypeMapping[itemType] || itemType; return ContextMenuController.call({ cmd: { items: selectedItems as any[], type: selectedItemType as any }, diff --git a/src/renderer/features/context-menu/actions/remove-from-playlist-action.tsx b/src/renderer/features/context-menu/actions/remove-from-playlist-action.tsx index 17a3cb951..bd87e5f7f 100644 --- a/src/renderer/features/context-menu/actions/remove-from-playlist-action.tsx +++ b/src/renderer/features/context-menu/actions/remove-from-playlist-action.tsx @@ -1,3 +1,4 @@ +import { closeAllModals, openModal } from '@mantine/modals'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; @@ -5,6 +6,8 @@ import { useParams } from 'react-router'; import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation'; import { useCurrentServerId } from '/@/renderer/store'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; +import { ConfirmModal } from '/@/shared/components/modal/modal'; +import { Text } from '/@/shared/components/text/text'; import { toast } from '/@/shared/components/toast/toast'; import { Song } from '/@/shared/types/domain-types'; @@ -19,41 +22,52 @@ export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProp const removeFromPlaylistMutation = useRemoveFromPlaylist(); const { ids } = useMemo(() => { - const ids = items.map((item) => item.id); + const ids = items.map((item) => item.playlistItemId).filter((id) => id !== undefined); return { ids }; }, [items]); - const handleRemoveFromPlaylist = useCallback(() => { + const handleRemoveFromPlaylist = useCallback(async () => { if (ids.length === 0 || !serverId || !playlistId) return; - removeFromPlaylistMutation.mutate( - { + try { + await removeFromPlaylistMutation.mutateAsync({ apiClientProps: { serverId }, query: { id: playlistId, songId: ids, }, - }, - { - onError: (err) => { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - }, - onSuccess: () => { - toast.success({ - message: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }), - }); - }, - }, - ); + }); + + toast.success({ + message: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }), + }); + } catch (err: any) { + toast.error({ + message: err.message, + title: t('error.genericError', { postProcess: 'sentenceCase' }), + }); + } + + closeAllModals(); }, [ids, playlistId, removeFromPlaylistMutation, serverId, t]); + const openRemoveFromPlaylistModal = useCallback(() => { + if (ids.length === 0 || !playlistId) return; + + openModal({ + children: ( + + {t('common.areYouSure', { postProcess: 'sentenceCase' })} + + ), + title: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }), + }); + }, [handleRemoveFromPlaylist, ids, playlistId, t]); + if (ids.length === 0 || !playlistId) return null; return ( - + {t('action.removeFromPlaylist', { postProcess: 'sentenceCase' })} ); diff --git a/src/renderer/features/context-menu/menus/playlist-song-context-menu.tsx b/src/renderer/features/context-menu/menus/playlist-song-context-menu.tsx index 5fca1aa71..fea72c383 100644 --- a/src/renderer/features/context-menu/menus/playlist-song-context-menu.tsx +++ b/src/renderer/features/context-menu/menus/playlist-song-context-menu.tsx @@ -24,6 +24,8 @@ export const PlaylistSongContextMenu = ({ items, type }: PlaylistSongContextMenu return { ids }; }, [items]); + console.log('items', items, ids); + return ( }