import React from 'react'; import './Toolbar.css'; import { saveProject } from '../util/saveUtil'; import { KGProjectStorage } from '../core/io/KGProjectStorage'; import { isValidProjectName, isReservedProjectName, RESERVED_PROJECT_NAME } from '../util/projectNameUtil'; import { KGCore } from '../core/KGCore'; import { useProjectStore } from '../stores/projectStore'; import { DEBUG_MODE } from '../constants/uiConstants'; import { TIME_CONSTANTS } from '../constants/coreConstants'; import { parseTimeSignature, getTimeSignatureErrorMessage } from '../util/timeUtil'; import { FaUndo, FaRedo, FaMousePointer, FaStepBackward, FaPlay, FaPause, FaComments, FaSync, FaFolderOpen, FaSave, FaDownload, FaUpload, FaPlus, FaCog, FaMagnet, FaCut, FaCircle, FaCompress } from 'react-icons/fa'; import { KGProject, type KeySignature } from '../core/KGProject'; import { KGMidiInput } from '../core/midi-input/KGMidiInput'; import { KGMidiRegion } from '../core/region/KGMidiRegion'; import { KGAudioTrack } from '../core/track/KGAudioTrack'; import { plainToInstance } from 'class-transformer'; import { FaPencil, FaCopy, FaPaste, FaTrash, FaWandMagicSparkles, FaListUl } from 'react-icons/fa6'; import { KGMainContentState } from '../core/state/KGMainContentState'; import { regionDeleteManager } from '../util/regionDeleteUtil'; import { handleCopyOperation, handlePasteOperation } from '../util/copyPasteUtil'; import { convertProjectToMidi, convertMidiToProject } from '../util/midiUtil'; import { KGOfflineRenderer } from '../core/audio-interface/KGOfflineRenderer'; import KGDropdown from './common/KGDropdown'; import FloatingPopup from './common/FloatingPopup'; import FileImportModal from './common/FileImportModal'; import LoadingOverlay from './common/LoadingOverlay'; import OpenProjectModal from './common/OpenProjectModal'; import KeySignaturePickerPopup from './KeySignaturePickerPopup'; import { clearChatHistoryAndUI } from '../util/chatUtil'; import PianoIcon from './common/icons/PianoIcon'; import MetronomeIcon from './common/icons/MetronomeIcon'; import { mergeSelectedMidiRegions, splitSelectedRegionAtPlayhead } from '../util/regionEditUtil'; import { showAlert, showChoice, showConfirm, showPrompt, showTimeSigPrompt } from '../util/dialogUtil'; const Toolbar: React.FC = () => { const { projectName, setProjectName, savedProjectName, setSavedProjectName, bpm, timeSignature, keySignature, setStatus, isPlaying, isPreparingPlayback, startPlaying, stopTransport, setPlayheadPosition, currentTime, setBpm, setTimeSignature, setKeySignature, maxBars, setMaxBars, barWidthMultiplier, setBarWidthMultiplier, isLooping, toggleLoop, canUndo, canRedo, undoDescription, redoDescription, undo, redo, toggleChatBox, toggleSettings, toggleKGOnePanel, toggleEventListPanel, activateSidePanel, showKGOnePanel, showEventListPanel, showChatBox, showSettings, cleanupProjectState, toggleMetronome, isMetronomeEnabled, isRecording, startRecording, stopRecording, // Piano roll state/actions showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId, // Selection state selectedRegionIds, selectedTrackId, // Playhead and refresh playheadPosition, refreshProjectState, requestMainContentScroll, requestPianoRollScroll } = useProjectStore(); // State for main content tools const [activeMainTool, setActiveMainTool] = React.useState<'pointer' | 'pencil'>('pointer'); const [isSnapping, setIsSnapping] = React.useState(true); // State for key signature dropdown const [showKeySignatureDropdown, setShowKeySignatureDropdown] = React.useState(false); // State for export dropdown const [showExportDropdown, setShowExportDropdown] = React.useState(false); // State for import modal const [showImportModal, setShowImportModal] = React.useState(false); // State for zoom slider popup const [showZoomSlider, setShowZoomSlider] = React.useState(false); const zoomSliderRef = React.useRef(null); // State for open project modal const [showOpenProject, setShowOpenProject] = React.useState(false); const [isOpeningProject, setIsOpeningProject] = React.useState(false); // Close zoom slider on click outside React.useEffect(() => { if (!showZoomSlider) return; const handleClickOutside = (e: MouseEvent) => { if (zoomSliderRef.current && !zoomSliderRef.current.contains(e.target as Node)) { setShowZoomSlider(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [showZoomSlider]); // Export options const exportOptions = ["Export to KGStudio file", "Export to MIDI file", "Export to WAV", "Export to MP3"]; const lastSelectedRegionId = selectedRegionIds[selectedRegionIds.length - 1] ?? null; const handleProjectNameClick = async () => { const newName = await showPrompt("Enter project name:", projectName); if (!newName) return; if (!isValidProjectName(newName)) { await showAlert("Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed."); return; } if (isReservedProjectName(newName)) { await showAlert(`"${RESERVED_PROJECT_NAME}" is a reserved project name. Please choose a different name.`); return; } // If the name hasn't changed from the saved state, just save without prompting if (newName === savedProjectName) { setProjectName(newName); await saveProject(newName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== newName) setProjectName(finalName); }); return; } // Untitled Project is ephemeral — no existing save to preserve, so rename directly if (savedProjectName === RESERVED_PROJECT_NAME) { const storage = KGProjectStorage.getInstance(); const exists = await storage.exists(newName); if (exists) { const confirmed = await showConfirm( `Project "${newName}" already exists. Do you want to overwrite it?` ); if (!confirmed) return; setProjectName(newName); await saveProject(newName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== newName) setProjectName(finalName); }, true /* forceOverwrite */); return; } setProjectName(newName); await saveProject(newName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== newName) setProjectName(finalName); }); return; } // Ask whether the user wants to rename or save as a copy const choice = await showChoice( "Would you like to rename this project, or save it as a new copy?", [ { label: 'Save as Copy', value: 'saveas' }, { label: 'Rename', value: 'rename' }, ] ); if (!choice) return; if (choice === 'rename') { // Conflict check: only relevant when targeting a different OPFS folder const storage = KGProjectStorage.getInstance(); const exists = await storage.exists(newName); if (exists) { const confirmed = await showConfirm( `Project "${newName}" already exists. Do you want to overwrite it?` ); if (!confirmed) return; setProjectName(newName); await saveProject(newName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== newName) setProjectName(finalName); }, true /* forceOverwrite */); return; } setProjectName(newName); await saveProject(newName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== newName) setProjectName(finalName); }); } else { // Save as Copy: save current state under a unique new name, then switch to it const storage = KGProjectStorage.getInstance(); const finalName = await storage.resolveUniqueName(newName); try { await storage.saveAs(savedProjectName, finalName, KGCore.instance().getCurrentProject()); setProjectName(finalName); setSavedProjectName(finalName); setStatus(`Saved as "${finalName}"`); } catch (error) { console.error('Error saving project as copy:', error); await showAlert(`An error occurred while saving: ${error}`); } } }; // Common project loading logic extracted for reuse const loadProjectFromData = async (project: KGProject, sourceDescription: string, savedName?: string) => { try { // Clean up UI state first cleanupProjectState(); // Automatically clear chat history when loading a project clearChatHistoryAndUI(); // Load the project using the store's loadProject method const { loadProject: storeLoadProject } = useProjectStore.getState(); await storeLoadProject(project, savedName); // Clean up orphan media files (only on open, not on save) if (savedName) { const storage = KGProjectStorage.getInstance(); await storage.cleanupOrphanMedia(savedName, project); } // Update status to indicate project loaded setStatus(`${sourceDescription} loaded successfully`); if (DEBUG_MODE.TOOLBAR) { console.log(`project loaded successfully from ${sourceDescription}`); } } catch (error) { console.error(`Error loading project from ${sourceDescription}:`, error); setStatus(`Failed to load project: ${error}`); await showAlert(`An error occurred while loading the project: ${error}`); } }; // Core logic for creating a new project (no confirmation dialog) const createNewProject = () => { if (DEBUG_MODE.TOOLBAR) { console.log("creating new project"); } cleanupProjectState(); clearChatHistoryAndUI(); const newProject = new KGProject(); const { loadProject: storeLoadProject } = useProjectStore.getState(); storeLoadProject(newProject); setStatus(`New project "${newProject.getName()}" created`); if (DEBUG_MODE.TOOLBAR) { console.log("new project created successfully"); } }; // Handler functions for file operations const handleNewProject = async () => { const confirmed = await showConfirm("Are you sure you want to create a new project? Any unsaved changes will be lost."); if (confirmed) { createNewProject(); } }; const handleLoadProject = () => { if (DEBUG_MODE.TOOLBAR) { console.log("user clicked load button"); } setShowOpenProject(true); }; const handleOpenProjectSelect = async (projectNameToLoad: string) => { setIsOpeningProject(true); try { const storage = KGProjectStorage.getInstance(); const loadedProject = await storage.load(projectNameToLoad); if (!loadedProject) { await showAlert(`Project "${projectNameToLoad}" not found.`); return; } await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad); } catch (error) { console.error("Error loading project:", error); await showAlert(`An error occurred while loading the project: ${error}`); } finally { setIsOpeningProject(false); } }; const handleConfirmOpenProject = async () => { const confirmed = await showConfirm( 'Open this project? Any unsaved changes in the current project will be lost.' ); return confirmed; }; const handleSaveProject = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("user clicked save button"); } await saveProject(projectName, savedProjectName, setStatus, (finalName) => { setSavedProjectName(finalName); if (finalName !== projectName) { setProjectName(finalName); } }); }; const handleExportProject = (exportType: string) => { if (DEBUG_MODE.TOOLBAR) { console.log("user selected export option:", exportType); } if (exportType === "Export to KGStudio file") { handleExportKGStudio(); } else if (exportType === "Export to MIDI file") { handleExportMIDI(); } else if (exportType === "Export to WAV") { handleBounceToWav(); } else if (exportType === "Export to MP3") { handleBounceToMp3(); } setShowExportDropdown(false); }; const handleExportKGStudio = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("exporting to KGStudio file"); } try { // First save the current project to OPFS so the export reflects the latest state const storage = KGProjectStorage.getInstance(); await storage.save(projectName, KGCore.instance().getCurrentProject(), true); // Bundle the project folder into a zip const blob = await storage.exportAsZip(projectName); // Trigger download const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${projectName}.kgstudio`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); setStatus(`Project "${projectName}" exported as KGStudio file`); if (DEBUG_MODE.TOOLBAR) { console.log("KGStudio export completed successfully"); } } catch (error) { console.error("Error exporting KGStudio file:", error); setStatus(`Error exporting project: ${error}`); await showAlert(`Failed to export project: ${error}`); } }; const handleExportMIDI = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("exporting to MIDI file"); } try { // Get the current project from KGCore const currentProject = KGCore.instance().getCurrentProject(); // Convert project to MIDI format const midiData = convertProjectToMidi(currentProject); // Create a downloadable blob const blob = new Blob([midiData.buffer as ArrayBuffer], { type: 'audio/midi' }); // Create a temporary download link const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${projectName}.mid`; // Trigger download document.body.appendChild(link); link.click(); // Cleanup document.body.removeChild(link); URL.revokeObjectURL(url); setStatus(`Project "${projectName}" exported as MIDI file`); if (DEBUG_MODE.TOOLBAR) { console.log("MIDI export completed successfully"); } } catch (error) { console.error("Error exporting MIDI:", error); setStatus(`Error exporting MIDI: ${error}`); await showAlert(`Failed to export project as MIDI: ${error}`); } }; const handleBounceToWav = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("bouncing to WAV"); } try { const currentProject = KGCore.instance().getCurrentProject(); await KGOfflineRenderer.instance().bounceToWav(currentProject, projectName); setStatus(`Project "${projectName}" exported as WAV file`); } catch (error) { console.error("Error bouncing to WAV:", error); setStatus(`Error exporting WAV: ${error}`); await showAlert(`Failed to export project as WAV: ${error}`); } }; const handleBounceToMp3 = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("bouncing to MP3"); } try { const currentProject = KGCore.instance().getCurrentProject(); await KGOfflineRenderer.instance().bounceToMp3(currentProject, projectName); setStatus(`Project "${projectName}" exported as MP3 file`); } catch (error) { console.error("Error bouncing to MP3:", error); setStatus(`Error exporting MP3: ${error}`); await showAlert(`Failed to export project as MP3: ${error}`); } }; const handleImportProject = () => { if (DEBUG_MODE.TOOLBAR) { console.log("user clicked import button"); } setShowImportModal(true); }; const handleFileImport = async (file: File) => { if (DEBUG_MODE.TOOLBAR) { console.log("file selected for import:", file.name); } // Get file extension const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); try { if (fileExtension === '.kgstudio') { // Handle KGStudio bundle import await handleKGStudioFileImport(file); } else if (fileExtension === '.json') { // Handle legacy KGStudio JSON import await handleKGStudioJSONImport(file); } else if (fileExtension === '.mid' || fileExtension === '.midi') { // Handle MIDI import await handleMIDIImport(file); } else { throw new Error(`Unsupported file type: ${fileExtension}`); } } catch (error) { console.error("Error importing file:", error); setStatus(`Failed to import file: ${error}`); await showAlert(`Failed to import project file: ${error}`); } }; const handleKGStudioFileImport = async (file: File) => { const storage = KGProjectStorage.getInstance(); try { const projectName = await storage.importFromZip(file); // Load the project from OPFS (this runs the upgrader) const loaded = await storage.load(projectName); if (!loaded) { throw new Error('Failed to load imported project'); } await loadProjectFromData(loaded, `KGStudio file "${file.name}"`, projectName); if (DEBUG_MODE.TOOLBAR) { console.log("KGStudio file imported successfully:", projectName); } } catch (error) { await showAlert(`The .kgstudio file is corrupted or invalid: ${error}`); throw error; } }; const handleKGStudioJSONImport = async (file: File) => { try { const fileContent = await file.text(); const projectData = JSON.parse(fileContent); // Deserialize and handle potential array return const deserializedResult = plainToInstance(KGProject, projectData); const project = Array.isArray(deserializedResult) ? deserializedResult[0] || null : deserializedResult; if (!project) { throw new Error("Failed to deserialize project data"); } // Use the project's name for the OPFS folder, auto-rename if it already exists const storage = KGProjectStorage.getInstance(); const importedName = await storage.resolveUniqueName(project.getName() || 'Imported Project'); // Save to OPFS as a proper folder-based project project.setName(importedName); await storage.save(importedName, project, false); // Load back from OPFS so the upgrader runs const loaded = await storage.load(importedName); if (!loaded) { throw new Error('Failed to load imported project from storage'); } await loadProjectFromData(loaded, `JSON file "${file.name}"`, importedName); if (DEBUG_MODE.TOOLBAR) { console.log("KGStudio JSON project imported and saved to OPFS:", importedName); } } catch (error) { throw new Error(`Invalid KGStudio JSON file: ${error}`); } }; const handleMIDIImport = async (file: File) => { try { if (DEBUG_MODE.TOOLBAR) { console.log("Starting MIDI file import:", file.name); } // Show loading status setStatus(`Importing MIDI file "${file.name}"...`); // Read the MIDI file as binary data const arrayBuffer = await file.arrayBuffer(); const midiData = new Uint8Array(arrayBuffer); if (DEBUG_MODE.TOOLBAR) { console.log("MIDI file read successfully, size:", midiData.length, "bytes"); } // Get current project to append MIDI tracks to it const currentProject = KGCore.instance().getCurrentProject(); // Convert MIDI data and append to current project const updatedProject = convertMidiToProject(midiData, currentProject); if (DEBUG_MODE.TOOLBAR) { console.log("MIDI conversion successful, tracks added to existing project"); } // Load the updated project using common loading logic await loadProjectFromData(updatedProject, `MIDI file "${file.name}"`); if (DEBUG_MODE.TOOLBAR) { console.log("MIDI file imported successfully:", file.name); } } catch (error) { console.error("Error importing MIDI file:", error); const errorMessage = error instanceof Error ? error.message : String(error); setStatus(`Failed to import MIDI file: ${errorMessage}`); throw new Error(`Invalid MIDI file: ${errorMessage}`); } }; // Handler functions for playback control const handlePlayClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Play button clicked"); } try { await startPlaying(); } catch (error) { console.error("Failed to start playback:", error); setStatus("Playback failed to start"); } }; const handlePauseClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Pause button clicked"); } try { await stopTransport(); if (isRecording) { setStatus("Recording stopped — notes committed"); } } catch (error) { console.error("Failed to stop playback:", error); setStatus("Failed to stop playback"); } }; const handleBackToBeginningClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Back to beginning button clicked"); } setPlayheadPosition(0); requestMainContentScroll(0); requestPianoRollScroll(0); }; const handleLoopToggle = () => { toggleLoop(); if (DEBUG_MODE.TOOLBAR) { console.log("Loop toggle clicked"); } }; // Prompt to change max bars when clicking on current-time display const handleCurrentTimeClick = async () => { const MIN_BARS = 16; const newMaxBarsStr = await showPrompt(`Enter new max bars (>= ${MIN_BARS}):`, String(maxBars ?? 32)); if (newMaxBarsStr === null) { return; // cancelled } const parsed = parseInt(newMaxBarsStr.trim(), 10); if (isNaN(parsed)) { await showAlert('Invalid input. Please enter a valid number.'); return; } if (parsed < MIN_BARS) { await showAlert(`Invalid value. Please enter a number >= ${MIN_BARS}.`); return; } setMaxBars(parsed); setStatus(`Max bars changed to ${parsed}`); }; const handleBpmClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("BPM clicked, current BPM:", bpm); } const newBpmStr = await showPrompt(`Enter new BPM (${TIME_CONSTANTS.MIN_BPM}-${TIME_CONSTANTS.MAX_BPM}):`, bpm.toString()); // Check if user cancelled if (newBpmStr === null) { return; } // Validate input const newBpm = parseInt(newBpmStr.trim()); // Check if it's a valid number if (isNaN(newBpm)) { await showAlert("Invalid input. Please enter a valid number."); return; } // Check if it's within valid range if (newBpm <= TIME_CONSTANTS.MIN_BPM || newBpm >= TIME_CONSTANTS.MAX_BPM) { await showAlert(`Invalid BPM. Please enter a value between ${TIME_CONSTANTS.MIN_BPM} and ${TIME_CONSTANTS.MAX_BPM}.`); return; } // Update BPM setBpm(newBpm); setStatus(`BPM changed to ${newBpm}`); if (DEBUG_MODE.TOOLBAR) { console.log(`BPM updated from ${bpm} to ${newBpm}`); } }; const handleTimeSignatureClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Time signature clicked, current:", `${timeSignature.numerator}/${timeSignature.denominator}`); } const result = await showTimeSigPrompt('Set the time signature:', timeSignature); if (result === null) return; const newTimeSignature = parseTimeSignature(`${result.numerator}/${result.denominator}`); if (newTimeSignature === null) { await showAlert(getTimeSignatureErrorMessage()); return; } setTimeSignature(newTimeSignature); setStatus(`Time signature changed to ${newTimeSignature.numerator}/${newTimeSignature.denominator}`); if (DEBUG_MODE.TOOLBAR) { console.log(`Time signature updated to ${newTimeSignature.numerator}/${newTimeSignature.denominator}`); } }; const handleKeySignatureChange = (newKeySignature: string) => { if (DEBUG_MODE.TOOLBAR) { console.log("Key signature changed from", keySignature, "to", newKeySignature); } setKeySignature(newKeySignature as KeySignature); setStatus(`Key signature changed to ${newKeySignature}`); setShowKeySignatureDropdown(false); }; // Handle main content tool selection const handleMainToolSelect = (tool: 'pointer' | 'pencil') => { setActiveMainTool(tool); KGMainContentState.instance().setActiveTool(tool); if (DEBUG_MODE.TOOLBAR) { console.log(`Selected main content tool: ${tool}`); } }; // Handle snapping toggle const handleSnappingToggle = () => { const newValue = !isSnapping; setIsSnapping(newValue); KGMainContentState.instance().setSnapping(newValue); if (DEBUG_MODE.TOOLBAR) { console.log(`Snapping ${newValue ? 'enabled' : 'disabled'}`); } }; // Handle copy button click const handleCopyClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Copy button clicked"); } const copied = handleCopyOperation(); if (copied) { setStatus("Items copied to clipboard"); if (DEBUG_MODE.TOOLBAR) { console.log("Items copied successfully"); } } else { setStatus("No items selected to copy"); if (DEBUG_MODE.TOOLBAR) { console.log("No items were selected for copying"); } } }; // Handle paste button click const handlePasteClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Paste button clicked"); } const pasted = handlePasteOperation(); if (pasted) { setStatus("Items pasted from clipboard"); if (DEBUG_MODE.TOOLBAR) { console.log("Items pasted successfully"); } } else { setStatus("Cannot paste - no valid clipboard content or context"); if (DEBUG_MODE.TOOLBAR) { console.log("Paste operation failed or no valid context"); } } }; // Handle delete button click const handleDeleteClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Delete button clicked"); } const deleted = regionDeleteManager.deleteSelectedRegions(); if (deleted) { setStatus("Selected regions deleted"); if (DEBUG_MODE.TOOLBAR) { console.log("Regions deleted successfully"); } } else { setStatus("No regions selected for deletion"); if (DEBUG_MODE.TOOLBAR) { console.log("No regions were selected for deletion"); } } }; // Handle split region button click const handleSplitClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Split button clicked"); } const status = await splitSelectedRegionAtPlayhead({ selectedRegionIds, playheadPosition, refreshProjectState, }); if (!status) { return; } setStatus(status); if (DEBUG_MODE.TOOLBAR) { console.log(`Split selected region at beat ${playheadPosition}`); } }; const handleMergeClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log('Merge button clicked'); } const status = await mergeSelectedMidiRegions({ selectedRegionIds, refreshProjectState, }); if (!status) { return; } setStatus(status); }; // Handle undo button click const handleUndoClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Undo button clicked"); } if (!canUndo) { await showAlert("Nothing to undo"); return; } undo(); const description = undoDescription || "action"; setStatus(`Undid: ${description}`); if (DEBUG_MODE.TOOLBAR) { console.log(`Undo successful: ${description}`); } }; // Handle redo button click const handleRedoClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log("Redo button clicked"); } if (!canRedo) { await showAlert("Nothing to redo"); return; } redo(); const description = redoDescription || "action"; setStatus(`Redid: ${description}`); if (DEBUG_MODE.TOOLBAR) { console.log(`Redo successful: ${description}`); } }; // Handle chat button click const handleChatClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Chat button clicked"); } if (showSettings) { activateSidePanel('chat'); } else { toggleChatBox(); } setStatus("Chat toggled"); }; // Handle settings button click const handleSettingsClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Settings button clicked"); } toggleSettings(); setStatus("Settings toggled"); }; // K.G.One panel toggle const handleKGOneClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("K.G.One button clicked"); } if (showSettings) { activateSidePanel('kgone'); return; } toggleKGOnePanel(); }; const handleEventListClick = () => { if (DEBUG_MODE.TOOLBAR) { console.log("Event List button clicked"); } if (showSettings) { activateSidePanel('eventList'); return; } toggleEventListPanel(); }; // Handle Piano button click: open piano roll if closed, targeting active or selected region const handlePianoButtonClick = async () => { if (DEBUG_MODE.TOOLBAR) { console.log('Piano button clicked'); } // Only open if not already open if (showPianoRoll) { if (DEBUG_MODE.TOOLBAR) { console.log('Piano roll already open; no action'); } return; } // Prefer current active region; otherwise, first selected region const candidateRegionId = activeRegionId || lastSelectedRegionId; if (!candidateRegionId) { if (DEBUG_MODE.TOOLBAR) { console.log('No active or selected region; piano roll will not open'); } await showAlert('Please select a MIDI region to open the Piano Roll.'); return; } setActiveRegionId(candidateRegionId); setShowPianoRoll(true); if (DEBUG_MODE.TOOLBAR) { console.log(`Opening piano roll for region ${candidateRegionId}`); } }; const handleMetronomeToggle = () => { toggleMetronome(); if (DEBUG_MODE.TOOLBAR) { console.log('Metronome toggled'); } }; const handleRecordClick = async () => { if (isRecording) { await stopRecording(); setStatus("Recording stopped"); return; } const selectedTrack = useProjectStore.getState().tracks.find(track => track.getId().toString() === selectedTrackId) ?? null; if (selectedTrack instanceof KGAudioTrack) { await startRecording(); setStatus("Audio recording started..."); return; } // Require an active or selected MIDI region const candidateId = activeRegionId ?? lastSelectedRegionId; if (!candidateId) { await showAlert("Please open a MIDI region in the Piano Roll before starting recording."); return; } const tracks = KGCore.instance().getCurrentProject().getTracks(); let isMidi = false; for (const track of tracks) { const region = track.getRegions().find(r => r.getId() === candidateId); if (region) { isMidi = region instanceof KGMidiRegion; break; } } if (!isMidi) { await showAlert("Please select a MIDI region before starting recording."); return; } // Require at least one connected MIDI device if (KGMidiInput.instance().getConnectedInputCount() === 0) { await showAlert("No MIDI device detected. Please connect a MIDI keyboard and try again."); return; } // Ensure the piano roll is open showing the target region if (!activeRegionId) { setActiveRegionId(candidateId); setShowPianoRoll(true); } await startRecording(); setStatus("Recording started..."); }; return ( <>
DAW Logo
{projectName}
{!isPlaying ? ( ) : ( )}
{/* */}
setShowZoomSlider(!showZoomSlider)} style={{ cursor: 'pointer' }} > {barWidthMultiplier}x {showZoomSlider && (
setBarWidthMultiplier(parseInt(e.target.value))} /> {barWidthMultiplier}x
)}
{currentTime}
{bpm}
{timeSignature.numerator + "/" + timeSignature.denominator}
setShowKeySignatureDropdown(false)} placement="bottom" className="key-signature-popup-anchor" contentClassName="key-signature-popup-surface" panelClassName="key-signature-popup-panel" arrowClassName="key-signature-popup-arrow" trigger={( )} >
setShowImportModal(false)} onFileImport={handleFileImport} acceptedTypes={['.kgstudio', '.json', '.mid', '.midi']} title="Import Project" description="Drag and drop your project file here" /> {showOpenProject && ( setShowOpenProject(false)} onConfirmOpenProject={handleConfirmOpenProject} onOpenProject={handleOpenProjectSelect} currentProjectName={savedProjectName} onCreateNewProject={createNewProject} /> )} ); }; export default Toolbar;