feat: replace browser built-in alert/confirm/prompt pop-up with new DialogProvider
This commit is contained in:
+3
-3
@@ -18,6 +18,7 @@ import type { RenderingEvent } from './core/audio-interface/KGOfflineRenderer';
|
|||||||
import { KGCore } from './core/KGCore';
|
import { KGCore } from './core/KGCore';
|
||||||
import { ConfigManager } from './core/config/ConfigManager';
|
import { ConfigManager } from './core/config/ConfigManager';
|
||||||
import { validateFunctionalChordsJSON } from './util/scaleUtil';
|
import { validateFunctionalChordsJSON } from './util/scaleUtil';
|
||||||
|
import { showAlert } from './components/common/DialogProvider';
|
||||||
import { KGProjectStorage } from './core/io/KGProjectStorage';
|
import { KGProjectStorage } from './core/io/KGProjectStorage';
|
||||||
import { RESERVED_PROJECT_NAME } from './util/projectNameUtil';
|
import { RESERVED_PROJECT_NAME } from './util/projectNameUtil';
|
||||||
|
|
||||||
@@ -212,12 +213,11 @@ const GlobalLoadingOverlayContainer: React.FC = () => {
|
|||||||
useEffectReact(() => {
|
useEffectReact(() => {
|
||||||
// When loading starts, start a 30s timer if not already overdue/timed
|
// When loading starts, start a 30s timer if not already overdue/timed
|
||||||
if (loadingCount > 0 && !overdue && timeoutRef.current === null) {
|
if (loadingCount > 0 && !overdue && timeoutRef.current === null) {
|
||||||
timeoutRef.current = window.setTimeout(() => {
|
timeoutRef.current = window.setTimeout(async () => {
|
||||||
// Only trigger if still loading
|
// Only trigger if still loading
|
||||||
if (loadingCount > 0) {
|
if (loadingCount > 0) {
|
||||||
setOverdue(true);
|
setOverdue(true);
|
||||||
// Friendly alert to the user
|
await showAlert(
|
||||||
window.alert(
|
|
||||||
'Loading resources is taking longer than expected and may have partially failed. If you notice any playback issues, please refresh the page to retry downloading the audio files.'
|
'Loading resources is taking longer than expected and may have partially failed. If you notice any playback issues, please refresh the page to retry downloading the audio files.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-37
@@ -31,6 +31,7 @@ import { clearChatHistoryAndUI } from '../util/chatUtil';
|
|||||||
import PianoIcon from './common/icons/PianoIcon';
|
import PianoIcon from './common/icons/PianoIcon';
|
||||||
import MetronomeIcon from './common/icons/MetronomeIcon';
|
import MetronomeIcon from './common/icons/MetronomeIcon';
|
||||||
import { ConfigManager } from '../core/config/ConfigManager';
|
import { ConfigManager } from '../core/config/ConfigManager';
|
||||||
|
import { showAlert, showConfirm, showPrompt } from './common/DialogProvider';
|
||||||
|
|
||||||
const Toolbar: React.FC = () => {
|
const Toolbar: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -91,14 +92,14 @@ const Toolbar: React.FC = () => {
|
|||||||
const exportOptions = ["Export to KGStudio file", "Export to MIDI file", "Export to WAV", "Export to MP3"];
|
const exportOptions = ["Export to KGStudio file", "Export to MIDI file", "Export to WAV", "Export to MP3"];
|
||||||
|
|
||||||
const handleProjectNameClick = async () => {
|
const handleProjectNameClick = async () => {
|
||||||
const newName = prompt("Enter project name:", projectName);
|
const newName = await showPrompt("Enter project name:", projectName);
|
||||||
if (!newName) return;
|
if (!newName) return;
|
||||||
if (!isValidProjectName(newName)) {
|
if (!isValidProjectName(newName)) {
|
||||||
window.alert("Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.");
|
await showAlert("Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isReservedProjectName(newName)) {
|
if (isReservedProjectName(newName)) {
|
||||||
window.alert(`"${RESERVED_PROJECT_NAME}" is a reserved project name. Please choose a different name.`);
|
await showAlert(`"${RESERVED_PROJECT_NAME}" is a reserved project name. Please choose a different name.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ const Toolbar: React.FC = () => {
|
|||||||
const storage = KGProjectStorage.getInstance();
|
const storage = KGProjectStorage.getInstance();
|
||||||
const exists = await storage.exists(newName);
|
const exists = await storage.exists(newName);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = await showConfirm(
|
||||||
`Project "${newName}" already exists. Do you want to overwrite it?`
|
`Project "${newName}" already exists. Do you want to overwrite it?`
|
||||||
);
|
);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
@@ -158,7 +159,7 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading project from ${sourceDescription}:`, error);
|
console.error(`Error loading project from ${sourceDescription}:`, error);
|
||||||
setStatus(`Failed to load project: ${error}`);
|
setStatus(`Failed to load project: ${error}`);
|
||||||
window.alert(`An error occurred while loading the project: ${error}`);
|
await showAlert(`An error occurred while loading the project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,8 +184,8 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handler functions for file operations
|
// Handler functions for file operations
|
||||||
const handleNewProject = () => {
|
const handleNewProject = async () => {
|
||||||
const confirmed = window.confirm("Are you sure you want to create a new project? Any unsaved changes will be lost.");
|
const confirmed = await showConfirm("Are you sure you want to create a new project? Any unsaved changes will be lost.");
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
createNewProject();
|
createNewProject();
|
||||||
}
|
}
|
||||||
@@ -203,14 +204,14 @@ const Toolbar: React.FC = () => {
|
|||||||
const loadedProject = await storage.load(projectNameToLoad);
|
const loadedProject = await storage.load(projectNameToLoad);
|
||||||
|
|
||||||
if (!loadedProject) {
|
if (!loadedProject) {
|
||||||
window.alert(`Project "${projectNameToLoad}" not found.`);
|
await showAlert(`Project "${projectNameToLoad}" not found.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad);
|
await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading project:", error);
|
console.error("Error loading project:", error);
|
||||||
window.alert(`An error occurred while loading the project: ${error}`);
|
await showAlert(`An error occurred while loading the project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,11 +278,11 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error exporting KGStudio file:", error);
|
console.error("Error exporting KGStudio file:", error);
|
||||||
setStatus(`Error exporting project: ${error}`);
|
setStatus(`Error exporting project: ${error}`);
|
||||||
window.alert(`Failed to export project: ${error}`);
|
await showAlert(`Failed to export project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExportMIDI = () => {
|
const handleExportMIDI = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("exporting to MIDI file");
|
console.log("exporting to MIDI file");
|
||||||
}
|
}
|
||||||
@@ -319,7 +320,7 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error exporting MIDI:", error);
|
console.error("Error exporting MIDI:", error);
|
||||||
setStatus(`Error exporting MIDI: ${error}`);
|
setStatus(`Error exporting MIDI: ${error}`);
|
||||||
window.alert(`Failed to export project as MIDI: ${error}`);
|
await showAlert(`Failed to export project as MIDI: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -335,7 +336,7 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error bouncing to WAV:", error);
|
console.error("Error bouncing to WAV:", error);
|
||||||
setStatus(`Error exporting WAV: ${error}`);
|
setStatus(`Error exporting WAV: ${error}`);
|
||||||
window.alert(`Failed to export project as WAV: ${error}`);
|
await showAlert(`Failed to export project as WAV: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -351,7 +352,7 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error bouncing to MP3:", error);
|
console.error("Error bouncing to MP3:", error);
|
||||||
setStatus(`Error exporting MP3: ${error}`);
|
setStatus(`Error exporting MP3: ${error}`);
|
||||||
window.alert(`Failed to export project as MP3: ${error}`);
|
await showAlert(`Failed to export project as MP3: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -387,7 +388,7 @@ const Toolbar: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error importing file:", error);
|
console.error("Error importing file:", error);
|
||||||
setStatus(`Failed to import file: ${error}`);
|
setStatus(`Failed to import file: ${error}`);
|
||||||
window.alert(`Failed to import project file: ${error}`);
|
await showAlert(`Failed to import project file: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -409,7 +410,7 @@ const Toolbar: React.FC = () => {
|
|||||||
console.log("KGStudio file imported successfully:", projectName);
|
console.log("KGStudio file imported successfully:", projectName);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.alert(`The .kgstudio file is corrupted or invalid: ${error}`);
|
await showAlert(`The .kgstudio file is corrupted or invalid: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -536,31 +537,31 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Prompt to change max bars when clicking on current-time display
|
// Prompt to change max bars when clicking on current-time display
|
||||||
const handleCurrentTimeClick = () => {
|
const handleCurrentTimeClick = async () => {
|
||||||
const MIN_BARS = 16;
|
const MIN_BARS = 16;
|
||||||
const newMaxBarsStr = prompt(`Enter new max bars (>= ${MIN_BARS}):`, String(maxBars ?? 32));
|
const newMaxBarsStr = await showPrompt(`Enter new max bars (>= ${MIN_BARS}):`, String(maxBars ?? 32));
|
||||||
if (newMaxBarsStr === null) {
|
if (newMaxBarsStr === null) {
|
||||||
return; // cancelled
|
return; // cancelled
|
||||||
}
|
}
|
||||||
const parsed = parseInt(newMaxBarsStr.trim(), 10);
|
const parsed = parseInt(newMaxBarsStr.trim(), 10);
|
||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
alert('Invalid input. Please enter a valid number.');
|
await showAlert('Invalid input. Please enter a valid number.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parsed < MIN_BARS) {
|
if (parsed < MIN_BARS) {
|
||||||
alert(`Invalid value. Please enter a number >= ${MIN_BARS}.`);
|
await showAlert(`Invalid value. Please enter a number >= ${MIN_BARS}.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMaxBars(parsed);
|
setMaxBars(parsed);
|
||||||
setStatus(`Max bars changed to ${parsed}`);
|
setStatus(`Max bars changed to ${parsed}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBpmClick = () => {
|
const handleBpmClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("BPM clicked, current BPM:", bpm);
|
console.log("BPM clicked, current BPM:", bpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBpmStr = prompt(`Enter new BPM (${TIME_CONSTANTS.MIN_BPM}-${TIME_CONSTANTS.MAX_BPM}):`, bpm.toString());
|
const newBpmStr = await showPrompt(`Enter new BPM (${TIME_CONSTANTS.MIN_BPM}-${TIME_CONSTANTS.MAX_BPM}):`, bpm.toString());
|
||||||
|
|
||||||
// Check if user cancelled
|
// Check if user cancelled
|
||||||
if (newBpmStr === null) {
|
if (newBpmStr === null) {
|
||||||
@@ -572,13 +573,13 @@ const Toolbar: React.FC = () => {
|
|||||||
|
|
||||||
// Check if it's a valid number
|
// Check if it's a valid number
|
||||||
if (isNaN(newBpm)) {
|
if (isNaN(newBpm)) {
|
||||||
alert("Invalid input. Please enter a valid number.");
|
await showAlert("Invalid input. Please enter a valid number.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's within valid range
|
// Check if it's within valid range
|
||||||
if (newBpm <= TIME_CONSTANTS.MIN_BPM || newBpm >= TIME_CONSTANTS.MAX_BPM) {
|
if (newBpm <= TIME_CONSTANTS.MIN_BPM || newBpm >= TIME_CONSTANTS.MAX_BPM) {
|
||||||
alert(`Invalid BPM. Please enter a value between ${TIME_CONSTANTS.MIN_BPM} and ${TIME_CONSTANTS.MAX_BPM}.`);
|
await showAlert(`Invalid BPM. Please enter a value between ${TIME_CONSTANTS.MIN_BPM} and ${TIME_CONSTANTS.MAX_BPM}.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,13 +592,13 @@ const Toolbar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTimeSignatureClick = () => {
|
const handleTimeSignatureClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Time signature clicked, current:", `${timeSignature.numerator}/${timeSignature.denominator}`);
|
console.log("Time signature clicked, current:", `${timeSignature.numerator}/${timeSignature.denominator}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTimeSignatureStr = `${timeSignature.numerator}/${timeSignature.denominator}`;
|
const currentTimeSignatureStr = `${timeSignature.numerator}/${timeSignature.denominator}`;
|
||||||
const newTimeSignatureStr = prompt(`Enter new time signature (numerator/denominator):`, currentTimeSignatureStr);
|
const newTimeSignatureStr = await showPrompt(`Enter new time signature (numerator/denominator):`, currentTimeSignatureStr);
|
||||||
|
|
||||||
// Check if user cancelled
|
// Check if user cancelled
|
||||||
if (newTimeSignatureStr === null) {
|
if (newTimeSignatureStr === null) {
|
||||||
@@ -608,7 +609,7 @@ const Toolbar: React.FC = () => {
|
|||||||
const newTimeSignature = parseTimeSignature(newTimeSignatureStr);
|
const newTimeSignature = parseTimeSignature(newTimeSignatureStr);
|
||||||
|
|
||||||
if (newTimeSignature === null) {
|
if (newTimeSignature === null) {
|
||||||
alert(getTimeSignatureErrorMessage());
|
await showAlert(getTimeSignatureErrorMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,17 +715,17 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle split region button click
|
// Handle split region button click
|
||||||
const handleSplitClick = () => {
|
const handleSplitClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Split button clicked");
|
console.log("Split button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRegionIds.length === 0) {
|
if (selectedRegionIds.length === 0) {
|
||||||
alert("Please select a region to split.");
|
await showAlert("Please select a region to split.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedRegionIds.length > 1) {
|
if (selectedRegionIds.length > 1) {
|
||||||
alert("Please select exactly one region to split.");
|
await showAlert("Please select exactly one region to split.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +738,7 @@ const Toolbar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!targetRegion) {
|
if (!targetRegion) {
|
||||||
alert("Selected region not found.");
|
await showAlert("Selected region not found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,7 +746,7 @@ const Toolbar: React.FC = () => {
|
|||||||
const regionEnd = regionStart + targetRegion.getLength();
|
const regionEnd = regionStart + targetRegion.getLength();
|
||||||
|
|
||||||
if (playheadPosition <= regionStart || playheadPosition >= regionEnd) {
|
if (playheadPosition <= regionStart || playheadPosition >= regionEnd) {
|
||||||
alert("The playhead is not inside the selected region. Move the playhead inside the region before splitting.");
|
await showAlert("The playhead is not inside the selected region. Move the playhead inside the region before splitting.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,13 +761,13 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle undo button click
|
// Handle undo button click
|
||||||
const handleUndoClick = () => {
|
const handleUndoClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Undo button clicked");
|
console.log("Undo button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canUndo) {
|
if (!canUndo) {
|
||||||
alert("Nothing to undo");
|
await showAlert("Nothing to undo");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,13 +781,13 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle redo button click
|
// Handle redo button click
|
||||||
const handleRedoClick = () => {
|
const handleRedoClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Redo button clicked");
|
console.log("Redo button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canRedo) {
|
if (!canRedo) {
|
||||||
alert("Nothing to redo");
|
await showAlert("Nothing to redo");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,7 +831,7 @@ const Toolbar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle Piano button click: open piano roll if closed, targeting active or selected region
|
// Handle Piano button click: open piano roll if closed, targeting active or selected region
|
||||||
const handlePianoButtonClick = () => {
|
const handlePianoButtonClick = async () => {
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log('Piano button clicked');
|
console.log('Piano button clicked');
|
||||||
}
|
}
|
||||||
@@ -849,6 +850,7 @@ const Toolbar: React.FC = () => {
|
|||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log('No active or selected region; piano roll will not open');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10001;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-modal {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 440px;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: dialogFadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dialogFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-close-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #b0b0b0;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-close-btn:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
padding: 24px 20px;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-message {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input:focus {
|
||||||
|
border-color: #5a9fd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-btn-primary {
|
||||||
|
background-color: #5a9fd4;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-btn-primary:hover {
|
||||||
|
background-color: #4a8fc4;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(90, 159, 212, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-btn-cancel {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #b0b0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-btn-cancel:hover {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import React, { useState, useCallback, useRef } from 'react';
|
||||||
|
import './DialogProvider.css';
|
||||||
|
import { FaTimes } from 'react-icons/fa';
|
||||||
|
|
||||||
|
export interface ConfirmOptions {
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromptOptions {
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _showAlertFn: ((message: string) => Promise<void>) | null = null;
|
||||||
|
let _showConfirmFn: ((message: string, options?: ConfirmOptions) => Promise<boolean>) | null = null;
|
||||||
|
let _showPromptFn: ((message: string, defaultValue?: string, options?: PromptOptions) => Promise<string | null>) | null = null;
|
||||||
|
|
||||||
|
export function showAlert(message: string): Promise<void> {
|
||||||
|
if (!_showAlertFn) {
|
||||||
|
window.alert(message);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return _showAlertFn(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showConfirm(message: string, options?: ConfirmOptions): Promise<boolean> {
|
||||||
|
if (!_showConfirmFn) {
|
||||||
|
return Promise.resolve(window.confirm(message));
|
||||||
|
}
|
||||||
|
return _showConfirmFn(message, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showPrompt(message: string, defaultValue?: string, options?: PromptOptions): Promise<string | null> {
|
||||||
|
if (!_showPromptFn) {
|
||||||
|
return Promise.resolve(window.prompt(message, defaultValue) );
|
||||||
|
}
|
||||||
|
return _showPromptFn(message, defaultValue, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DialogInfo {
|
||||||
|
type: 'alert' | 'confirm' | 'prompt';
|
||||||
|
message: string;
|
||||||
|
options?: ConfirmOptions | PromptOptions;
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [dialog, setDialog] = useState<DialogInfo | null>(null);
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const resolveRef = useRef<((value: any) => void) | null>(null);
|
||||||
|
|
||||||
|
const openAlert = useCallback((message: string): Promise<void> => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
resolveRef.current = resolve;
|
||||||
|
setDialog({ type: 'alert', message });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openConfirm = useCallback((message: string, options?: ConfirmOptions): Promise<boolean> => {
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
resolveRef.current = resolve;
|
||||||
|
setDialog({ type: 'confirm', message, options });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openPrompt = useCallback((message: string, defaultValue?: string, options?: PromptOptions): Promise<string | null> => {
|
||||||
|
return new Promise<string | null>((resolve) => {
|
||||||
|
resolveRef.current = resolve;
|
||||||
|
setInputValue(defaultValue ?? '');
|
||||||
|
setDialog({ type: 'prompt', message, options, defaultValue });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const close = useCallback((value: unknown) => {
|
||||||
|
setDialog(null);
|
||||||
|
setInputValue('');
|
||||||
|
if (resolveRef.current) {
|
||||||
|
resolveRef.current(value);
|
||||||
|
resolveRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const registered = useRef(false);
|
||||||
|
if (!registered.current) {
|
||||||
|
registered.current = true;
|
||||||
|
_showAlertFn = openAlert;
|
||||||
|
_showConfirmFn = openConfirm;
|
||||||
|
_showPromptFn = openPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dialog) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAlert = dialog.type === 'alert';
|
||||||
|
const isPrompt = dialog.type === 'prompt';
|
||||||
|
const promptOptions = isPrompt ? (dialog.options as PromptOptions | undefined) : undefined;
|
||||||
|
|
||||||
|
const title = isAlert ? 'Notice' : isPrompt ? 'Input' : 'Confirm';
|
||||||
|
|
||||||
|
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
close(isAlert ? undefined : isPrompt ? null : false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => close(isAlert ? undefined : isPrompt ? null : false);
|
||||||
|
const handleConfirm = () => close(isAlert ? undefined : isPrompt ? inputValue : true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<div className="dialog-overlay" onClick={handleOverlayClick}>
|
||||||
|
<div className="dialog-modal">
|
||||||
|
<div className="dialog-header">
|
||||||
|
<h3 className="dialog-title">{title}</h3>
|
||||||
|
<button
|
||||||
|
className="dialog-close-btn"
|
||||||
|
onClick={handleCancel}
|
||||||
|
aria-label="Close dialog"
|
||||||
|
>
|
||||||
|
<FaTimes />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="dialog-body">
|
||||||
|
<p className="dialog-message">{dialog.message}</p>
|
||||||
|
{isPrompt && (
|
||||||
|
<input
|
||||||
|
className="dialog-input"
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
placeholder={promptOptions?.placeholder}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') handleConfirm();
|
||||||
|
if (e.key === 'Escape') handleCancel();
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="dialog-footer">
|
||||||
|
{!isAlert && (
|
||||||
|
<button
|
||||||
|
className="dialog-btn dialog-btn-cancel"
|
||||||
|
onClick={handleCancel}
|
||||||
|
>
|
||||||
|
{(dialog.options as ConfirmOptions | PromptOptions | undefined)?.cancelLabel ?? 'Cancel'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="dialog-btn dialog-btn-primary"
|
||||||
|
onClick={handleConfirm}
|
||||||
|
autoFocus={!isPrompt}
|
||||||
|
>
|
||||||
|
{isAlert ? 'OK' : ((dialog.options as ConfirmOptions | PromptOptions | undefined)?.confirmLabel ?? (isPrompt ? 'OK' : 'Yes'))}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DialogProvider;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import './FileImportModal.css';
|
import './FileImportModal.css';
|
||||||
import { FaTimes } from 'react-icons/fa';
|
import { FaTimes } from 'react-icons/fa';
|
||||||
|
import { showAlert } from './DialogProvider';
|
||||||
|
|
||||||
interface FileImportModalProps {
|
interface FileImportModalProps {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
@@ -41,7 +42,7 @@ const FileImportModal: React.FC<FileImportModalProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDragOver(false);
|
setIsDragOver(false);
|
||||||
@@ -56,7 +57,7 @@ const FileImportModal: React.FC<FileImportModalProps> = ({
|
|||||||
onFileImport(file);
|
onFileImport(file);
|
||||||
onClose();
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
alert(`Invalid file type. Please select a file with one of these extensions: ${acceptedTypes.join(', ')}`);
|
await showAlert(`Invalid file type. Please select a file with one of these extensions: ${acceptedTypes.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [acceptedTypes, onFileImport, onClose]);
|
}, [acceptedTypes, onFileImport, onClose]);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { FaTimes, FaSortUp, FaSortDown, FaCopy, FaTrash, FaUndo } from 'react-ic
|
|||||||
import { KGProjectStorage, type ProjectMeta } from '../../core/io/KGProjectStorage';
|
import { KGProjectStorage, type ProjectMeta } from '../../core/io/KGProjectStorage';
|
||||||
import { isValidProjectName } from '../../util/projectNameUtil';
|
import { isValidProjectName } from '../../util/projectNameUtil';
|
||||||
import './OpenProjectModal.css';
|
import './OpenProjectModal.css';
|
||||||
|
import { showAlert, showConfirm, showPrompt } from './DialogProvider';
|
||||||
|
|
||||||
interface OpenProjectModalProps {
|
interface OpenProjectModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -115,12 +116,12 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
|
|||||||
|
|
||||||
const handleDuplicate = async (e: React.MouseEvent, projectName: string) => {
|
const handleDuplicate = async (e: React.MouseEvent, projectName: string) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const newName = window.prompt('Enter a name for the duplicated project:', projectName);
|
const newName = await showPrompt('Enter a name for the duplicated project:', projectName);
|
||||||
if (!newName || newName.trim() === '') return;
|
if (!newName || newName.trim() === '') return;
|
||||||
|
|
||||||
const trimmed = newName.trim();
|
const trimmed = newName.trim();
|
||||||
if (!isValidProjectName(trimmed)) {
|
if (!isValidProjectName(trimmed)) {
|
||||||
window.alert('Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.');
|
await showAlert('Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
|
|||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error duplicating project:', error);
|
console.error('Error duplicating project:', error);
|
||||||
window.alert(`Failed to duplicate project: ${error}`);
|
await showAlert(`Failed to duplicate project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,7 +145,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
|
|||||||
await fetchProjects(viewMode);
|
await fetchProjects(viewMode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting project:', error);
|
console.error('Error deleting project:', error);
|
||||||
window.alert(`Failed to delete project: ${error}`);
|
await showAlert(`Failed to delete project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,13 +157,13 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
|
|||||||
await fetchProjects(viewMode);
|
await fetchProjects(viewMode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error restoring project:', error);
|
console.error('Error restoring project:', error);
|
||||||
window.alert(`Failed to restore project: ${error}`);
|
await showAlert(`Failed to restore project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePermanentDelete = async (e: React.MouseEvent, projectName: string) => {
|
const handlePermanentDelete = async (e: React.MouseEvent, projectName: string) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const confirmed = window.confirm(
|
const confirmed = await showConfirm(
|
||||||
`Are you sure you want to permanently delete "${projectName}"?\n\nThis operation cannot be undone.`
|
`Are you sure you want to permanently delete "${projectName}"?\n\nThis operation cannot be undone.`
|
||||||
);
|
);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
@@ -177,7 +178,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error permanently deleting project:', error);
|
console.error('Error permanently deleting project:', error);
|
||||||
window.alert(`Failed to permanently delete project: ${error}`);
|
await showAlert(`Failed to permanently delete project: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ export { default as Playhead } from './Playhead';
|
|||||||
export { default as FileImportModal } from './FileImportModal';
|
export { default as FileImportModal } from './FileImportModal';
|
||||||
export { default as LoadingOverlay } from './LoadingOverlay';
|
export { default as LoadingOverlay } from './LoadingOverlay';
|
||||||
export { default as OpenProjectModal } from './OpenProjectModal';
|
export { default as OpenProjectModal } from './OpenProjectModal';
|
||||||
|
export { default as DialogProvider, showAlert, showConfirm, showPrompt } from './DialogProvider';
|
||||||
|
export type { ConfirmOptions, PromptOptions } from './DialogProvider';
|
||||||
@@ -15,6 +15,7 @@ import { ConfigManager } from '../../core/config/ConfigManager';
|
|||||||
import { beatsToBar } from '../../util/midiUtil';
|
import { beatsToBar } from '../../util/midiUtil';
|
||||||
import { UpdateRegionCommand } from '../../core/commands';
|
import { UpdateRegionCommand } from '../../core/commands';
|
||||||
import { getSuitableChords, noteNameToPitchClass } from '../../util/scaleUtil';
|
import { getSuitableChords, noteNameToPitchClass } from '../../util/scaleUtil';
|
||||||
|
import { showAlert, showPrompt } from '../common/DialogProvider';
|
||||||
|
|
||||||
interface PianoRollProps {
|
interface PianoRollProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -250,7 +251,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
}, [isDragging, isResizing, dragOffset, position]);
|
}, [isDragging, isResizing, dragOffset, position]);
|
||||||
|
|
||||||
// Handle title click to rename the region
|
// Handle title click to rename the region
|
||||||
const handleTitleClick = () => {
|
const handleTitleClick = async () => {
|
||||||
// If we were just dragging, don't show the rename dialog
|
// If we were just dragging, don't show the rename dialog
|
||||||
if (wasDraggingRef.current) {
|
if (wasDraggingRef.current) {
|
||||||
if (DEBUG_MODE.PIANO_ROLL) {
|
if (DEBUG_MODE.PIANO_ROLL) {
|
||||||
@@ -262,7 +263,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
if (!activeRegion) return;
|
if (!activeRegion) return;
|
||||||
|
|
||||||
// Show a prompt to get the new name
|
// Show a prompt to get the new name
|
||||||
const newName = window.prompt("Enter a new name for the region:", activeRegion.getName());
|
const newName = await showPrompt("Enter a new name for the region:", activeRegion.getName());
|
||||||
|
|
||||||
// If the user clicked Cancel or entered an empty string, do nothing
|
// If the user clicked Cancel or entered an empty string, do nothing
|
||||||
if (!newName || newName.trim() === '' || newName === activeRegion.getName()) return;
|
if (!newName || newName.trim() === '' || newName === activeRegion.getName()) return;
|
||||||
@@ -282,8 +283,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error renaming region:', error);
|
console.error('Error renaming region:', error);
|
||||||
// Optionally show user-friendly error message
|
await showAlert('Failed to rename region. Please try again.');
|
||||||
alert('Failed to rename region. Please try again.');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import { ConfigManager } from '../../../core/config/ConfigManager';
|
import { ConfigManager } from '../../../core/config/ConfigManager';
|
||||||
import { validateFunctionalChordsJSON } from '../../../util/scaleUtil';
|
import { validateFunctionalChordsJSON } from '../../../util/scaleUtil';
|
||||||
import { KGCore } from '../../../core/KGCore';
|
import { KGCore } from '../../../core/KGCore';
|
||||||
|
import { showAlert } from '../../common/DialogProvider';
|
||||||
|
|
||||||
const ChordGuideSettings: React.FC = () => {
|
const ChordGuideSettings: React.FC = () => {
|
||||||
const [chordDefinition, setChordDefinition] = useState<string>('');
|
const [chordDefinition, setChordDefinition] = useState<string>('');
|
||||||
@@ -88,7 +89,7 @@ const ChordGuideSettings: React.FC = () => {
|
|||||||
console.log('Loaded default chord template');
|
console.log('Loaded default chord template');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load default template:', error);
|
console.error('Failed to load default template:', error);
|
||||||
alert('Failed to load default template. Please check the console for details.');
|
await showAlert('Failed to load default template. Please check the console for details.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
|
|||||||
import { KGAudioRegion } from '../../core/region/KGAudioRegion';
|
import { KGAudioRegion } from '../../core/region/KGAudioRegion';
|
||||||
import { generateNewRegionName } from '../../util/miscUtil';
|
import { generateNewRegionName } from '../../util/miscUtil';
|
||||||
import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage';
|
import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage';
|
||||||
|
import { showAlert } from '../common/DialogProvider';
|
||||||
import { parseMidiFirstTrackNotes } from '../../util/midiUtil';
|
import { parseMidiFirstTrackNotes } from '../../util/midiUtil';
|
||||||
import * as Tone from 'tone';
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
|
const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
|
||||||
|
|
||||||
// Utility function to create a region at a specific position
|
// Utility function to create a region at a specific position
|
||||||
const createRegionAtPosition = (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
const createRegionAtPosition = async (e: React.MouseEvent<HTMLDivElement>, trackIndex: number) => {
|
||||||
// Get the grid container element
|
// Get the grid container element
|
||||||
const gridContainer = e.currentTarget.closest('.grid-container');
|
const gridContainer = e.currentTarget.closest('.grid-container');
|
||||||
if (!gridContainer) return;
|
if (!gridContainer) return;
|
||||||
@@ -108,7 +109,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
if (DEBUG_MODE.TRACK_GRID_PANEL) {
|
if (DEBUG_MODE.TRACK_GRID_PANEL) {
|
||||||
console.log(`Cannot create region at bar ${barNumber}: overlaps with existing region`);
|
console.log(`Cannot create region at bar ${barNumber}: overlaps with existing region`);
|
||||||
}
|
}
|
||||||
alert('Cannot create region: overlaps with existing region');
|
await showAlert('Cannot create region: overlaps with existing region');
|
||||||
return; // Don't create the region if it overlaps
|
return; // Don't create the region if it overlaps
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +516,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
try {
|
try {
|
||||||
if (track.getType() === TrackType.MIDI) {
|
if (track.getType() === TrackType.MIDI) {
|
||||||
if (!dropData.midiUrl) {
|
if (!dropData.midiUrl) {
|
||||||
window.alert(
|
await showAlert(
|
||||||
'This audio clip can only be imported into an audio track.\n' +
|
'This audio clip can only be imported into an audio track.\n' +
|
||||||
'Please drag it onto an audio track instead.'
|
'Please drag it onto an audio track instead.'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { FLUIDR3_INSTRUMENT_MAP } from '../../constants/generalMidiConstants';
|
|||||||
import { DEBUG_MODE } from '../../constants/uiConstants';
|
import { DEBUG_MODE } from '../../constants/uiConstants';
|
||||||
import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
|
import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
|
||||||
import { AUDIO_INTERFACE_CONSTANTS } from '../../constants/coreConstants';
|
import { AUDIO_INTERFACE_CONSTANTS } from '../../constants/coreConstants';
|
||||||
|
import { showAlert, showConfirm, showPrompt } from '../common/DialogProvider';
|
||||||
interface TrackInfoItemProps {
|
interface TrackInfoItemProps {
|
||||||
track: KGTrack;
|
track: KGTrack;
|
||||||
index: number;
|
index: number;
|
||||||
@@ -97,10 +98,10 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
|
|||||||
}, [allTracks, track]);
|
}, [allTracks, track]);
|
||||||
|
|
||||||
// Handle track name edit within the component
|
// Handle track name edit within the component
|
||||||
const handleTrackNameClick = (e: React.MouseEvent) => {
|
const handleTrackNameClick = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation(); // Prevent opening piano roll when clicking track name
|
e.stopPropagation(); // Prevent opening piano roll when clicking track name
|
||||||
|
|
||||||
const newName = prompt("Enter track name:", track.getName());
|
const newName = await showPrompt("Enter track name:", track.getName());
|
||||||
if (newName) {
|
if (newName) {
|
||||||
// Call the parent handler with the new name
|
// Call the parent handler with the new name
|
||||||
onTrackNameEdit(track, newName);
|
onTrackNameEdit(track, newName);
|
||||||
@@ -233,7 +234,7 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
|
|||||||
// Handle settings action
|
// Handle settings action
|
||||||
const handleSettingsAction = async (action: string) => {
|
const handleSettingsAction = async (action: string) => {
|
||||||
if (action === 'Delete Track') {
|
if (action === 'Delete Track') {
|
||||||
const confirmed = window.confirm(`Are you sure you want to delete track "${track.getName()}"?`);
|
const confirmed = await showConfirm(`Are you sure you want to delete track "${track.getName()}"?`);
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
try {
|
try {
|
||||||
if (DEBUG_MODE.TRACK_INFO) {
|
if (DEBUG_MODE.TRACK_INFO) {
|
||||||
@@ -252,7 +253,7 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
|
|||||||
setShowSettingsDropdown(false);
|
setShowSettingsDropdown(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete track:', error);
|
console.error('Failed to delete track:', error);
|
||||||
alert('Failed to delete track. Please try again.');
|
await showAlert('Failed to delete track. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -4,6 +4,7 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import './styles/variables.css';
|
import './styles/variables.css';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App.tsx';
|
import App from './App.tsx';
|
||||||
|
import DialogProvider from './components/common/DialogProvider';
|
||||||
import { KGCore } from './core/KGCore';
|
import { KGCore } from './core/KGCore';
|
||||||
import { KGAudioInterface } from './core/audio-interface/KGAudioInterface';
|
import { KGAudioInterface } from './core/audio-interface/KGAudioInterface';
|
||||||
import { KGMidiInput } from './core/midi-input/KGMidiInput';
|
import { KGMidiInput } from './core/midi-input/KGMidiInput';
|
||||||
@@ -132,9 +133,11 @@ window.addEventListener('beforeunload', (event) => {
|
|||||||
// Note: Custom messages are no longer supported in modern browsers for security reasons
|
// Note: Custom messages are no longer supported in modern browsers for security reasons
|
||||||
});
|
});
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<DialogProvider>
|
||||||
</StrictMode>,
|
<App />
|
||||||
);
|
</DialogProvider>
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
} // end isSecureContext else
|
} // end isSecureContext else
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { KGProjectStorage } from '../core/io/KGProjectStorage';
|
import { KGProjectStorage } from '../core/io/KGProjectStorage';
|
||||||
import { KGCore } from '../core/KGCore';
|
import { KGCore } from '../core/KGCore';
|
||||||
import { RESERVED_PROJECT_NAME } from './projectNameUtil';
|
import { RESERVED_PROJECT_NAME } from './projectNameUtil';
|
||||||
|
import { showAlert } from '../components/common/DialogProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save project utility function.
|
* Save project utility function.
|
||||||
@@ -59,7 +60,7 @@ export const saveProject = async (
|
|||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving renamed project:', error);
|
console.error('Error saving renamed project:', error);
|
||||||
window.alert(`An error occurred while saving: ${error}`);
|
await showAlert(`An error occurred while saving: ${error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ export const saveProject = async (
|
|||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving project:', error);
|
console.error('Error saving project:', error);
|
||||||
window.alert(`An error occurred while saving: ${error}`);
|
await showAlert(`An error occurred while saving: ${error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user