feat: replace browser built-in alert/confirm/prompt pop-up with new DialogProvider

This commit is contained in:
Xiaohan-Tian
2026-04-19 23:54:26 -07:00
parent 0ac39068b2
commit 1828dbbc63
13 changed files with 392 additions and 70 deletions
+39 -37
View File
@@ -31,6 +31,7 @@ import { clearChatHistoryAndUI } from '../util/chatUtil';
import PianoIcon from './common/icons/PianoIcon';
import MetronomeIcon from './common/icons/MetronomeIcon';
import { ConfigManager } from '../core/config/ConfigManager';
import { showAlert, showConfirm, showPrompt } from './common/DialogProvider';
const Toolbar: React.FC = () => {
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 handleProjectNameClick = async () => {
const newName = prompt("Enter project name:", projectName);
const newName = await showPrompt("Enter project name:", projectName);
if (!newName) return;
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;
}
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;
}
@@ -107,7 +108,7 @@ const Toolbar: React.FC = () => {
const storage = KGProjectStorage.getInstance();
const exists = await storage.exists(newName);
if (exists) {
const confirmed = window.confirm(
const confirmed = await showConfirm(
`Project "${newName}" already exists. Do you want to overwrite it?`
);
if (!confirmed) return;
@@ -158,7 +159,7 @@ const Toolbar: React.FC = () => {
} catch (error) {
console.error(`Error loading project from ${sourceDescription}:`, 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
const handleNewProject = () => {
const confirmed = window.confirm("Are you sure you want to create a new project? Any unsaved changes will be lost.");
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();
}
@@ -203,14 +204,14 @@ const Toolbar: React.FC = () => {
const loadedProject = await storage.load(projectNameToLoad);
if (!loadedProject) {
window.alert(`Project "${projectNameToLoad}" not found.`);
await showAlert(`Project "${projectNameToLoad}" not found.`);
return;
}
await loadProjectFromData(loadedProject, `Project "${projectNameToLoad}"`, projectNameToLoad);
} catch (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) {
console.error("Error exporting KGStudio file:", 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) {
console.log("exporting to MIDI file");
}
@@ -319,7 +320,7 @@ const Toolbar: React.FC = () => {
} catch (error) {
console.error("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) {
console.error("Error bouncing to 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) {
console.error("Error bouncing to 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) {
console.error("Error importing 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);
}
} catch (error) {
window.alert(`The .kgstudio file is corrupted or invalid: ${error}`);
await showAlert(`The .kgstudio file is corrupted or invalid: ${error}`);
throw error;
}
};
@@ -536,31 +537,31 @@ const Toolbar: React.FC = () => {
};
// Prompt to change max bars when clicking on current-time display
const handleCurrentTimeClick = () => {
const handleCurrentTimeClick = async () => {
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) {
return; // cancelled
}
const parsed = parseInt(newMaxBarsStr.trim(), 10);
if (isNaN(parsed)) {
alert('Invalid input. Please enter a valid number.');
await showAlert('Invalid input. Please enter a valid number.');
return;
}
if (parsed < MIN_BARS) {
alert(`Invalid value. Please enter a number >= ${MIN_BARS}.`);
await showAlert(`Invalid value. Please enter a number >= ${MIN_BARS}.`);
return;
}
setMaxBars(parsed);
setStatus(`Max bars changed to ${parsed}`);
};
const handleBpmClick = () => {
const handleBpmClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
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
if (newBpmStr === null) {
@@ -572,13 +573,13 @@ const Toolbar: React.FC = () => {
// Check if it's a valid number
if (isNaN(newBpm)) {
alert("Invalid input. Please enter a valid number.");
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) {
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;
}
@@ -591,13 +592,13 @@ const Toolbar: React.FC = () => {
}
};
const handleTimeSignatureClick = () => {
const handleTimeSignatureClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
console.log("Time signature clicked, current:", `${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
if (newTimeSignatureStr === null) {
@@ -608,7 +609,7 @@ const Toolbar: React.FC = () => {
const newTimeSignature = parseTimeSignature(newTimeSignatureStr);
if (newTimeSignature === null) {
alert(getTimeSignatureErrorMessage());
await showAlert(getTimeSignatureErrorMessage());
return;
}
@@ -714,17 +715,17 @@ const Toolbar: React.FC = () => {
};
// Handle split region button click
const handleSplitClick = () => {
const handleSplitClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
console.log("Split button clicked");
}
if (selectedRegionIds.length === 0) {
alert("Please select a region to split.");
await showAlert("Please select a region to split.");
return;
}
if (selectedRegionIds.length > 1) {
alert("Please select exactly one region to split.");
await showAlert("Please select exactly one region to split.");
return;
}
@@ -737,7 +738,7 @@ const Toolbar: React.FC = () => {
}
if (!targetRegion) {
alert("Selected region not found.");
await showAlert("Selected region not found.");
return;
}
@@ -745,7 +746,7 @@ const Toolbar: React.FC = () => {
const regionEnd = regionStart + targetRegion.getLength();
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;
}
@@ -760,13 +761,13 @@ const Toolbar: React.FC = () => {
};
// Handle undo button click
const handleUndoClick = () => {
const handleUndoClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
console.log("Undo button clicked");
}
if (!canUndo) {
alert("Nothing to undo");
await showAlert("Nothing to undo");
return;
}
@@ -780,13 +781,13 @@ const Toolbar: React.FC = () => {
};
// Handle redo button click
const handleRedoClick = () => {
const handleRedoClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
console.log("Redo button clicked");
}
if (!canRedo) {
alert("Nothing to redo");
await showAlert("Nothing to redo");
return;
}
@@ -830,7 +831,7 @@ const Toolbar: React.FC = () => {
};
// Handle Piano button click: open piano roll if closed, targeting active or selected region
const handlePianoButtonClick = () => {
const handlePianoButtonClick = async () => {
if (DEBUG_MODE.TOOLBAR) {
console.log('Piano button clicked');
}
@@ -849,6 +850,7 @@ const Toolbar: React.FC = () => {
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;
}
+141
View File
@@ -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;
}
+168
View File
@@ -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;
+3 -2
View File
@@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import './FileImportModal.css';
import { FaTimes } from 'react-icons/fa';
import { showAlert } from './DialogProvider';
interface FileImportModalProps {
isVisible: boolean;
@@ -41,7 +42,7 @@ const FileImportModal: React.FC<FileImportModalProps> = ({
e.stopPropagation();
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
const handleDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragOver(false);
@@ -56,7 +57,7 @@ const FileImportModal: React.FC<FileImportModalProps> = ({
onFileImport(file);
onClose();
} 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]);
+8 -7
View File
@@ -3,6 +3,7 @@ import { FaTimes, FaSortUp, FaSortDown, FaCopy, FaTrash, FaUndo } from 'react-ic
import { KGProjectStorage, type ProjectMeta } from '../../core/io/KGProjectStorage';
import { isValidProjectName } from '../../util/projectNameUtil';
import './OpenProjectModal.css';
import { showAlert, showConfirm, showPrompt } from './DialogProvider';
interface OpenProjectModalProps {
onClose: () => void;
@@ -115,12 +116,12 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
const handleDuplicate = async (e: React.MouseEvent, projectName: string) => {
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;
const trimmed = newName.trim();
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;
}
@@ -132,7 +133,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
onClose();
} catch (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);
} catch (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);
} catch (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) => {
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.`
);
if (!confirmed) return;
@@ -177,7 +178,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({ onClose, onOpenProj
}
} catch (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 -1
View File
@@ -2,4 +2,6 @@ export { default as KGDropdown } from './KGDropdown';
export { default as Playhead } from './Playhead';
export { default as FileImportModal } from './FileImportModal';
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';
+4 -4
View File
@@ -15,6 +15,7 @@ import { ConfigManager } from '../../core/config/ConfigManager';
import { beatsToBar } from '../../util/midiUtil';
import { UpdateRegionCommand } from '../../core/commands';
import { getSuitableChords, noteNameToPitchClass } from '../../util/scaleUtil';
import { showAlert, showPrompt } from '../common/DialogProvider';
interface PianoRollProps {
onClose: () => void;
@@ -250,7 +251,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
}, [isDragging, isResizing, dragOffset, position]);
// Handle title click to rename the region
const handleTitleClick = () => {
const handleTitleClick = async () => {
// If we were just dragging, don't show the rename dialog
if (wasDraggingRef.current) {
if (DEBUG_MODE.PIANO_ROLL) {
@@ -262,7 +263,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
if (!activeRegion) return;
// 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 (!newName || newName.trim() === '' || newName === activeRegion.getName()) return;
@@ -282,8 +283,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
} catch (error) {
console.error('Error renaming region:', error);
// Optionally show user-friendly error message
alert('Failed to rename region. Please try again.');
await showAlert('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 { validateFunctionalChordsJSON } from '../../../util/scaleUtil';
import { KGCore } from '../../../core/KGCore';
import { showAlert } from '../../common/DialogProvider';
const ChordGuideSettings: React.FC = () => {
const [chordDefinition, setChordDefinition] = useState<string>('');
@@ -88,7 +89,7 @@ const ChordGuideSettings: React.FC = () => {
console.log('Loaded default chord template');
} catch (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.');
}
};
+4 -3
View File
@@ -13,6 +13,7 @@ import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
import { KGAudioRegion } from '../../core/region/KGAudioRegion';
import { generateNewRegionName } from '../../util/miscUtil';
import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage';
import { showAlert } from '../common/DialogProvider';
import { parseMidiFirstTrackNotes } from '../../util/midiUtil';
import * as Tone from 'tone';
@@ -52,7 +53,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
// 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
const gridContainer = e.currentTarget.closest('.grid-container');
if (!gridContainer) return;
@@ -108,7 +109,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
if (DEBUG_MODE.TRACK_GRID_PANEL) {
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
}
@@ -515,7 +516,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
try {
if (track.getType() === TrackType.MIDI) {
if (!dropData.midiUrl) {
window.alert(
await showAlert(
'This audio clip can only be imported into an audio track.\n' +
'Please drag it onto an audio track instead.'
);
+6 -5
View File
@@ -12,6 +12,7 @@ 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';
import { showAlert, showConfirm, showPrompt } from '../common/DialogProvider';
interface TrackInfoItemProps {
track: KGTrack;
index: number;
@@ -97,10 +98,10 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
}, [allTracks, track]);
// 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
const newName = prompt("Enter track name:", track.getName());
const newName = await showPrompt("Enter track name:", track.getName());
if (newName) {
// Call the parent handler with the new name
onTrackNameEdit(track, newName);
@@ -233,7 +234,7 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
// 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()}"?`);
const confirmed = await showConfirm(`Are you sure you want to delete track "${track.getName()}"?`);
if (confirmed) {
try {
if (DEBUG_MODE.TRACK_INFO) {
@@ -252,7 +253,7 @@ const TrackInfoItem: React.FC<TrackInfoItemProps> = ({
setShowSettingsDropdown(false);
} catch (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.');
}
}
}