diff --git a/src/components/sequencer/SequencerCanvas.tsx b/src/components/sequencer/SequencerCanvas.tsx index 8b4f5fc..d623489 100644 --- a/src/components/sequencer/SequencerCanvas.tsx +++ b/src/components/sequencer/SequencerCanvas.tsx @@ -157,26 +157,38 @@ function TrackRow({ track, duration, zoom, isPlaying, currentTime, draggedItem, {/* Precise drag preview */} - {draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && ( -
-
- {draggedItem.type === 'sound' - ? (draggedItem.sound.name || draggedItem.sound.filename) - : draggedItem.name - } + {draggedItem && dragOverInfo && dragOverInfo.trackId === track.id && (() => { + const soundDuration = draggedItem.type === 'sound' + ? draggedItem.sound.duration / 1000 // Convert ms to seconds + : draggedItem.duration + const startTime = dragOverInfo.x / zoom + const endTime = startTime + soundDuration + const isValidPosition = startTime >= 0 && endTime <= duration + + return ( +
+
+ {draggedItem.type === 'sound' + ? (draggedItem.sound.name || draggedItem.sound.filename) + : draggedItem.name + } + {!isValidPosition && ' (Invalid)'} +
-
- )} + ) + })()} {/* Playhead */} {isPlaying && ( diff --git a/src/components/sequencer/SoundLibrary.tsx b/src/components/sequencer/SoundLibrary.tsx index a22d014..051b88a 100644 --- a/src/components/sequencer/SoundLibrary.tsx +++ b/src/components/sequencer/SoundLibrary.tsx @@ -21,6 +21,8 @@ import { } from 'lucide-react' import { useCallback, useEffect, useState } from 'react' import { toast } from 'sonner' +import { formatDuration } from '@/utils/format-duration' +import { formatSize } from '@/utils/format-size' interface DraggableSoundProps { sound: Sound @@ -43,18 +45,6 @@ function DraggableSound({ sound }: DraggableSoundProps) { // Don't apply transform to prevent layout shift - DragOverlay handles the visual feedback const style = undefined - const formatDuration = (ms: number): string => { - const seconds = Math.floor(ms / 1000) - const mins = Math.floor(seconds / 60) - const secs = seconds % 60 - return `${mins}:${secs.toString().padStart(2, '0')}` - } - - const formatFileSize = (bytes: number): string => { - const mb = bytes / (1024 * 1024) - return `${mb.toFixed(1)}MB` - } - return (
{formatDuration(sound.duration)} - {formatFileSize(sound.size)} + {formatSize(sound.size)} {sound.play_count > 0 && ( <> diff --git a/src/pages/SequencerPage.tsx b/src/pages/SequencerPage.tsx index f5674ea..1c3f1c8 100644 --- a/src/pages/SequencerPage.tsx +++ b/src/pages/SequencerPage.tsx @@ -119,85 +119,89 @@ export function SequencerPage() { startTime = Math.max(0, dragOverInfo.x / state.zoom) } - const newPlacedSound: PlacedSound = { - id: `placed-${Date.now()}-${Math.random()}`, - soundId: dragData.sound.id, - name: dragData.sound.name || dragData.sound.filename, - duration: dragData.sound.duration / 1000, // Convert from ms to seconds - startTime, - trackId: overData.trackId, - } + const soundDuration = dragData.sound.duration / 1000 // Convert from ms to seconds + + // Restrict placement to within track duration + const maxStartTime = Math.max(0, state.duration - soundDuration) + startTime = Math.min(startTime, maxStartTime) - setState(prev => ({ - ...prev, - tracks: prev.tracks.map(track => - track.id === overData.trackId - ? { ...track, sounds: [...track.sounds, newPlacedSound] } - : track - ), - })) + // Only proceed if the sound can fit within the track + if (startTime >= 0 && startTime + soundDuration <= state.duration) { + const newPlacedSound: PlacedSound = { + id: `placed-${Date.now()}-${Math.random()}`, + soundId: dragData.sound.id, + name: dragData.sound.name || dragData.sound.filename, + duration: soundDuration, + startTime, + trackId: overData.trackId, + } + + setState(prev => ({ + ...prev, + tracks: prev.tracks.map(track => + track.id === overData.trackId + ? { ...track, sounds: [...track.sounds, newPlacedSound] } + : track + ), + })) + } } // Handle moving placed sounds within tracks if (dragData?.type === 'placed-sound' && overData?.type === 'track') { - console.log('Moving placed sound:', dragData, 'to track:', overData.trackId) - // Use precise drop position if available let startTime = dragData.startTime || 0 if (dragOverInfo && dragOverInfo.trackId === overData.trackId) { startTime = Math.max(0, dragOverInfo.x / state.zoom) } - const sourceTrackId = dragData.trackId - const targetTrackId = overData.trackId - - console.log('Source track:', sourceTrackId, 'Target track:', targetTrackId, 'New start time:', startTime) + // Restrict placement to within track duration + const maxStartTime = Math.max(0, state.duration - dragData.duration) + startTime = Math.min(startTime, maxStartTime) - setState(prev => ({ - ...prev, - tracks: prev.tracks.map(track => { - if (track.id === sourceTrackId && sourceTrackId === targetTrackId) { - // Moving within the same track - just update position - console.log('Moving within same track') - const updatedSound: PlacedSound = { - ...dragData, - startTime, - trackId: targetTrackId, + // Only proceed if the sound can fit within the track + if (startTime >= 0 && startTime + dragData.duration <= state.duration) { + const sourceTrackId = dragData.trackId + const targetTrackId = overData.trackId + + setState(prev => ({ + ...prev, + tracks: prev.tracks.map(track => { + if (track.id === sourceTrackId && sourceTrackId === targetTrackId) { + // Moving within the same track - just update position + const updatedSound: PlacedSound = { + ...dragData, + startTime, + trackId: targetTrackId, + } + return { + ...track, + sounds: track.sounds.map(s => + s.id === dragData.id ? updatedSound : s + ), + } + } else if (track.id === sourceTrackId) { + // Remove from source track (different track move) + return { + ...track, + sounds: track.sounds.filter(s => s.id !== dragData.id), + } + } else if (track.id === targetTrackId) { + // Add to target track (different track move) + const updatedSound: PlacedSound = { + ...dragData, + startTime, + trackId: targetTrackId, + } + return { + ...track, + sounds: [...track.sounds, updatedSound], + } } - return { - ...track, - sounds: track.sounds.map(s => - s.id === dragData.id ? updatedSound : s - ), - } - } else if (track.id === sourceTrackId) { - // Remove from source track (different track move) - console.log('Removing sound from source track:', track.id, 'sounds before:', track.sounds.length) - const filtered = track.sounds.filter(s => s.id !== dragData.id) - console.log('Sounds after removal:', filtered.length) - return { - ...track, - sounds: filtered, - } - } else if (track.id === targetTrackId) { - // Add to target track (different track move) - console.log('Adding sound to target track:', track.id, 'sounds before:', track.sounds.length) - const updatedSound: PlacedSound = { - ...dragData, - startTime, - trackId: targetTrackId, - } - console.log('Updated sound:', updatedSound) - const newSounds = [...track.sounds, updatedSound] - console.log('Sounds after addition:', newSounds.length) - return { - ...track, - sounds: newSounds, - } - } - return track - }), - })) + return track + }), + })) + } } // Clear state