fix: prevent multiple ImportDialog instances from appearing

Fixed issue where ImportDialog was being rendered for each track in
waveform mode, causing multiple unclosable dialogs to appear on page load.

Changes:
- Moved ImportDialog to renderControlsOnly mode only
- Each track now has exactly one ImportDialog (rendered in controls column)
- Removed duplicate ImportDialog from renderWaveformOnly mode
- Removed ImportDialog from fallback return statement

This ensures only one dialog appears per track, making it properly closable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-19 11:30:03 +01:00
parent 7a7d6891cd
commit ca63d12cbf

View File

@@ -682,226 +682,137 @@ export function Track({
// Render only controls
if (renderControlsOnly) {
return (
<div
className={cn(
"w-48 flex-shrink-0 border-b border-r-4 p-2 flex flex-col gap-2 min-h-0 transition-all duration-200 cursor-pointer border-border",
isSelected
? "bg-primary/10 border-r-primary"
: "bg-card border-r-transparent hover:bg-accent/30",
)}
style={{ height: trackHeight }}
onClick={(e) => {
e.stopPropagation();
if (onSelect) onSelect();
}}
>
{/* Track Name Row - Integrated collapse (DAW style) */}
<div
className={cn(
"group flex items-center gap-1.5 px-1 py-0.5 rounded cursor-pointer transition-colors",
isSelected ? "bg-primary/10" : "hover:bg-accent/50",
)}
onClick={(e) => {
if (!isEditingName) {
e.stopPropagation();
onToggleCollapse();
}
}}
title={track.collapsed ? "Expand track" : "Collapse track"}
>
{/* Small triangle indicator */}
<div
className={cn(
"flex-shrink-0 transition-colors",
isSelected
? "text-primary"
: "text-muted-foreground group-hover:text-foreground",
)}
>
{track.collapsed ? (
<ChevronRight className="h-3 w-3" />
) : (
<ChevronDown className="h-3 w-3" />
)}
</div>
{/* Color stripe (thicker when selected) */}
<div
className={cn(
"h-5 rounded-full flex-shrink-0 transition-all",
isSelected ? "w-1" : "w-0.5",
)}
style={{ backgroundColor: track.color }}
></div>
<div className="flex-1 min-w-0">
{isEditingName ? (
<input
ref={inputRef}
type="text"
value={nameInput}
onChange={(e) => setNameInput(e.target.value)}
onBlur={handleNameBlur}
onKeyDown={handleNameKeyDown}
onClick={(e) => e.stopPropagation()}
className="w-full px-1 py-0.5 text-xs font-semibold bg-background border border-border rounded"
/>
) : (
<div
onClick={(e) => {
e.stopPropagation();
handleNameClick();
}}
className="px-1 py-0.5 text-xs font-semibold text-foreground truncate"
title={String(track.name || "Untitled Track")}
>
{String(track.name || "Untitled Track")}
</div>
)}
</div>
</div>
{/* Track Controls - Only show when not collapsed */}
{!track.collapsed && (
<div className="flex-1 flex flex-col items-center justify-between min-h-0 overflow-hidden">
{/* Integrated Track Controls (Pan + Fader + Buttons) */}
<TrackControls
volume={track.volume}
pan={track.pan}
peakLevel={
track.recordEnabled || isRecording
? recordingLevel
: playbackLevel
}
rmsLevel={
track.recordEnabled || isRecording
? recordingLevel * 0.7
: playbackLevel * 0.7
}
isMuted={track.mute}
isSolo={track.solo}
isRecordEnabled={track.recordEnabled}
showAutomation={track.automation?.showAutomation}
showEffects={track.showEffects}
isRecording={isRecording}
onVolumeChange={onVolumeChange}
onPanChange={onPanChange}
onMuteToggle={onToggleMute}
onSoloToggle={onToggleSolo}
onRecordToggle={onToggleRecordEnable}
onAutomationToggle={() => {
onUpdateTrack(track.id, {
automation: {
...track.automation,
showAutomation: !track.automation?.showAutomation,
},
});
}}
onEffectsClick={() => {
onUpdateTrack(track.id, {
showEffects: !track.showEffects,
});
}}
onVolumeTouchStart={handleVolumeTouchStart}
onVolumeTouchEnd={handleVolumeTouchEnd}
onPanTouchStart={handlePanTouchStart}
onPanTouchEnd={handlePanTouchEnd}
/>
</div>
)}
</div>
);
}
// Render only waveform
if (renderWaveformOnly) {
return (
<>
<div
className={cn(
"relative bg-waveform-bg border-b transition-all duration-200",
isSelected && "bg-primary/5",
"w-48 flex-shrink-0 border-b border-r-4 p-2 flex flex-col gap-2 min-h-0 transition-all duration-200 cursor-pointer border-border",
isSelected
? "bg-primary/10 border-r-primary"
: "bg-card border-r-transparent hover:bg-accent/30",
)}
style={{ height: trackHeight }}
onClick={(e) => {
e.stopPropagation();
if (onSelect) onSelect();
}}
>
{/* Inner container with dynamic width */}
{/* Track Name Row - Integrated collapse (DAW style) */}
<div
className="relative h-full"
style={{
minWidth:
track.audioBuffer && zoom > 1
? `${duration * zoom * 100}px`
: "100%",
}}
>
{/* Delete Button - Top Right Overlay */}
<button
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
className={cn(
"absolute top-2 right-2 z-20 h-6 w-6 rounded flex items-center justify-center transition-all",
"bg-card/80 hover:bg-destructive/90 text-muted-foreground hover:text-white",
"border border-border/50 hover:border-destructive",
"backdrop-blur-sm shadow-sm hover:shadow-md",
)}
title="Remove track"
>
<Trash2 className="h-3 w-3" />
</button>
{track.audioBuffer ? (
<>
{/* Waveform Canvas */}
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full cursor-pointer"
onMouseDown={handleCanvasMouseDown}
onMouseMove={handleCanvasMouseMove}
onMouseUp={handleCanvasMouseUp}
/>
</>
) : (
!track.collapsed && (
<>
<div
className={cn(
"absolute inset-0 flex flex-col items-center justify-center text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer group",
isDragging
? "bg-primary/20 text-primary border-2 border-primary border-dashed"
: "hover:bg-accent/50",
)}
onClick={(e) => {
e.stopPropagation();
handleLoadAudioClick();
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<Upload className="h-6 w-6 mb-2 opacity-50 group-hover:opacity-100" />
<p>
{isDragging
? "Drop audio file here"
: "Click to load audio file"}
</p>
<p className="text-xs opacity-75 mt-1">or drag & drop</p>
</div>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
onChange={handleFileChange}
className="hidden"
/>
</>
)
className={cn(
"group flex items-center gap-1.5 px-1 py-0.5 rounded cursor-pointer transition-colors",
isSelected ? "bg-primary/10" : "hover:bg-accent/50",
)}
onClick={(e) => {
if (!isEditingName) {
e.stopPropagation();
onToggleCollapse();
}
}}
title={track.collapsed ? "Expand track" : "Collapse track"}
>
{/* Small triangle indicator */}
<div
className={cn(
"flex-shrink-0 transition-colors",
isSelected
? "text-primary"
: "text-muted-foreground group-hover:text-foreground",
)}
>
{track.collapsed ? (
<ChevronRight className="h-3 w-3" />
) : (
<ChevronDown className="h-3 w-3" />
)}
</div>
{/* Color stripe (thicker when selected) */}
<div
className={cn(
"h-5 rounded-full flex-shrink-0 transition-all",
isSelected ? "w-1" : "w-0.5",
)}
style={{ backgroundColor: track.color }}
></div>
<div className="flex-1 min-w-0">
{isEditingName ? (
<input
ref={inputRef}
type="text"
value={nameInput}
onChange={(e) => setNameInput(e.target.value)}
onBlur={handleNameBlur}
onKeyDown={handleNameKeyDown}
onClick={(e) => e.stopPropagation()}
className="w-full px-1 py-0.5 text-xs font-semibold bg-background border border-border rounded"
/>
) : (
<div
onClick={(e) => {
e.stopPropagation();
handleNameClick();
}}
className="px-1 py-0.5 text-xs font-semibold text-foreground truncate"
title={String(track.name || "Untitled Track")}
>
{String(track.name || "Untitled Track")}
</div>
)}
</div>
</div>
{/* Track Controls - Only show when not collapsed */}
{!track.collapsed && (
<div className="flex-1 flex flex-col items-center justify-between min-h-0 overflow-hidden">
{/* Integrated Track Controls (Pan + Fader + Buttons) */}
<TrackControls
volume={track.volume}
pan={track.pan}
peakLevel={
track.recordEnabled || isRecording
? recordingLevel
: playbackLevel
}
rmsLevel={
track.recordEnabled || isRecording
? recordingLevel * 0.7
: playbackLevel * 0.7
}
isMuted={track.mute}
isSolo={track.solo}
isRecordEnabled={track.recordEnabled}
showAutomation={track.automation?.showAutomation}
showEffects={track.showEffects}
isRecording={isRecording}
onVolumeChange={onVolumeChange}
onPanChange={onPanChange}
onMuteToggle={onToggleMute}
onSoloToggle={onToggleSolo}
onRecordToggle={onToggleRecordEnable}
onAutomationToggle={() => {
onUpdateTrack(track.id, {
automation: {
...track.automation,
showAutomation: !track.automation?.showAutomation,
},
});
}}
onEffectsClick={() => {
onUpdateTrack(track.id, {
showEffects: !track.showEffects,
});
}}
onVolumeTouchStart={handleVolumeTouchStart}
onVolumeTouchEnd={handleVolumeTouchEnd}
onPanTouchStart={handlePanTouchStart}
onPanTouchEnd={handlePanTouchEnd}
/>
</div>
)}
</div>
{/* Import Dialog */}
{/* Import Dialog - Only render in controls mode to avoid duplicates */}
<ImportDialog
open={showImportDialog}
onClose={handleImportCancel}
@@ -914,7 +825,97 @@ export function Track({
);
}
// Render only waveform
if (renderWaveformOnly) {
return (
<div
className={cn(
"relative bg-waveform-bg border-b transition-all duration-200",
isSelected && "bg-primary/5",
)}
style={{ height: trackHeight }}
>
{/* Inner container with dynamic width */}
<div
className="relative h-full"
style={{
minWidth:
track.audioBuffer && zoom > 1
? `${duration * zoom * 100}px`
: "100%",
}}
>
{/* Delete Button - Top Right Overlay */}
<button
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
className={cn(
"absolute top-2 right-2 z-20 h-6 w-6 rounded flex items-center justify-center transition-all",
"bg-card/80 hover:bg-destructive/90 text-muted-foreground hover:text-white",
"border border-border/50 hover:border-destructive",
"backdrop-blur-sm shadow-sm hover:shadow-md",
)}
title="Remove track"
>
<Trash2 className="h-3 w-3" />
</button>
{track.audioBuffer ? (
<>
{/* Waveform Canvas */}
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full cursor-pointer"
onMouseDown={handleCanvasMouseDown}
onMouseMove={handleCanvasMouseMove}
onMouseUp={handleCanvasMouseUp}
/>
</>
) : (
!track.collapsed && (
<>
<div
className={cn(
"absolute inset-0 flex flex-col items-center justify-center text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer group",
isDragging
? "bg-primary/20 text-primary border-2 border-primary border-dashed"
: "hover:bg-accent/50",
)}
onClick={(e) => {
e.stopPropagation();
handleLoadAudioClick();
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<Upload className="h-6 w-6 mb-2 opacity-50 group-hover:opacity-100" />
<p>
{isDragging
? "Drop audio file here"
: "Click to load audio file"}
</p>
<p className="text-xs opacity-75 mt-1">or drag & drop</p>
</div>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
onChange={handleFileChange}
className="hidden"
/>
</>
)
)}
</div>
</div>
);
}
// Render full track (both controls and waveform side by side)
// This mode is no longer used - tracks are rendered separately with renderControlsOnly and renderWaveformOnly
return (
<div
ref={containerRef}