Compare commits

..

No commits in common. "770c6065619478b60658a7103b7e39f1093abddf" and "e8bb2a4326b1542eb1eb75ae2dd7277afef89cb2" have entirely different histories.

4 changed files with 39 additions and 79 deletions

View File

@ -1,6 +1,6 @@
import { Box, Button, Flex, Heading, Spinner, Text, useBreakpointValue, IconButton, Drawer, DrawerBody, DrawerHeader, DrawerOverlay, DrawerContent, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, VStack } from "@chakra-ui/react"; import { Box, Button, Flex, Heading, Spinner, Text, useBreakpointValue, IconButton, Drawer, DrawerBody, DrawerHeader, DrawerOverlay, DrawerContent, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, VStack } from "@chakra-ui/react";
import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon } from "@chakra-ui/icons"; import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon } from "@chakra-ui/icons";
import React, { useState, useRef, useEffect, useCallback } from "react"; import { useState, useRef, useEffect, useCallback } from "react";
import { useNavigate, useLocation, Routes, Route } from "react-router-dom"; import { useNavigate, useLocation, Routes, Route } from "react-router-dom";
import { PaginatedSongList } from "./components/PaginatedSongList"; import { PaginatedSongList } from "./components/PaginatedSongList";
import { PlaylistManager } from "./components/PlaylistManager"; import { PlaylistManager } from "./components/PlaylistManager";
@ -154,15 +154,13 @@ export default function RekordboxReader() {
}, [currentPlaylist, playlists, navigate, xmlLoading]); }, [currentPlaylist, playlists, navigate, xmlLoading]);
const handlePlaylistSelect = (name: string) => { const handlePlaylistSelect = (name: string) => {
// Clear selected song immediately to prevent stale state setSelectedSong(null); // Clear selected song when changing playlists
setSelectedSong(null);
// Navigate immediately without any delays
if (name === "All Songs") { if (name === "All Songs") {
navigate("/", { replace: true }); navigate("/");
} else { } else {
// Use encodeURIComponent to properly handle spaces and special characters
const encodedName = encodeURIComponent(name); const encodedName = encodeURIComponent(name);
navigate(`/playlists/${encodedName}`, { replace: true }); navigate(`/playlists/${encodedName}`);
} }
}; };

View File

