feat: added a hotkey for loop button
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"main": {
|
||||
"hold_to_create_region": "ctrl",
|
||||
"play": "space",
|
||||
"loop": "c",
|
||||
"undo": "ctrl+z",
|
||||
"redo": "ctrl+shift+z",
|
||||
"select_all": "ctrl+a",
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
FaCog
|
||||
} from 'react-icons/fa';
|
||||
import { KGProject, type KeySignature } from '../core/KGProject';
|
||||
import { ChangeLoopSettingsCommand } from '../core/commands';
|
||||
import { plainToInstance, instanceToPlain } from 'class-transformer';
|
||||
import { FaPencil, FaCopy, FaPaste, FaTrash } from 'react-icons/fa6';
|
||||
import { KGMainContentState } from '../core/state/KGMainContentState';
|
||||
@@ -34,7 +33,7 @@ const Toolbar: React.FC = () => {
|
||||
isPlaying, startPlaying, stopPlaying, setPlayheadPosition,
|
||||
currentTime, setBpm, setTimeSignature, setKeySignature,
|
||||
maxBars, setMaxBars,
|
||||
isLooping, loopingRange,
|
||||
isLooping, toggleLoop,
|
||||
canUndo, canRedo, undoDescription, redoDescription, undo, redo,
|
||||
toggleChatBox, toggleSettings, cleanupProjectState,
|
||||
// Piano roll state/actions
|
||||
@@ -415,45 +414,9 @@ const Toolbar: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleLoopToggle = () => {
|
||||
const core = KGCore.instance();
|
||||
const newLoopingState = !isLooping;
|
||||
let newLoopingRange = loopingRange;
|
||||
|
||||
// When enabling loop, validate and set the loop range
|
||||
if (newLoopingState) {
|
||||
const currentRange = loopingRange;
|
||||
const projectMaxBars = maxBars;
|
||||
|
||||
// If range is [0, 0], set it to the entire song
|
||||
if (currentRange[0] === 0 && currentRange[1] === 0) {
|
||||
newLoopingRange = [0, projectMaxBars] as [number, number];
|
||||
if (DEBUG_MODE.TOOLBAR) {
|
||||
console.log("Loop range auto-set to entire song:", newLoopingRange);
|
||||
}
|
||||
} else {
|
||||
// Validate range is within [0, maxBars]
|
||||
const validatedStart = Math.max(0, Math.min(currentRange[0], projectMaxBars));
|
||||
const validatedEnd = Math.max(0, Math.min(currentRange[1], projectMaxBars));
|
||||
|
||||
// If range changed, update it
|
||||
if (validatedStart !== currentRange[0] || validatedEnd !== currentRange[1]) {
|
||||
newLoopingRange = [validatedStart, validatedEnd] as [number, number];
|
||||
if (DEBUG_MODE.TOOLBAR) {
|
||||
console.log("Loop range clamped to valid range:", newLoopingRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute command for undo/redo support
|
||||
const command = new ChangeLoopSettingsCommand({
|
||||
isLooping: newLoopingState,
|
||||
loopingRange: newLoopingRange
|
||||
});
|
||||
core.executeCommand(command);
|
||||
|
||||
toggleLoop();
|
||||
if (DEBUG_MODE.TOOLBAR) {
|
||||
console.log("Loop toggle clicked, isLooping:", newLoopingState, "range:", newLoopingRange);
|
||||
console.log("Loop toggle clicked");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ interface AppConfig {
|
||||
main: {
|
||||
hold_to_create_region: string;
|
||||
play: string;
|
||||
loop: string;
|
||||
undo: string;
|
||||
redo: string;
|
||||
select_all: string;
|
||||
@@ -205,6 +206,7 @@ export class ConfigManager {
|
||||
main: {
|
||||
hold_to_create_region: 'ctrl',
|
||||
play: 'space',
|
||||
loop: 'c',
|
||||
undo: 'ctrl+z',
|
||||
redo: 'ctrl+shift+z',
|
||||
select_all: 'ctrl+a',
|
||||
|
||||
@@ -11,7 +11,7 @@ import { selectAllNotesInActiveRegion } from '../util/selectionUtil';
|
||||
* Handles keyboard shortcuts defined in the configuration
|
||||
*/
|
||||
export const useGlobalKeyboardHandler = () => {
|
||||
const { undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, projectName } = useProjectStore();
|
||||
const { undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, toggleLoop, projectName } = useProjectStore();
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -59,6 +59,7 @@ export const useGlobalKeyboardHandler = () => {
|
||||
const pasteShortcut = configManager.get('hotkeys.main.paste') as string;
|
||||
const selectAllShortcut = configManager.get('hotkeys.main.select_all') as string;
|
||||
const playShortcut = configManager.get('hotkeys.main.play') as string;
|
||||
const loopShortcut = configManager.get('hotkeys.main.loop') as string;
|
||||
const saveShortcut = configManager.get('hotkeys.main.save') as string;
|
||||
|
||||
// Check for undo shortcut
|
||||
@@ -136,6 +137,19 @@ export const useGlobalKeyboardHandler = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for loop toggle shortcut
|
||||
if (loopShortcut && matchesKeyboardShortcut(event, loopShortcut)) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
toggleLoop();
|
||||
setStatus('Loop toggled');
|
||||
} catch (error) {
|
||||
console.error('Loop toggle failed:', error);
|
||||
setStatus('Loop toggle failed');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for save shortcut
|
||||
if (saveShortcut && matchesKeyboardShortcut(event, saveShortcut)) {
|
||||
event.preventDefault();
|
||||
@@ -156,5 +170,5 @@ export const useGlobalKeyboardHandler = () => {
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown, { capture: true });
|
||||
};
|
||||
}, [undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, projectName]); // Include dependencies for store actions
|
||||
}, [undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, toggleLoop, projectName]); // Include dependencies for store actions
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { KGRegion } from '../core/region/KGRegion';
|
||||
import { AddTrackCommand, RemoveTrackCommand, ReorderTracksCommand, UpdateTrackCommand, type TrackUpdateProperties, PasteRegionsCommand, PasteNotesCommand, ChangeProjectPropertyCommand } from '../core/commands';
|
||||
import { ConfigManager } from '../core/config/ConfigManager';
|
||||
import { upgradeProjectToLatest } from '../core/project-upgrader/KGProjectUpgrader';
|
||||
import { toggleLoop } from '../util/loopUtil';
|
||||
|
||||
/**
|
||||
* Update CSS custom property for time signature numerator
|
||||
@@ -86,6 +87,7 @@ interface ProjectState {
|
||||
setPlayheadPosition: (position: number) => void;
|
||||
startPlaying: () => Promise<void>;
|
||||
stopPlaying: () => Promise<void>;
|
||||
toggleLoop: () => void;
|
||||
setBpm: (bpm: number) => void;
|
||||
setMaxBars: (maxBars: number) => void;
|
||||
setTimeSignature: (timeSignature: TimeSignature) => void;
|
||||
@@ -570,6 +572,11 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
set({ isPlaying: false });
|
||||
},
|
||||
|
||||
toggleLoop: () => {
|
||||
const { isLooping, loopingRange, maxBars } = get();
|
||||
toggleLoop(isLooping, loopingRange, maxBars);
|
||||
},
|
||||
|
||||
setBpm: (bpm: number) => {
|
||||
try {
|
||||
// Create and execute the change project property command
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { KGCore } from '../core/KGCore';
|
||||
import { ChangeLoopSettingsCommand } from '../core/commands';
|
||||
|
||||
/**
|
||||
* Toggle the loop mode on/off with proper validation.
|
||||
* When enabling loop mode:
|
||||
* - If loop range is [0, 0], sets it to the entire song [0, maxBars]
|
||||
* - Validates that loop range is within [0, maxBars]
|
||||
* Uses the command pattern for undo/redo support.
|
||||
*
|
||||
* @param currentIsLooping Current loop mode state
|
||||
* @param currentLoopingRange Current loop range [startBar, endBar]
|
||||
* @param maxBars Maximum number of bars in the project
|
||||
*/
|
||||
export const toggleLoop = (
|
||||
currentIsLooping: boolean,
|
||||
currentLoopingRange: [number, number],
|
||||
maxBars: number
|
||||
): void => {
|
||||
const newLoopingState = !currentIsLooping;
|
||||
let newLoopingRange = currentLoopingRange;
|
||||
|
||||
// When enabling loop, validate and set the loop range
|
||||
if (newLoopingState) {
|
||||
const currentRange = currentLoopingRange;
|
||||
const projectMaxBars = maxBars;
|
||||
|
||||
// If range is [0, 0], set it to the entire song
|
||||
if (currentRange[0] === 0 && currentRange[1] === 0) {
|
||||
newLoopingRange = [0, projectMaxBars] as [number, number];
|
||||
} else {
|
||||
// Validate range is within [0, maxBars]
|
||||
const validatedStart = Math.max(0, Math.min(currentRange[0], projectMaxBars));
|
||||
const validatedEnd = Math.max(0, Math.min(currentRange[1], projectMaxBars));
|
||||
|
||||
// If range changed, update it
|
||||
if (validatedStart !== currentRange[0] || validatedEnd !== currentRange[1]) {
|
||||
newLoopingRange = [validatedStart, validatedEnd] as [number, number];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute command for undo/redo support
|
||||
const command = new ChangeLoopSettingsCommand({
|
||||
isLooping: newLoopingState,
|
||||
loopingRange: newLoopingRange
|
||||
});
|
||||
KGCore.instance().executeCommand(command);
|
||||
};
|
||||
Reference in New Issue
Block a user