import React, { useState, useRef, useEffect } from 'react'; import { KGTrack } from '../../core/track/KGTrack'; import { KGMidiTrack } from '../../core/track/KGMidiTrack'; import { useProjectStore } from '../../stores/projectStore'; import { TbPiano } from 'react-icons/tb'; import { TbSettings } from 'react-icons/tb'; import KGDropdown from '../common/KGDropdown'; import { FLUIDR3_INSTRUMENT_MAP } from '../../constants/generalMidiConstants'; import { DEBUG_MODE } from '../../constants/uiConstants'; import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface'; import { AUDIO_INTERFACE_CONSTANTS } from '../../constants/coreConstants'; interface TrackInfoItemProps { track: KGTrack; index: number; isDragging: boolean; isDragOver: boolean; onTrackClick?: () => void; onTrackNameEdit: (track: KGTrack, newName: string) => void; // eslint-disable-line no-unused-vars onDragStart: (e: React.DragEvent, index: number) => void; // eslint-disable-line no-unused-vars onDragOver: (e: React.DragEvent, index: number) => void; // eslint-disable-line no-unused-vars onDrop: (e: React.DragEvent) => void; // eslint-disable-line no-unused-vars onDragEnd: (e: React.DragEvent) => void; // eslint-disable-line no-unused-vars } const TrackInfoItem: React.FC = ({ track, index, isDragging, isDragOver, onTrackClick, onTrackNameEdit, onDragStart, onDragOver, onDrop, onDragEnd }) => { const { selectedTrackId, setSelectedTrack, removeTrack, toggleInstrumentSelectionForTrack, tracks: allTracks } = useProjectStore(); const isSelected = selectedTrackId === track.getId().toString(); // Inline instrument dropdown removed; use InstrumentSelection panel instead // Initialize current instrument from track data const getTrackInstrument = () => { if (track instanceof KGMidiTrack) { return track.getInstrument(); } return 'acoustic_grand_piano'; // Default fallback }; const [currentInstrument, setCurrentInstrument] = useState(getTrackInstrument()); const [showSettingsDropdown, setShowSettingsDropdown] = useState(false); const settingsDropdownRef = useRef(null); const suppressDragRef = useRef(false); const [volume, setVolume] = useState(track.getVolume()); // Local flag to track slider interaction; not used for rendering const isAdjustingVolumeRef = useRef(false); const [muted, setMuted] = useState(false); const [solo, setSolo] = useState(false); // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( showSettingsDropdown && settingsDropdownRef.current && !settingsDropdownRef.current.contains(event.target as Node) ) { setShowSettingsDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showSettingsDropdown]); // Sync currentInstrument state with actual track instrument value const instrumentFromTrack = track instanceof KGMidiTrack ? track.getInstrument() : 'acoustic_grand_piano'; useEffect(() => { setCurrentInstrument(instrumentFromTrack); }, [instrumentFromTrack]); // Sync volume UI with model when tracks state changes (e.g., load, undo/redo, external updates) useEffect(() => { setVolume(track.getVolume()); }, [allTracks, track]); // Sync mute/solo UI with audio interface state on track/project changes useEffect(() => { setMuted(false); setSolo(false); }, [allTracks, track]); // Handle track name edit within the component const handleTrackNameClick = (e: React.MouseEvent) => { e.stopPropagation(); // Prevent opening piano roll when clicking track name const newName = prompt("Enter track name:", track.getName()); if (newName) { // Call the parent handler with the new name onTrackNameEdit(track, newName); } }; // Prevent drag reordering when interacting with interactive controls const handleMouseDownCapture = (e: React.MouseEvent) => { const target = e.target as HTMLElement; const isInteractive = !!target.closest( 'input, button, .volume-slider, .instrument-dropdown, .settings-dropdown' ); suppressDragRef.current = isInteractive; }; const handleDragStartWrapper = (e: React.DragEvent) => { if (suppressDragRef.current) { e.preventDefault(); e.stopPropagation(); suppressDragRef.current = false; return; } onDragStart(e, index); }; const handleVolumeChange = (e: React.ChangeEvent) => { e.stopPropagation(); const next = Number(e.target.value) / 100; isAdjustingVolumeRef.current = true; setVolume(next); try { // Live preview: update audio only while sliding KGAudioInterface.instance().setTrackVolume(track.getId().toString(), next); } catch (err) { console.error('Failed to update live volume:', err); } }; const commitVolumeChange = () => { // Only commit if value actually changed from model to avoid extra commands const modelVolume = track.getVolume(); if (Math.abs(modelVolume - volume) < 1e-6) { isAdjustingVolumeRef.current = false; return; } try { useProjectStore.getState().updateTrackProperties(track.getId(), { volume }); } catch (err) { console.error('Failed to persist volume:', err); } finally { isAdjustingVolumeRef.current = false; } }; const handleResetVolume = (e: React.MouseEvent) => { e.stopPropagation(); const defaultVolume = AUDIO_INTERFACE_CONSTANTS.DEFAULT_TRACK_VOLUME; setVolume(defaultVolume); try { useProjectStore.getState().updateTrackProperties(track.getId(), { volume: defaultVolume }); } catch (err) { console.error('Failed to reset volume:', err); } }; const handleToggleMute = (e: React.MouseEvent) => { e.stopPropagation(); const next = !muted; setMuted(next); try { KGAudioInterface.instance().setTrackMute(track.getId().toString(), next); } catch (err) { console.error('Failed to toggle mute:', err); } }; const handleToggleSolo = (e: React.MouseEvent) => { e.stopPropagation(); const next = !solo; setSolo(next); try { KGAudioInterface.instance().setTrackSolo(track.getId().toString(), next); } catch (err) { console.error('Failed to toggle solo:', err); } }; // Handle track click const handleTrackClick = () => { // Select this track when clicked setSelectedTrack(track.getId().toString()); if (onTrackClick) { onTrackClick(); } }; // Inline instrument change removed; handled by InstrumentSelection panel // Handle piano button click const handlePianoButtonClick = (e: React.MouseEvent) => { e.stopPropagation(); // Select this track as active when opening instrument panel setSelectedTrack(track.getId().toString()); // Toggle global InstrumentSelection panel (it follows selectedTrackId) toggleInstrumentSelectionForTrack(); }; // Handle settings button click const handleSettingsButtonClick = (e: React.MouseEvent) => { e.stopPropagation(); setShowSettingsDropdown(!showSettingsDropdown); }; // Handle settings action const handleSettingsAction = async (action: string) => { if (action === 'Delete Track') { const confirmed = window.confirm(`Are you sure you want to delete track "${track.getName()}"?`); if (confirmed) { try { if (DEBUG_MODE.TRACK_INFO) { console.log('Delete track confirmed for:', track.getName()); } // Clear selection if this track is selected // if (selectedTrackId === track.getId().toString()) { // setSelectedTrack(null); // } // Delete the track using the command system await removeTrack(track.getId()); // Close the settings dropdown setShowSettingsDropdown(false); } catch (error) { console.error('Failed to delete track:', error); alert('Failed to delete track. Please try again.'); } } } }; return (
onDragOver(e, index)} onDrop={onDrop} onDragEnd={onDragEnd} >
{String(FLUIDR3_INSTRUMENT_MAP[currentInstrument
{track.getName()}
{ e.stopPropagation(); isAdjustingVolumeRef.current = true; }} onMouseUp={(e) => { e.stopPropagation(); commitVolumeChange(); }} onTouchStart={(e) => { e.stopPropagation(); isAdjustingVolumeRef.current = true; }} onTouchEnd={(e) => { e.stopPropagation(); commitVolumeChange(); }} onBlur={commitVolumeChange} onClick={(e) => e.stopPropagation()} />
); }; export default TrackInfoItem;