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 { DEBUG_MODE, BAR_NUMBERS_CONSTANTS } from '../constants';
|
||||||
import { useRegionOperations } from '../hooks/useRegionOperations';
|
import { useRegionOperations } from '../hooks/useRegionOperations';
|
||||||
import { regionDeleteManager } from '../util/regionDeleteUtil';
|
import { regionDeleteManager } from '../util/regionDeleteUtil';
|
||||||
|
import { ChangeLoopSettingsCommand } from '../core/commands';
|
||||||
|
|
||||||
interface MainContentProps {
|
interface MainContentProps {
|
||||||
onTrackClick?: () => void;
|
onTrackClick?: () => void;
|
||||||
@@ -78,6 +79,7 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
const isLoopDraggingRef = useRef(false);
|
const isLoopDraggingRef = useRef(false);
|
||||||
const loopDragStartBarRef = useRef<number | null>(null);
|
const loopDragStartBarRef = useRef<number | null>(null);
|
||||||
const loopDragStartXRef = 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
|
// Effect to verify track updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -550,6 +552,12 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
loopDragStartBarRef.current = startBarIndex;
|
loopDragStartBarRef.current = startBarIndex;
|
||||||
loopDragStartXRef.current = e.clientX;
|
loopDragStartXRef.current = e.clientX;
|
||||||
|
|
||||||
|
// Capture original loop settings for undo/redo
|
||||||
|
loopDragOriginalSettingsRef.current = {
|
||||||
|
isLooping,
|
||||||
|
loopingRange: [...loopingRange] as [number, number]
|
||||||
|
};
|
||||||
|
|
||||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
if (DEBUG_MODE.MAIN_CONTENT) {
|
||||||
console.log(`Bar numbers mouse down - Start bar: ${startBarIndex} (displayed as bar ${startBarIndex + 1})`);
|
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) {
|
if (loopDragStartXRef.current !== null) {
|
||||||
const distanceMoved = Math.abs(e.clientX - loopDragStartXRef.current);
|
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) {
|
if (distanceMoved >= BAR_NUMBERS_CONSTANTS.DRAG_THRESHOLD) {
|
||||||
const core = KGCore.instance();
|
const core = KGCore.instance();
|
||||||
const project = core.getCurrentProject();
|
const currentIsLooping = core.getCurrentProject().getIsLooping();
|
||||||
project.setIsLooping(true);
|
const currentLoopingRange = core.getCurrentProject().getLoopingRange();
|
||||||
useProjectStore.setState({ isLooping: true });
|
|
||||||
|
|
||||||
if (DEBUG_MODE.MAIN_CONTENT) {
|
// Only execute command if settings actually changed from original
|
||||||
console.log('Loop range drag ended - Looping auto-enabled');
|
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 {
|
} else {
|
||||||
// Single click (moved < threshold) - set playhead position
|
// Single click (moved < threshold) - set playhead position
|
||||||
@@ -626,6 +655,7 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
isLoopDraggingRef.current = false;
|
isLoopDraggingRef.current = false;
|
||||||
loopDragStartBarRef.current = null;
|
loopDragStartBarRef.current = null;
|
||||||
loopDragStartXRef.current = null;
|
loopDragStartXRef.current = null;
|
||||||
|
loopDragOriginalSettingsRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
FaCog
|
FaCog
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { KGProject, type KeySignature } from '../core/KGProject';
|
import { KGProject, type KeySignature } from '../core/KGProject';
|
||||||
|
import { ChangeLoopSettingsCommand } from '../core/commands';
|
||||||
import { plainToInstance, instanceToPlain } from 'class-transformer';
|
import { plainToInstance, instanceToPlain } from 'class-transformer';
|
||||||
import { FaPencil, FaCopy, FaPaste, FaTrash } from 'react-icons/fa6';
|
import { FaPencil, FaCopy, FaPaste, FaTrash } from 'react-icons/fa6';
|
||||||
import { KGMainContentState } from '../core/state/KGMainContentState';
|
import { KGMainContentState } from '../core/state/KGMainContentState';
|
||||||
@@ -415,7 +416,6 @@ const Toolbar: React.FC = () => {
|
|||||||
|
|
||||||
const handleLoopToggle = () => {
|
const handleLoopToggle = () => {
|
||||||
const core = KGCore.instance();
|
const core = KGCore.instance();
|
||||||
const project = core.getCurrentProject();
|
|
||||||
const newLoopingState = !isLooping;
|
const newLoopingState = !isLooping;
|
||||||
let newLoopingRange = loopingRange;
|
let newLoopingRange = loopingRange;
|
||||||
|
|
||||||
@@ -445,12 +445,12 @@ const Toolbar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update project model
|
// Execute command for undo/redo support
|
||||||
project.setIsLooping(newLoopingState);
|
const command = new ChangeLoopSettingsCommand({
|
||||||
project.setLoopingRange(newLoopingRange);
|
isLooping: newLoopingState,
|
||||||
|
loopingRange: newLoopingRange
|
||||||
// Update store to trigger UI re-render
|
});
|
||||||
useProjectStore.setState({ isLooping: newLoopingState, loopingRange: newLoopingRange });
|
core.executeCommand(command);
|
||||||
|
|
||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Loop toggle clicked, isLooping:", newLoopingState, "range:", newLoopingRange);
|
console.log("Loop toggle clicked, isLooping:", newLoopingState, "range:", newLoopingRange);
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ export { MoveNotesCommand } from './note/MoveNotesCommand';
|
|||||||
export { PasteNotesCommand } from './note/PasteNotesCommand';
|
export { PasteNotesCommand } from './note/PasteNotesCommand';
|
||||||
|
|
||||||
// Project commands
|
// 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