feat: enhance SequencerPage and SequencerCanvas with drag-and-drop functionality for sound placement and improved track management

This commit is contained in:
JSC
2025-09-03 14:46:28 +02:00
parent 28faf9b149
commit 25eacbc85f
3 changed files with 335 additions and 249 deletions

View File

@@ -2,7 +2,7 @@ import { useDroppable, useDraggable } from '@dnd-kit/core'
import type { Track, PlacedSound } from '@/pages/SequencerPage'
import { Button } from '@/components/ui/button'
import { Trash2, Volume2 } from 'lucide-react'
import { useState, forwardRef, useRef } from 'react'
import { useState, forwardRef, useRef, useEffect } from 'react'
interface SequencerCanvasProps {
tracks: Track[]
@@ -11,6 +11,8 @@ interface SequencerCanvasProps {
currentTime: number
isPlaying: boolean
onScroll?: () => void
draggedItem?: any // Current dragged item from parent
dragOverInfo?: {trackId: string, x: number} | null // Drag over position info
}
interface TrackRowProps {
@@ -19,6 +21,8 @@ interface TrackRowProps {
zoom: number
isPlaying: boolean
currentTime: number
draggedItem?: any // Current dragged item
dragOverInfo?: {trackId: string, x: number} | null // Drag over position info
}
interface PlacedSoundItemProps {
@@ -101,7 +105,7 @@ function PlacedSoundItem({ sound, zoom, trackId, onRemove }: PlacedSoundItemProp
)
}
function TrackRow({ track, duration, zoom, isPlaying, currentTime }: TrackRowProps) {
function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem, dragOverInfo }: TrackRowProps) {
const totalWidth = duration * zoom
const playheadPosition = currentTime * zoom
@@ -113,23 +117,7 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime }: TrackRowPro
},
})
const [dragOverX, setDragOverX] = useState<number | null>(null)
const handleMouseMove = (e: React.MouseEvent) => {
if (isOver) {
const rect = e.currentTarget.getBoundingClientRect()
const x = e.clientX - rect.left
setDragOverX(x)
}
}
const handleMouseLeave = () => {
setDragOverX(null)
}
const handleRemoveSound = (soundId: string) => {
// This would typically be handled by the parent component
// For now, we'll just console.log
console.log('Remove sound:', soundId, 'from track:', track.id)
}
@@ -137,6 +125,7 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime }: TrackRowPro
<div className="relative" style={{ height: '80px' }}>
<div
ref={setDropRef}
id={`track-${track.id}`}
className={`
w-full h-full border-b border-border/50
relative overflow-hidden
@@ -144,8 +133,6 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime }: TrackRowPro
transition-colors
`}
style={{ minWidth: `${totalWidth}px` }}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
{/* Grid lines for time markers */}
<div className="absolute inset-0 pointer-events-none">
@@ -166,12 +153,26 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime }: TrackRowPro
))}
</div>
{/* Drop indicator */}
{isOver && dragOverX !== null && (
{/* Precise drag preview */}
{draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (
<div
className="absolute top-0 bottom-0 w-0.5 bg-primary/60 pointer-events-none z-20"
style={{ left: `${dragOverX}px` }}
/>
className="absolute top-2 bottom-2 border-2 border-dashed border-primary/60 bg-primary/10 rounded pointer-events-none z-10 flex items-center px-2"
style={{
left: `${dragOverInfo.x}px`,
width: `${Math.max(60,
draggedItem.type === 'sound'
? (draggedItem.sound.duration / 1000) * zoom
: draggedItem.duration * zoom
)}px`,
}}
>
<div className="text-xs text-primary/80 truncate font-medium">
{draggedItem.type === 'sound'
? (draggedItem.sound.name || draggedItem.sound.filename)
: draggedItem.name
}
</div>
</div>
)}
{/* Playhead */}
@@ -204,9 +205,19 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
currentTime,
isPlaying,
onScroll,
draggedItem,
dragOverInfo,
}, ref) => {
const totalWidth = duration * zoom
const timelineRef = useRef<HTMLDivElement>(null)
// Add a fallback droppable for the entire canvas area
const { setNodeRef: setCanvasDropRef } = useDroppable({
id: 'sequencer-canvas',
data: {
type: 'canvas',
},
})
const handleTracksScroll = (e: React.UIEvent<HTMLDivElement>) => {
// Sync timeline horizontal scroll with tracks
@@ -222,7 +233,7 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
}
return (
<div className="h-full flex flex-col">
<div ref={setCanvasDropRef} className="h-full flex flex-col">
{/* Time ruler */}
<div className="h-8 bg-muted/50 border-b border-border/50 flex-shrink-0 overflow-hidden">
<div
@@ -278,6 +289,8 @@ export const SequencerCanvas = forwardRef<HTMLDivElement, SequencerCanvasProps>(
zoom={zoom}
isPlaying={isPlaying}
currentTime={currentTime}
draggedItem={draggedItem}
dragOverInfo={dragOverInfo}
/>
))}
</div>