From b80066c2a4ce008c9d3473c99647dac70aae793e Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Sun, 24 May 2026 23:33:54 -0700 Subject: [PATCH] feat: use embedded inputbox for track name editing and region name editing --- src/components/piano-roll/PianoRoll.css | 23 +++++- src/components/piano-roll/PianoRoll.tsx | 66 ++++++++++----- src/components/piano-roll/PianoRollHeader.tsx | 55 ++++++++++--- src/components/track/Track.css | 33 +++++--- src/components/track/TrackInfoItem.tsx | 80 +++++++++++++++---- 5 files changed, 204 insertions(+), 53 deletions(-) diff --git a/src/components/piano-roll/PianoRoll.css b/src/components/piano-roll/PianoRoll.css index 794960d..5cb6bcd 100644 --- a/src/components/piano-roll/PianoRoll.css +++ b/src/components/piano-roll/PianoRoll.css @@ -316,11 +316,32 @@ font-weight: bold; color: #e0e0e0; /* text-transform: uppercase; */ - cursor: pointer; + cursor: text; padding: 5px; border-radius: 3px; } +.piano-roll-title-input { + flex: 1; + min-width: 0; + margin: 0; + padding: 5px 8px; + box-sizing: border-box; + border: 1px solid #5a5a5a; + border-radius: 6px; + outline: none; + background-color: #4a4a4a; + color: #e0e0e0; + font-size: 12px; + font-weight: bold; + text-align: center; +} + +.piano-roll-title-input:focus { + border-color: #5a9fd4; + background-color: #4a4a4a; +} + .close-button { background: transparent; border: none; diff --git a/src/components/piano-roll/PianoRoll.tsx b/src/components/piano-roll/PianoRoll.tsx index 141fe35..fac6e52 100644 --- a/src/components/piano-roll/PianoRoll.tsx +++ b/src/components/piano-roll/PianoRoll.tsx @@ -17,7 +17,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 '../../util/dialogUtil'; +import { showAlert } from '../../util/dialogUtil'; import { normalizeSpectrogramHeightResolution, type SpectrogramHeightResolution, @@ -103,6 +103,9 @@ const PianoRoll: React.FC = ({ const [sheetMusicTrackScopeEnabled, setSheetMusicTrackScopeEnabled] = useState(false); const [sheetQuantization, setSheetQuantization] = useState('16,48'); const [sheetMeasureMetrics, setSheetMeasureMetrics] = useState([]); + const [isEditingTitle, setIsEditingTitle] = useState(false); + const [titleInputValue, setTitleInputValue] = useState(''); + const titleInputRef = useRef(null); // Quantization state const [quantPosition, setQuantPosition] = useState('1/8'); @@ -419,43 +422,64 @@ const PianoRoll: React.FC = ({ }; }, [isDragging, isResizing, dragOffset, position]); - // Handle title click to rename the region - const handleTitleClick = async () => { - // If we were just dragging, don't show the rename dialog - if (wasDraggingRef.current) { - if (DEBUG_MODE.PIANO_ROLL) { - console.log("Skipping rename dialog because the window was just dragged"); - } + useEffect(() => { + if (!isEditingTitle && activeRegion) { + setTitleInputValue(activeRegion.getName()); + } + }, [activeRegion, isEditingTitle]); + + const cancelTitleEdit = () => { + setTitleInputValue(activeRegion?.getName() ?? ''); + setIsEditingTitle(false); + }; + + const commitTitleEdit = async () => { + if (!activeRegion) { + setIsEditingTitle(false); return; } - if (!activeRegion) return; + const newName = titleInputValue.trim(); + setIsEditingTitle(false); + setTitleInputValue(activeRegion.getName()); - // Show a prompt to get the new name - const newName = await showPrompt("Enter a new name for the region:", activeRegion.getName()); + if (!newName || newName === activeRegion.getName()) return; - // If the user clicked Cancel or entered an empty string, do nothing - if (!newName || newName.trim() === '' || newName === activeRegion.getName()) return; - - // Use command pattern to update the region name with undo support try { - const command = new UpdateRegionCommand(activeRegion.getId(), { name: newName.trim() }); + const command = new UpdateRegionCommand(activeRegion.getId(), { name: newName }); KGCore.instance().executeCommand(command); if (DEBUG_MODE.PIANO_ROLL) { console.log(`Executed UpdateRegionCommand: renamed region ${activeRegion.getId()} to "${newName}" using command pattern`); } - // Update the store to trigger re-render const updatedTracks = [...tracks]; useProjectStore.setState({ tracks: updatedTracks }); - } catch (error) { console.error('Error renaming region:', error); await showAlert('Failed to rename region. Please try again.'); } }; + // Handle title click to rename the region + const handleTitleClick = () => { + // If we were just dragging, don't show the rename dialog + if (wasDraggingRef.current) { + if (DEBUG_MODE.PIANO_ROLL) { + console.log("Skipping inline rename because the window was just dragged"); + } + return; + } + + if (!activeRegion) return; + setTitleInputValue(activeRegion.getName()); + setIsEditingTitle(true); + window.setTimeout(() => { + titleInputRef.current?.focus(); + titleInputRef.current?.select(); + }, 0); + }; + // Handle tool selection const handleToolSelect = (tool: 'pointer' | 'pencil') => { setActiveTool(tool); @@ -1274,8 +1298,14 @@ const PianoRoll: React.FC = ({ { void commitTitleEdit(); }} + onTitleCancel={cancelTitleEdit} onMouseDown={(e) => handleMouseDown(e, 'drag')} + titleInputRef={titleInputRef} /> void; title: string; + isEditingTitle: boolean; + titleInputValue: string; onTitleClick: () => void; + onTitleInputChange: (value: string) => void; + onTitleCommit: () => void; + onTitleCancel: () => void; onMouseDown: (e: React.MouseEvent) => void; + titleInputRef: React.RefObject; } const PianoRollHeader: React.FC = ({ onClose, title, + isEditingTitle, + titleInputValue, onTitleClick, - onMouseDown + onTitleInputChange, + onTitleCommit, + onTitleCancel, + onMouseDown, + titleInputRef }) => { return (
-
- {title} -
+ {isEditingTitle ? ( + onTitleInputChange(e.target.value.replace(/\r?\n/g, ' '))} + onBlur={onTitleCommit} + onClick={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + onTitleCommit(); + } else if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + onTitleCancel(); + } + }} + /> + ) : ( +
+ {title} +
+ )}
-
- {track.getName()} -
+ {isEditingTrackName ? ( + setTrackNameInput(e.target.value.replace(/\r?\n/g, ' '))} + onBlur={commitTrackNameEdit} + onClick={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + commitTrackNameEdit(); + } else if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + cancelTrackNameEdit(); + } + }} + /> + ) : ( +
+ {track.getName()} +
+ )}