fix(dnd): align drop flow with modal add; use same backend save and show success toast; improve payload parsing for drop targets

This commit is contained in:
Geert Rademakes 2025-08-08 13:17:15 +02:00
parent c7dd2e6d33
commit 8a9f51a0c6
2 changed files with 17 additions and 12 deletions

View File

@ -1,4 +1,4 @@
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, useToast } from "@chakra-ui/react";
import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon, DownloadIcon } from "@chakra-ui/icons"; import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon, DownloadIcon } from "@chakra-ui/icons";
import React, { useState, useRef, useEffect, useCallback } from "react"; import React, { useState, useRef, useEffect, useCallback } from "react";
import { useNavigate, useLocation, Routes, Route } from "react-router-dom"; import { useNavigate, useLocation, Routes, Route } from "react-router-dom";
@ -76,6 +76,7 @@ const RekordboxReader: React.FC = () => {
const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false); const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false);
const [isSwitchingPlaylist, setIsSwitchingPlaylist] = useState(false); const [isSwitchingPlaylist, setIsSwitchingPlaylist] = useState(false);
const { currentSong, playSong, closePlayer } = useMusicPlayer(); const { currentSong, playSong, closePlayer } = useMusicPlayer();
const toast = useToast();
// Memoized song selection handler to prevent unnecessary re-renders // Memoized song selection handler to prevent unnecessary re-renders
const handleSongSelect = useCallback((song: Song) => { const handleSongSelect = useCallback((song: Song) => {
@ -254,8 +255,10 @@ const RekordboxReader: React.FC = () => {
} }
return node; return node;
}); });
const savedPlaylists = await api.savePlaylists(updatedPlaylists); await api.savePlaylists(updatedPlaylists);
setPlaylists(savedPlaylists); // Always normalize state to structure for consistent counters
const structure = await api.getPlaylistStructure();
setPlaylists(structure);
}; };
// Handle drop from song list into playlist (with duplicate check and user choice) // Handle drop from song list into playlist (with duplicate check and user choice)
@ -286,11 +289,13 @@ const RekordboxReader: React.FC = () => {
const finalIds = proceedMode === 'skip' ? songIds.filter(id => !existing.has(id)) : songIds; const finalIds = proceedMode === 'skip' ? songIds.filter(id => !existing.has(id)) : songIds;
if (finalIds.length === 0) return; if (finalIds.length === 0) return;
await handleAddSongsToPlaylist(finalIds, playlistName); await handleAddSongsToPlaylist(finalIds, playlistName);
// If we were on that playlist, refresh counters by reloading structure toast({
try { title: 'Songs Added',
const updated = await api.getPlaylistStructure(); description: `${finalIds.length} song${finalIds.length === 1 ? '' : 's'} added to "${playlistName}"`,
setPlaylists(updated); status: 'success',
} catch {} duration: 3000,
isClosable: true,
});
}; };
const handleRemoveFromPlaylist = async (songIds: string[]) => { const handleRemoveFromPlaylist = async (songIds: string[]) => {

View File

@ -153,8 +153,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
onDragLeave={() => setIsDragOver(false)} onDragLeave={() => setIsDragOver(false)}
onDrop={(e) => { onDrop={(e) => {
try { try {
const data = e.dataTransfer.getData('application/json'); const json = e.dataTransfer.getData('application/json') || e.dataTransfer.getData('text/plain');
const parsed = JSON.parse(data); const parsed = JSON.parse(json);
if (parsed?.type === 'songs' && Array.isArray(parsed.songIds) && onDropSongs) { if (parsed?.type === 'songs' && Array.isArray(parsed.songIds) && onDropSongs) {
onDropSongs(node.name, parsed.songIds); onDropSongs(node.name, parsed.songIds);
} }
@ -233,8 +233,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = React.memo(({
onDragLeave={() => setIsDragOver(false)} onDragLeave={() => setIsDragOver(false)}
onDrop={(e) => { onDrop={(e) => {
try { try {
const data = e.dataTransfer.getData('application/json'); const json = e.dataTransfer.getData('application/json') || e.dataTransfer.getData('text/plain');
const parsed = JSON.parse(data); const parsed = JSON.parse(json);
if (parsed?.type === 'songs' && Array.isArray(parsed.songIds) && onDropSongs) { if (parsed?.type === 'songs' && Array.isArray(parsed.songIds) && onDropSongs) {
onDropSongs(node.name, parsed.songIds); onDropSongs(node.name, parsed.songIds);
} }