feat(playlist-reorder): enable intra-playlist row drag&drop with landing indicator; persist order via backend
This commit is contained in:
parent
32d545959d
commit
50a486f6d8
@ -51,7 +51,10 @@ const SongItem = memo<{
|
||||
showCheckbox: boolean;
|
||||
onPlaySong?: (song: Song) => void;
|
||||
onDragStart: (e: React.DragEvent, songIdsFallback: string[]) => void;
|
||||
}>(({ song, isSelected, isHighlighted, onSelect, onToggleSelection, showCheckbox, onPlaySong, onDragStart }) => {
|
||||
onRowDragOver?: (e: React.DragEvent) => void;
|
||||
onRowDrop?: (e: React.DragEvent) => void;
|
||||
onRowDragStartCapture?: (e: React.DragEvent) => void;
|
||||
}>(({ song, isSelected, isHighlighted, onSelect, onToggleSelection, showCheckbox, onPlaySong, onDragStart, onRowDragOver, onRowDrop, onRowDragStartCapture }) => {
|
||||
// Memoize the formatted duration to prevent recalculation
|
||||
const formattedDuration = useMemo(() => formatDuration(song.totalTime || ''), [song.totalTime]);
|
||||
const handleClick = useCallback(() => {
|
||||
@ -89,6 +92,9 @@ const SongItem = memo<{
|
||||
onDragStart={(e) => {
|
||||
onDragStart(e, [song.id]);
|
||||
}}
|
||||
onDragOver={onRowDragOver}
|
||||
onDrop={onRowDrop}
|
||||
onDragStartCapture={onRowDragStartCapture}
|
||||
>
|
||||
{showCheckbox && (
|
||||
<Checkbox
|
||||
@ -162,7 +168,7 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isTriggeringRef = useRef(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const dragIndexRef = useRef<number | null>(null);
|
||||
const [dragHoverIndex, setDragHoverIndex] = useState<number | null>(null);
|
||||
|
||||
// Store current values in refs to avoid stale closures
|
||||
const hasMoreRef = useRef(hasMore);
|
||||
@ -267,11 +273,12 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
onPlaySong={onPlaySong}
|
||||
onDragStart={handleDragStart}
|
||||
// Simple playlist reordering within same list by dragging rows
|
||||
onDragOver={(e: React.DragEvent) => {
|
||||
onRowDragOver={(e: React.DragEvent) => {
|
||||
if (!onReorder || !currentPlaylist || selectedSongs.size > 0) return;
|
||||
e.preventDefault();
|
||||
setDragHoverIndex(index);
|
||||
}}
|
||||
onDrop={async (e: React.DragEvent) => {
|
||||
onRowDrop={async (e: React.DragEvent) => {
|
||||
if (!onReorder || !currentPlaylist || selectedSongs.size > 0) return;
|
||||
e.preventDefault();
|
||||
const fromId = e.dataTransfer.getData('text/song-id');
|
||||
@ -283,12 +290,12 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
const [moved] = ordered.splice(fromIndex, 1);
|
||||
ordered.splice(toIndex, 0, moved);
|
||||
await onReorder(ordered.map(s => s.id));
|
||||
setDragHoverIndex(null);
|
||||
}}
|
||||
onDragStartCapture={(e: React.DragEvent) => {
|
||||
onRowDragStartCapture={(e: React.DragEvent) => {
|
||||
// Provide a simple id for intra-list reorder
|
||||
if (!currentPlaylist || selectedSongs.size > 0) return;
|
||||
e.dataTransfer.setData('text/song-id', song.id);
|
||||
dragIndexRef.current = index;
|
||||
}}
|
||||
/>
|
||||
));
|
||||
@ -467,7 +474,16 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
id="song-list-container"
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<Box onDragEnd={handleDragEnd}>{songItems}</Box>
|
||||
<Box onDragEnd={handleDragEnd}>
|
||||
{songs.map((song, index) => (
|
||||
<Box key={`row-${song.id}`} position="relative">
|
||||
{dragHoverIndex === index && (
|
||||
<Box position="absolute" top={0} left={0} right={0} height="2px" bg="blue.400" zIndex={1} />
|
||||
)}
|
||||
{songItems[index]}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Loading indicator for infinite scroll or playlist switching */}
|
||||
{(loading || isSwitchingPlaylist) && (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user