@ -50,8 +50,6 @@ const SongItem = memo<{
onToggleSelection: (songId: string) => void; onToggleSelection: (songId: string) => void;
showCheckbox: boolean; showCheckbox: boolean;
}>(({ song, isSelected, isHighlighted, onSelect, onToggleSelection, showCheckbox }) => { }>(({ song, isSelected, isHighlighted, onSelect, onToggleSelection, showCheckbox }) => {
// Memoize the formatted duration to prevent recalculation
const formattedDuration = useMemo(() => formatDuration(song.totalTime || ''), [song.totalTime]);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
onSelect(song); onSelect(song);
}, [onSelect, song]); }, [onSelect, song]);
@ -91,7 +89,7 @@ const SongItem = memo<{
</Box> </Box>
<Box textAlign="right" ml={2}> <Box textAlign="right" ml={2}>
<Text fontSize="xs" color="gray.500"> <Text fontSize="xs" color="gray.500">
{formattedDuration} {formatDuration(song.totalTime || '')}
</Text> </Text>
<Text fontSize="xs" color="gray.600"> <Text fontSize="xs" color="gray.600">
{song.averageBpm} BPM {song.averageBpm} BPM
@ -215,7 +213,7 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
showCheckbox={selectedSongs.size > 0 || depth === 0} showCheckbox={selectedSongs.size > 0 || depth === 0}
/> />
)); ));
}, [songs, selectedSongs, selectedSongId, toggleSelection, depth]); // Removed handleSongSelect since it's already memoized }, [songs, selectedSongs, selectedSongId, handleSongSelect, toggleSelection, depth]);
// Use total playlist duration if available, otherwise calculate from current songs // Use total playlist duration if available, otherwise calculate from current songs
const totalDuration = useMemo(() => { const totalDuration = useMemo(() => {
@ -259,7 +257,7 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
// Reset the flag after a short delay to prevent multiple triggers // Reset the flag after a short delay to prevent multiple triggers
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
isTriggeringRef.current = false; isTriggeringRef.current = false;
}, 100); }, 1000);
} }
}, },
{ {

View File

@ -36,50 +36,27 @@ interface PlaylistManagerProps {
onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void; onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void;
} }
// Memoized button styles to prevent unnecessary re-renders const getButtonStyles = (isSelected: boolean) => ({
const selectedButtonStyles = {
width: "100%", width: "100%",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
bg: "blue.800", bg: isSelected ? "blue.800" : "transparent",
color: "white", color: isSelected ? "white" : "gray.100",
fontWeight: "600", fontWeight: isSelected ? "600" : "normal",
borderRadius: "md", borderRadius: "md",
px: 4, px: 4,
py: 2, py: 2,
cursor: "pointer", cursor: "pointer",
transition: "all 0.2s", transition: "all 0.2s",
_hover: { _hover: {
bg: "blue.600", bg: isSelected ? "blue.600" : "whiteAlpha.200",
transform: "translateX(2px)", transform: "translateX(2px)",
}, },
_active: { _active: {
bg: "blue.700", bg: isSelected ? "blue.700" : "whiteAlpha.300",
}, },
}; });
const unselectedButtonStyles = {
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
bg: "transparent",
color: "gray.100",
fontWeight: "normal",
borderRadius: "md",
px: 4,
py: 2,
cursor: "pointer",
transition: "all 0.2s",
_hover: {
bg: "whiteAlpha.200",
transform: "translateX(2px)",
},
_active: {
bg: "whiteAlpha.300",
},
};
interface PlaylistItemProps { interface PlaylistItemProps {
node: PlaylistNode; node: PlaylistNode;
@ -91,7 +68,7 @@ interface PlaylistItemProps {
allFolders: { name: string }[]; allFolders: { name: string }[];
} }
const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({ const PlaylistItem: React.FC<PlaylistItemProps> = ({
node, node,
level, level,
selectedItem, selectedItem,
@ -101,15 +78,6 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
allFolders, allFolders,
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
// Memoize click handlers to prevent recreation
const handlePlaylistClick = useCallback(() => {
onPlaylistSelect(node.name);
}, [onPlaylistSelect, node.name]);
const handleFolderToggle = useCallback(() => {
setIsOpen(prev => !prev);
}, []);
const indent = level * 10; // Reverted back to 10px per level const indent = level * 10; // Reverted back to 10px per level
if (node.type === 'folder') { if (node.type === 'folder') {
@ -118,8 +86,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
<Flex align="center" gap={1}> <Flex align="center" gap={1}>
<Button <Button
flex={1} flex={1}
{...unselectedButtonStyles} {...getButtonStyles(false)}
onClick={handleFolderToggle} onClick={() => setIsOpen(!isOpen)}
ml={indent} ml={indent}
pl={level > 0 ? 6 : 4} // Add extra padding for nested items pl={level > 0 ? 6 : 4} // Add extra padding for nested items
justifyContent="flex-start" justifyContent="flex-start"
@ -186,8 +154,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
<Flex align="center" gap={0}> <Flex align="center" gap={0}>
<Button <Button
flex="1 1 auto" flex="1 1 auto"
{...(selectedItem === node.name ? selectedButtonStyles : unselectedButtonStyles)} {...getButtonStyles(selectedItem === node.name)}
onClick={handlePlaylistClick} onClick={() => onPlaylistSelect(node.name)}
ml={indent} ml={indent}
pl={level > 0 ? 6 : 4} // Add extra padding for nested items pl={level > 0 ? 6 : 4} // Add extra padding for nested items
borderRightRadius={0} borderRightRadius={0}
@ -269,7 +237,7 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
</Menu> </Menu>
</Flex> </Flex>
); );
}); };
export const PlaylistManager: React.FC<PlaylistManagerProps> = ({ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
playlists, playlists,
@ -321,7 +289,7 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
<Box> <Box>
<VStack spacing={2} align="stretch" mb={4}> <VStack spacing={2} align="stretch" mb={4}>
<Button <Button
{...(selectedItem === null ? selectedButtonStyles : unselectedButtonStyles)} {...getButtonStyles(selectedItem === null)}
onClick={() => onPlaylistSelect(null)} onClick={() => onPlaylistSelect(null)}
> >
All Songs All Songs

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { api, type SongsResponse } from '../services/api'; import { api, type SongsResponse } from '../services/api';
import type { Song } from '../types/interfaces'; import type { Song } from '../types/interfaces';
@ -125,12 +125,9 @@ export const usePaginatedSongs = (options: UsePaginatedSongsOptions = {}) => {
setIsInitialLoad(true); setIsInitialLoad(true);
}, [initialSearch, cleanup]); }, [initialSearch, cleanup]);
// Initial load - only run once when the hook is first created // Initial load
useEffect(() => { useEffect(() => {
// Only load if we haven't loaded anything yet
if (songs.length === 0 && !loading) {
loadPage(1); loadPage(1);
}
// Cleanup on unmount // Cleanup on unmount
return () => { return () => {
@ -138,27 +135,26 @@ export const usePaginatedSongs = (options: UsePaginatedSongsOptions = {}) => {
}; };
}, []); }, []);
// Handle playlist changes - optimized for immediate response // Handle playlist changes
useEffect(() => { useEffect(() => {
if (previousPlaylistRef.current !== playlistName) { if (!isInitialLoad && previousPlaylistRef.current !== playlistName) {
// Update refs immediately // Update refs
currentPlaylistRef.current = playlistName; currentPlaylistRef.current = playlistName;
currentSearchQueryRef.current = searchQuery; currentSearchQueryRef.current = searchQuery;
previousPlaylistRef.current = playlistName; previousPlaylistRef.current = playlistName;
// Batch all state updates together to reduce re-renders // Clear songs for new playlist to replace them
React.startTransition(() => {
setSongs([]); setSongs([]);
setHasMore(true); setHasMore(true);
setCurrentPage(1); setCurrentPage(1);
setSearchQuery(initialSearch); setSearchQuery(initialSearch);
setError(null); setError(null);
}); // Use setTimeout to avoid the dependency issue
setTimeout(() => {
// Load immediately
loadPage(1, initialSearch, playlistName); loadPage(1, initialSearch, playlistName);
}, 0);
} }
}, [playlistName, initialSearch, loadPage]); }, [playlistName, isInitialLoad, initialSearch, loadPage]);
return { return {
songs, songs,