fix(reorder): backend move endpoint and frontend use precise move to persist order reliably; refresh after move
This commit is contained in:
parent
61d4ca16de
commit
8136bbb959
@ -125,4 +125,61 @@ router.post('/reorder', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Move a single track before another (stable, full-playlist context)
|
||||||
|
router.post('/reorder-move', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { playlistName, fromId, toId } = req.body as { playlistName: string; fromId: string; toId?: string };
|
||||||
|
if (!playlistName || !fromId) {
|
||||||
|
return res.status(400).json({ message: 'playlistName and fromId are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlists = await Playlist.find({});
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
const applyMove = (node: any): any => {
|
||||||
|
if (!node) return node;
|
||||||
|
if (node.type === 'playlist' && node.name === playlistName) {
|
||||||
|
const base: string[] = Array.isArray(node.tracks) ? node.tracks : [];
|
||||||
|
const order: string[] = Array.isArray(node.order) ? node.order : [];
|
||||||
|
// Build effective order
|
||||||
|
const baseSet = new Set(base);
|
||||||
|
const effective = [
|
||||||
|
...order.filter((id: string) => baseSet.has(id)),
|
||||||
|
...base.filter((id: string) => !order.includes(id))
|
||||||
|
];
|
||||||
|
|
||||||
|
// Remove fromId if present
|
||||||
|
const without = effective.filter(id => id !== fromId);
|
||||||
|
let insertIndex = typeof toId === 'string' ? without.indexOf(toId) : without.length;
|
||||||
|
if (insertIndex < 0) insertIndex = without.length;
|
||||||
|
without.splice(insertIndex, 0, fromId);
|
||||||
|
|
||||||
|
node.order = without; // store full order overlay
|
||||||
|
updated = true;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (Array.isArray(node.children)) {
|
||||||
|
node.children = node.children.map(applyMove);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const p of playlists) {
|
||||||
|
applyMove(p as any);
|
||||||
|
p.markModified('children');
|
||||||
|
p.markModified('order');
|
||||||
|
await p.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
return res.status(404).json({ message: `Playlist "${playlistName}" not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ message: 'Track moved', playlistName });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error moving track in playlist:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to move track' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const playlistsRouter = router;
|
export const playlistsRouter = router;
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
import { Search2Icon } from '@chakra-ui/icons';
|
import { Search2Icon } from '@chakra-ui/icons';
|
||||||
import { FiPlay } from 'react-icons/fi';
|
import { FiPlay } from 'react-icons/fi';
|
||||||
import type { Song, PlaylistNode } from '../types/interfaces';
|
import type { Song, PlaylistNode } from '../types/interfaces';
|
||||||
|
import { api } from '../services/api';
|
||||||
import { formatDuration, formatTotalDuration } from '../utils/formatters';
|
import { formatDuration, formatTotalDuration } from '../utils/formatters';
|
||||||
import { useDebounce } from '../hooks/useDebounce';
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import { PlaylistSelectionModal } from './PlaylistSelectionModal';
|
import { PlaylistSelectionModal } from './PlaylistSelectionModal';
|
||||||
@ -296,12 +297,10 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
|||||||
console.debug('[Reorder] same index drop');
|
console.debug('[Reorder] same index drop');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ordered = [...songs];
|
const toId = songs[index].id;
|
||||||
const [moved] = ordered.splice(fromIndex, 1);
|
// Simpler and more robust: instruct backend to move fromId before toId
|
||||||
ordered.splice(toIndex, 0, moved);
|
await api.moveTrackInPlaylist(currentPlaylist, fromId, toId);
|
||||||
const orderedIds = ordered.map(s => s.id);
|
await onReorder(songs.map(s => s.id)); // trigger refresh via parent
|
||||||
console.debug('[Reorder] persisting order', { fromIndex, toIndex, fromId, toId: songs[index].id });
|
|
||||||
await onReorder(orderedIds);
|
|
||||||
setDragHoverIndex(null);
|
setDragHoverIndex(null);
|
||||||
}}
|
}}
|
||||||
onRowDragStartCapture={(e: React.DragEvent) => {
|
onRowDragStartCapture={(e: React.DragEvent) => {
|
||||||
|
|||||||
@ -106,6 +106,15 @@ class Api {
|
|||||||
if (!response.ok) throw new Error('Failed to reorder playlist');
|
if (!response.ok) throw new Error('Failed to reorder playlist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async moveTrackInPlaylist(playlistName: string, fromId: string, toId?: string): Promise<void> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/playlists/reorder-move`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ playlistName, fromId, toId })
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Failed to move track in playlist');
|
||||||
|
}
|
||||||
|
|
||||||
async resetDatabase(): Promise<boolean> {
|
async resetDatabase(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/reset`, {
|
const response = await fetch(`${API_BASE_URL}/reset`, {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user