From 1ba6f2399904074870b3f747406aa4884fc56c3e Mon Sep 17 00:00:00 2001 From: JSC Date: Wed, 3 Sep 2025 17:17:19 +0200 Subject: [PATCH] Improves sound placement and preview logic Refines the sound placement logic in the sequencer to ensure sounds are placed correctly within track boundaries. It restricts sound placement to the track duration, preventing sounds from being placed out of bounds. Enhances the drag preview by visually indicating invalid placement positions with a red border and "Invalid" label. Also extracts duration and size formatting into separate utility functions for better code organization. --- src/components/sequencer/SequencerCanvas.tsx | 50 ++++--- src/components/sequencer/SoundLibrary.tsx | 16 +-- src/pages/SequencerPage.tsx | 136 ++++++++++--------- 3 files changed, 104 insertions(+), 98 deletions(-) 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