diff --git a/packages/frontend/src/components/PaginatedSongList.tsx b/packages/frontend/src/components/PaginatedSongList.tsx index 958c93e..5b7a1d1 100644 --- a/packages/frontend/src/components/PaginatedSongList.tsx +++ b/packages/frontend/src/components/PaginatedSongList.tsx @@ -12,8 +12,13 @@ import { Input, InputGroup, InputLeftElement, + Menu, + MenuButton, + MenuList, + MenuItem, + MenuDivider, } from '@chakra-ui/react'; -import { Search2Icon } from '@chakra-ui/icons'; +import { Search2Icon, ChevronDownIcon } from '@chakra-ui/icons'; import { FiPlay } from 'react-icons/fi'; import type { Song, PlaylistNode } from '../types/interfaces'; import { api } from '../services/api'; @@ -131,7 +136,7 @@ const SongItem = memo<{ - {formattedDuration} + {formattedDuration}{song.tonality ? ` - ${song.tonality}` : ''} {song.averageBpm} BPM @@ -202,6 +207,13 @@ export const PaginatedSongList: React.FC = memo(({ loadingRef_state.current = loading; onLoadMoreRef.current = onLoadMore; }, [hasMore, loading, onLoadMore]); + + // Clear selection when switching playlists + useEffect(() => { + if (isSwitchingPlaylist) { + setSelectedSongs(new Set()); + } + }, [isSwitchingPlaylist]); // Debounce search to prevent excessive API calls const debouncedSearchQuery = useDebounce(localSearchQuery, 300); @@ -522,27 +534,32 @@ export const PaginatedSongList: React.FC = memo(({ {selectedSongs.size > 0 && ( - - - {currentPlaylist !== "All Songs" && onRemoveFromPlaylist && ( - - )} - + Actions + + + + Add to Playlist... + + {currentPlaylist !== "All Songs" && onRemoveFromPlaylist && ( + <> + + + Remove from {currentPlaylist} + + + )} + + )} diff --git a/packages/frontend/src/components/SongList.tsx b/packages/frontend/src/components/SongList.tsx index 0b8171f..e18a710 100644 --- a/packages/frontend/src/components/SongList.tsx +++ b/packages/frontend/src/components/SongList.tsx @@ -19,7 +19,7 @@ import { Input, InputGroup, InputLeftElement } from "@chakra-ui/input"; import { Search2Icon, ChevronDownIcon } from "@chakra-ui/icons"; import { FiPlay, FiMusic } from 'react-icons/fi'; import type { Song, PlaylistNode } from "../types/interfaces"; -import { useState, useCallback, useMemo } from "react"; +import { useState, useCallback, useMemo, useEffect } from "react"; import { formatDuration, formatTotalDuration } from '../utils/formatters'; @@ -33,6 +33,7 @@ interface SongListProps { currentPlaylist: string | null; depth?: number; onPlaySong?: (song: Song) => void; + isSwitchingPlaylist?: boolean; } export const SongList: React.FC = ({ @@ -44,11 +45,19 @@ export const SongList: React.FC = ({ selectedSongId, currentPlaylist, depth = 0, - onPlaySong + onPlaySong, + isSwitchingPlaylist = false }) => { const [selectedSongs, setSelectedSongs] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(""); + // Clear selection when switching playlists + useEffect(() => { + if (isSwitchingPlaylist) { + setSelectedSongs(new Set()); + } + }, [isSwitchingPlaylist]); + // Helper function to get all playlists (excluding folders) from the playlist tree const getAllPlaylists = useCallback((nodes: PlaylistNode[]): PlaylistNode[] => { let result: PlaylistNode[] = []; @@ -270,7 +279,7 @@ export const SongList: React.FC = ({ fontSize={depth > 0 ? "xs" : "sm"} color={selectedSongId === song.id ? "gray.300" : "gray.500"} > - {song.artist} • {formatDuration(song.totalTime)} + {song.artist} • {formatDuration(song.totalTime)}{song.tonality ? ` - ${song.tonality}` : ''} {song.location && ( - Sync and Matching + Sync and Matching ({storageProvider})