feat: implemented change loop setting command
This commit is contained in:
@@ -11,6 +11,7 @@ import type { RegionUI } from './interfaces';
|
||||
import { DEBUG_MODE, BAR_NUMBERS_CONSTANTS } from '../constants';
|
||||
import { useRegionOperations } from '../hooks/useRegionOperations';
|
||||
import { regionDeleteManager } from '../util/regionDeleteUtil';
|
||||
import { ChangeLoopSettingsCommand } from '../core/commands';
|
||||
|
||||
interface MainContentProps {
|
||||
onTrackClick?: () => void;
|
||||
@@ -78,6 +79,7 @@ const MainContent: React.FC<MainContentProps> = ({
|
||||
const isLoopDraggingRef = useRef(false);
|
||||
const loopDragStartBarRef = useRef<number | null>(null);
|
||||
const loopDragStartXRef = useRef<number | null>(null);
|
||||
const loopDragOriginalSettingsRef = useRef<{ isLooping: boolean; loopingRange: [number, number] } | null>(null);
|
||||
|
||||
// Effect to verify track updates
|
||||
useEffect(() => {
|
||||
@@ -550,6 +552,12 @@ const MainContent: React.FC<MainContentProps> = ({
|
||||
loopDragStartBarRef.current = startBarIndex;
|
||||
loopDragStartXRef.current = e.clientX;
|
||||
|
||||
// Capture original loop settings for undo/redo
|
||||
loopDragOriginalSettingsRef.current = {
|
||||
isLooping,
|
||||
loopingRange: [...loopingRange] as [number, number]
|
||||
};
|
||||
|
||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
||||
console.log(`Bar numbers mouse down - Start bar: ${startBarIndex} (displayed as bar ${startBarIndex + 1})`);
|
||||
}
|
||||
@@ -599,15 +607,36 @@ const MainContent: React.FC<MainContentProps> = ({
|
||||
if (loopDragStartXRef.current !== null) {
|
||||
const distanceMoved = Math.abs(e.clientX - loopDragStartXRef.current);
|
||||
|
||||
// If dragged beyond threshold, enable looping
|
||||
// If dragged beyond threshold, execute command for undo/redo support
|
||||
if (distanceMoved >= BAR_NUMBERS_CONSTANTS.DRAG_THRESHOLD) {
|
||||
const core = KGCore.instance();
|
||||
const project = core.getCurrentProject();
|
||||
project.setIsLooping(true);
|
||||
useProjectStore.setState({ isLooping: true });
|
||||
const currentIsLooping = core.getCurrentProject().getIsLooping();
|
||||
const currentLoopingRange = core.getCurrentProject().getLoopingRange();
|
||||
|
||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
||||
console.log('Loop range drag ended - Looping auto-enabled');
|
||||
// Only execute command if settings actually changed from original
|
||||
if (loopDragOriginalSettingsRef.current) {
|
||||
const originalSettings = loopDragOriginalSettingsRef.current;
|
||||
const settingsChanged =
|
||||
originalSettings.isLooping !== currentIsLooping ||
|
||||
originalSettings.loopingRange[0] !== currentLoopingRange[0] ||
|
||||
originalSettings.loopingRange[1] !== currentLoopingRange[1];
|
||||
|
||||
if (settingsChanged) {
|
||||
// Revert to original state first (since we updated in real-time)
|
||||
core.getCurrentProject().setIsLooping(originalSettings.isLooping);
|
||||
core.getCurrentProject().setLoopingRange(originalSettings.loopingRange);
|
||||
|
||||
// Now execute command to apply new settings with undo support
|
||||
const command = new ChangeLoopSettingsCommand({
|
||||
isLooping: currentIsLooping,
|
||||
loopingRange: currentLoopingRange
|
||||
});
|
||||
core.executeCommand(command);
|
||||
|
||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
||||
console.log('Loop range drag ended - Command executed for undo/redo');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single click (moved < threshold) - set playhead position
|
||||
@@ -626,6 +655,7 @@ const MainContent: React.FC<MainContentProps> = ({
|
||||
isLoopDraggingRef.current = false;
|
||||
loopDragStartBarRef.current = null;
|
||||
loopDragStartXRef.current = null;
|
||||
loopDragOriginalSettingsRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ 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';
|
||||
@@ -415,7 +416,6 @@ const Toolbar: React.FC = () => {
|
||||
|
||||
const handleLoopToggle = () => {
|
||||
const core = KGCore.instance();
|
||||
const project = core.getCurrentProject();
|
||||
const newLoopingState = !isLooping;
|
||||
let newLoopingRange = loopingRange;
|
||||
|
||||
@@ -445,12 +445,12 @@ const Toolbar: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Update project model
|
||||
project.setIsLooping(newLoopingState);
|
||||
project.setLoopingRange(newLoopingRange);
|
||||
|
||||
// Update store to trigger UI re-render
|
||||
useProjectStore.setState({ isLooping: newLoopingState, loopingRange: newLoopingRange });
|
||||
// Execute command for undo/redo support
|
||||
const command = new ChangeLoopSettingsCommand({
|
||||
isLooping: newLoopingState,
|
||||
loopingRange: newLoopingRange
|
||||
});
|
||||
core.executeCommand(command);
|
||||
|
||||
if (DEBUG_MODE.TOOLBAR) {
|
||||
console.log("Loop toggle clicked, isLooping:", newLoopingState, "range:", newLoopingRange);
|
||||
|
||||
@@ -28,4 +28,5 @@ export { MoveNotesCommand } from './note/MoveNotesCommand';
|
||||
export { PasteNotesCommand } from './note/PasteNotesCommand';
|
||||
|
||||
// Project commands
|
||||
export { ChangeProjectPropertyCommand, type ProjectUpdateProperties } from './project/ChangeProjectPropertyCommand';
|
||||
export { ChangeProjectPropertyCommand, type ProjectUpdateProperties } from './project/ChangeProjectPropertyCommand';
|
||||
export { ChangeLoopSettingsCommand, type LoopSettings } from './project/ChangeLoopSettingsCommand';
|
||||
@@ -0,0 +1,163 @@
|
||||
import { KGCommand } from '../KGCommand';
|
||||
import { KGCore } from '../../KGCore';
|
||||
import { KGProject } from '../../KGProject';
|
||||
import { useProjectStore } from '../../../stores/projectStore';
|
||||
|
||||
/**
|
||||
* Interface defining loop settings that can be updated
|
||||
*/
|
||||
export interface LoopSettings {
|
||||
isLooping?: boolean;
|
||||
loopingRange?: [number, number]; // [startBar, endBar] - bar indices (0-based)
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to update loop settings (isLooping and loopingRange)
|
||||
* Handles updating loop mode and range with undo support
|
||||
*/
|
||||
export class ChangeLoopSettingsCommand extends KGCommand {
|
||||
private newSettings: LoopSettings;
|
||||
private originalSettings: LoopSettings = {};
|
||||
private targetProject: KGProject | null = null;
|
||||
private changedSettings: Set<keyof LoopSettings> = new Set();
|
||||
|
||||
constructor(settings: LoopSettings) {
|
||||
super();
|
||||
this.newSettings = settings;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
const core = KGCore.instance();
|
||||
this.targetProject = core.getCurrentProject();
|
||||
|
||||
// Store original settings for undo
|
||||
this.originalSettings = {
|
||||
isLooping: this.targetProject.getIsLooping(),
|
||||
loopingRange: [...this.targetProject.getLoopingRange()] as [number, number], // Create a copy
|
||||
};
|
||||
|
||||
// Apply updates and track what actually changes
|
||||
const updatedSettings: string[] = [];
|
||||
|
||||
// Update isLooping
|
||||
if (this.newSettings.isLooping !== undefined && this.newSettings.isLooping !== this.originalSettings.isLooping) {
|
||||
this.targetProject.setIsLooping(this.newSettings.isLooping);
|
||||
this.changedSettings.add('isLooping');
|
||||
updatedSettings.push(`isLooping: ${this.originalSettings.isLooping} → ${this.newSettings.isLooping}`);
|
||||
}
|
||||
|
||||
// Update loopingRange
|
||||
if (this.newSettings.loopingRange !== undefined) {
|
||||
const originalRange = this.originalSettings.loopingRange!;
|
||||
const newRange = this.newSettings.loopingRange;
|
||||
|
||||
// Compare loop ranges
|
||||
if (originalRange[0] !== newRange[0] || originalRange[1] !== newRange[1]) {
|
||||
this.targetProject.setLoopingRange(newRange);
|
||||
this.changedSettings.add('loopingRange');
|
||||
updatedSettings.push(`loopingRange: [${originalRange[0]}, ${originalRange[1]}] → [${newRange[0]}, ${newRange[1]}]`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the store to trigger UI re-render
|
||||
const storeUpdate: { isLooping?: boolean; loopingRange?: [number, number] } = {};
|
||||
if (this.changedSettings.has('isLooping') && this.newSettings.isLooping !== undefined) {
|
||||
storeUpdate.isLooping = this.newSettings.isLooping;
|
||||
}
|
||||
if (this.changedSettings.has('loopingRange') && this.newSettings.loopingRange !== undefined) {
|
||||
storeUpdate.loopingRange = this.newSettings.loopingRange;
|
||||
}
|
||||
if (Object.keys(storeUpdate).length > 0) {
|
||||
useProjectStore.setState(storeUpdate);
|
||||
}
|
||||
|
||||
if (updatedSettings.length > 0) {
|
||||
console.log(`Updated loop settings: ${updatedSettings.join(', ')}`);
|
||||
} else {
|
||||
console.log('No changes applied to loop settings');
|
||||
}
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (!this.targetProject) {
|
||||
throw new Error('Cannot undo: no loop settings were updated');
|
||||
}
|
||||
|
||||
// Only restore settings that were actually changed
|
||||
const restoredSettings: string[] = [];
|
||||
|
||||
// Restore isLooping (only if it was changed)
|
||||
if (this.changedSettings.has('isLooping') && this.originalSettings.isLooping !== undefined) {
|
||||
this.targetProject.setIsLooping(this.originalSettings.isLooping);
|
||||
restoredSettings.push(`isLooping: ${this.originalSettings.isLooping}`);
|
||||
}
|
||||
|
||||
// Restore loopingRange (only if it was changed)
|
||||
if (this.changedSettings.has('loopingRange') && this.originalSettings.loopingRange !== undefined) {
|
||||
this.targetProject.setLoopingRange(this.originalSettings.loopingRange);
|
||||
const range = this.originalSettings.loopingRange;
|
||||
restoredSettings.push(`loopingRange: [${range[0]}, ${range[1]}]`);
|
||||
}
|
||||
|
||||
// Update the store to trigger UI re-render
|
||||
const storeUpdate: { isLooping?: boolean; loopingRange?: [number, number] } = {};
|
||||
if (this.changedSettings.has('isLooping') && this.originalSettings.isLooping !== undefined) {
|
||||
storeUpdate.isLooping = this.originalSettings.isLooping;
|
||||
}
|
||||
if (this.changedSettings.has('loopingRange') && this.originalSettings.loopingRange !== undefined) {
|
||||
storeUpdate.loopingRange = this.originalSettings.loopingRange;
|
||||
}
|
||||
if (Object.keys(storeUpdate).length > 0) {
|
||||
useProjectStore.setState(storeUpdate);
|
||||
}
|
||||
|
||||
console.log(`Restored loop settings: ${restoredSettings.join(', ')}`);
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
const updatedSettings: string[] = [];
|
||||
|
||||
if (this.newSettings.isLooping !== undefined) {
|
||||
updatedSettings.push('loop mode');
|
||||
}
|
||||
if (this.newSettings.loopingRange !== undefined) {
|
||||
updatedSettings.push('loop range');
|
||||
}
|
||||
|
||||
if (updatedSettings.length === 1) {
|
||||
return `Change ${updatedSettings[0]}`;
|
||||
} else if (updatedSettings.length > 1) {
|
||||
return `Change loop settings (${updatedSettings.join(', ')})`;
|
||||
}
|
||||
|
||||
return `Change loop settings`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the new settings being applied
|
||||
*/
|
||||
public getNewSettings(): LoopSettings {
|
||||
return this.newSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original settings (only available after execute)
|
||||
*/
|
||||
public getOriginalSettings(): LoopSettings {
|
||||
return this.originalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target project instance (only available after execute)
|
||||
*/
|
||||
public getTargetProject(): KGProject | null {
|
||||
return this.targetProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings that were actually changed (only available after execute)
|
||||
*/
|
||||
public getChangedSettings(): Set<keyof LoopSettings> {
|
||||
return new Set(this.changedSettings);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user