feat(reorder): support dragging multiple selected songs to a specific position and to end (block move)
This commit is contained in:
parent
ac52441dd1
commit
9249a5a4a7
@ -184,4 +184,69 @@ router.post('/reorder-move', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Move multiple tracks before another (en bloc, preserves relative order)
|
||||||
|
router.post('/reorder-move-many', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { playlistName, fromIds, toId } = req.body as { playlistName: string; fromIds: string[]; toId?: string };
|
||||||
|
if (!playlistName || !Array.isArray(fromIds) || fromIds.length === 0) {
|
||||||
|
return res.status(400).json({ message: 'playlistName and fromIds are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlists = await Playlist.find({});
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
const applyMoveMany = (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 : [];
|
||||||
|
const baseSet = new Set(base);
|
||||||
|
const effective: string[] = [
|
||||||
|
...order.filter((id: string) => baseSet.has(id)),
|
||||||
|
...base.filter((id: string) => !order.includes(id))
|
||||||
|
];
|
||||||
|
|
||||||
|
const moveSet = new Set(fromIds);
|
||||||
|
const movingOrdered = effective.filter(id => moveSet.has(id));
|
||||||
|
if (movingOrdered.length === 0) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all moving ids
|
||||||
|
const without = effective.filter(id => !moveSet.has(id));
|
||||||
|
let insertIndex = typeof toId === 'string' ? without.indexOf(toId) : without.length;
|
||||||
|
if (insertIndex < 0) insertIndex = without.length;
|
||||||
|
// Insert block preserving relative order
|
||||||
|
without.splice(insertIndex, 0, ...movingOrdered);
|
||||||
|
|
||||||
|
console.log('[REORDER_MOVE_MANY] playlist:', playlistName, 'count:', movingOrdered.length, 'to:', toId, 'baseLen:', base.length, 'orderLenBefore:', order.length, 'orderLenAfter:', without.length);
|
||||||
|
console.log('[REORDER_MOVE_MANY] sample order:', without.slice(0, 7));
|
||||||
|
node.order = without;
|
||||||
|
updated = true;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (Array.isArray(node.children)) {
|
||||||
|
node.children = node.children.map(applyMoveMany);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const p of playlists) {
|
||||||
|
applyMoveMany(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: 'Tracks moved', playlistName });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error moving tracks in playlist:', error);
|
||||||
|
res.status(500).json({ message: 'Error moving tracks in playlist', error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const playlistsRouter = router;
|
export const playlistsRouter = router;
|
||||||
@ -287,12 +287,22 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
|||||||
onRowDrop={async (e: React.DragEvent) => {
|
onRowDrop={async (e: React.DragEvent) => {
|
||||||
if (!onReorder || !currentPlaylist || selectedSongs.size > 0) return;
|
if (!onReorder || !currentPlaylist || selectedSongs.size > 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const fromId = e.dataTransfer.getData('text/song-id');
|
const fromId = e.dataTransfer.getData('text/song-id');
|
||||||
if (!fromId) {
|
const multiJson = e.dataTransfer.getData('application/json');
|
||||||
|
let multiIds: string[] | null = null;
|
||||||
|
if (multiJson) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(multiJson);
|
||||||
|
if (parsed && parsed.type === 'songs' && Array.isArray(parsed.songIds)) {
|
||||||
|
multiIds = parsed.songIds as string[];
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
if (!fromId && !multiIds) {
|
||||||
console.debug('[Reorder] missing fromId');
|
console.debug('[Reorder] missing fromId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fromIndex = songs.findIndex(s => s.id === fromId);
|
const fromIndex = fromId ? songs.findIndex(s => s.id === fromId) : -1;
|
||||||
const toIndex = index;
|
const toIndex = index;
|
||||||
if (fromIndex < 0 || toIndex < 0) {
|
if (fromIndex < 0 || toIndex < 0) {
|
||||||
console.debug('[Reorder] invalid indexes', { fromIndex, toIndex });
|
console.debug('[Reorder] invalid indexes', { fromIndex, toIndex });
|
||||||
@ -303,9 +313,14 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const toId = songs[index].id;
|
const toId = songs[index].id;
|
||||||
// Simpler and more robust: instruct backend to move fromId before toId
|
// If multiple, move block; else move single
|
||||||
console.debug('[Reorder] move request', { playlist: currentPlaylist, fromId, toId });
|
if (multiIds && multiIds.length > 0) {
|
||||||
await api.moveTrackInPlaylist(currentPlaylist, fromId, toId);
|
console.debug('[Reorder] move many request', { playlist: currentPlaylist, fromIds: multiIds, toId });
|
||||||
|
await api.moveTracksInPlaylist(currentPlaylist, multiIds, toId);
|
||||||
|
} else {
|
||||||
|
console.debug('[Reorder] move request', { playlist: currentPlaylist, fromId, toId });
|
||||||
|
await api.moveTrackInPlaylist(currentPlaylist, fromId!, toId);
|
||||||
|
}
|
||||||
await onReorder(songs.map(s => s.id)); // trigger refresh via parent
|
await onReorder(songs.map(s => s.id)); // trigger refresh via parent
|
||||||
setDragHoverIndex(null);
|
setDragHoverIndex(null);
|
||||||
setIsReorderDragging(false);
|
setIsReorderDragging(false);
|
||||||
@ -521,10 +536,25 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
|||||||
onDrop={async (e: React.DragEvent) => {
|
onDrop={async (e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const fromId = e.dataTransfer.getData('text/song-id');
|
const fromId = e.dataTransfer.getData('text/song-id');
|
||||||
if (!fromId) return;
|
const multiJson = e.dataTransfer.getData('application/json');
|
||||||
|
let multiIds: string[] | null = null;
|
||||||
|
if (multiJson) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(multiJson);
|
||||||
|
if (parsed && parsed.type === 'songs' && Array.isArray(parsed.songIds)) {
|
||||||
|
multiIds = parsed.songIds as string[];
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
if (!fromId && !multiIds) return;
|
||||||
// Move to end: omit toId
|
// Move to end: omit toId
|
||||||
console.debug('[Reorder] move to end request', { playlist: currentPlaylist, fromId });
|
if (multiIds && multiIds.length > 0) {
|
||||||
await api.moveTrackInPlaylist(currentPlaylist, fromId);
|
console.debug('[Reorder] move many to end request', { playlist: currentPlaylist, fromIds: multiIds });
|
||||||
|
await api.moveTracksInPlaylist(currentPlaylist, multiIds);
|
||||||
|
} else {
|
||||||
|
console.debug('[Reorder] move to end request', { playlist: currentPlaylist, fromId });
|
||||||
|
await api.moveTrackInPlaylist(currentPlaylist, fromId!);
|
||||||
|
}
|
||||||
await onReorder(songs.map(s => s.id));
|
await onReorder(songs.map(s => s.id));
|
||||||
setEndDropHover(false);
|
setEndDropHover(false);
|
||||||
setIsReorderDragging(false);
|
setIsReorderDragging(false);
|
||||||
|
|||||||
@ -115,6 +115,15 @@ class Api {
|
|||||||
if (!response.ok) throw new Error('Failed to move track in playlist');
|
if (!response.ok) throw new Error('Failed to move track in playlist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async moveTracksInPlaylist(playlistName: string, fromIds: string[], toId?: string): Promise<void> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/playlists/reorder-move-many`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ playlistName, fromIds, toId })
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Failed to move tracks 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