import { KGCommand } from '../KGCommand'; import { KGCore } from '../../KGCore'; import { KGProject, type KeySignature } from '../../KGProject'; import type { TimeSignature } from '../../../types/projectTypes'; import { normalizeTempoRegionsForProject } from '../../../util/globalTrackUtil'; /** * Interface defining properties that can be updated on a project */ export interface ProjectUpdateProperties { name?: string; maxBars?: number; currentBars?: number; bpm?: number; timeSignature?: TimeSignature; keySignature?: KeySignature; selectedMode?: string; } /** * Command to update project properties * Handles updating project name, BPM, time signature, etc. with undo support */ export class ChangeProjectPropertyCommand extends KGCommand { private newProperties: ProjectUpdateProperties; private originalProperties: ProjectUpdateProperties = {}; private targetProject: KGProject | null = null; private changedProperties: Set = new Set(); constructor(properties: ProjectUpdateProperties) { super(); this.newProperties = properties; } execute(): void { const core = KGCore.instance(); this.targetProject = core.getCurrentProject(); // Store original properties for undo this.originalProperties = { name: this.targetProject.getName(), maxBars: this.targetProject.getMaxBars(), currentBars: this.targetProject.getCurrentBars(), bpm: this.targetProject.getBpm(), timeSignature: { ...this.targetProject.getTimeSignature() }, // Create a copy keySignature: this.targetProject.getKeySignature(), selectedMode: this.targetProject.getSelectedMode(), }; // Apply updates and track what actually changes const updatedProperties: string[] = []; // Update name if (this.newProperties.name !== undefined && this.newProperties.name !== this.originalProperties.name) { this.targetProject.setName(this.newProperties.name); this.changedProperties.add('name'); updatedProperties.push(`name: "${this.originalProperties.name}" → "${this.newProperties.name}"`); } // Update maxBars if (this.newProperties.maxBars !== undefined && this.newProperties.maxBars !== this.originalProperties.maxBars) { this.targetProject.setMaxBars(this.newProperties.maxBars); normalizeTempoRegionsForProject(this.targetProject); this.changedProperties.add('maxBars'); updatedProperties.push(`maxBars: ${this.originalProperties.maxBars} → ${this.newProperties.maxBars}`); } // Update currentBars if (this.newProperties.currentBars !== undefined && this.newProperties.currentBars !== this.originalProperties.currentBars) { this.targetProject.setCurrentBars(this.newProperties.currentBars); this.changedProperties.add('currentBars'); updatedProperties.push(`currentBars: ${this.originalProperties.currentBars} → ${this.newProperties.currentBars}`); } // Update BPM if (this.newProperties.bpm !== undefined && this.newProperties.bpm !== this.originalProperties.bpm) { this.targetProject.setBpm(this.newProperties.bpm); this.changedProperties.add('bpm'); updatedProperties.push(`bpm: ${this.originalProperties.bpm} → ${this.newProperties.bpm}`); } // Update time signature if (this.newProperties.timeSignature !== undefined) { const originalTS = this.originalProperties.timeSignature!; const newTS = this.newProperties.timeSignature; // Compare time signatures if (originalTS.numerator !== newTS.numerator || originalTS.denominator !== newTS.denominator) { this.targetProject.setTimeSignature(newTS); normalizeTempoRegionsForProject(this.targetProject); this.changedProperties.add('timeSignature'); updatedProperties.push(`timeSignature: ${originalTS.numerator}/${originalTS.denominator} → ${newTS.numerator}/${newTS.denominator}`); } } // Update key signature if (this.newProperties.keySignature !== undefined && this.newProperties.keySignature !== this.originalProperties.keySignature) { this.targetProject.setKeySignature(this.newProperties.keySignature); this.changedProperties.add('keySignature'); updatedProperties.push(`keySignature: "${this.originalProperties.keySignature}" → "${this.newProperties.keySignature}"`); } // Update selected mode if (this.newProperties.selectedMode !== undefined && this.newProperties.selectedMode !== this.originalProperties.selectedMode) { this.targetProject.setSelectedMode(this.newProperties.selectedMode); this.changedProperties.add('selectedMode'); updatedProperties.push(`selectedMode: "${this.originalProperties.selectedMode}" → "${this.newProperties.selectedMode}"`); } if (updatedProperties.length > 0) { console.log(`Updated project: ${updatedProperties.join(', ')}`); } else { console.log('No changes applied to project'); } } undo(): void { if (!this.targetProject) { throw new Error('Cannot undo: no project was updated'); } // Only restore properties that were actually changed const restoredProperties: string[] = []; // Restore name (only if it was changed) if (this.changedProperties.has('name') && this.originalProperties.name !== undefined) { this.targetProject.setName(this.originalProperties.name); restoredProperties.push(`name: "${this.originalProperties.name}"`); } // Restore maxBars (only if it was changed) if (this.changedProperties.has('maxBars') && this.originalProperties.maxBars !== undefined) { this.targetProject.setMaxBars(this.originalProperties.maxBars); normalizeTempoRegionsForProject(this.targetProject); restoredProperties.push(`maxBars: ${this.originalProperties.maxBars}`); } // Restore currentBars (only if it was changed) if (this.changedProperties.has('currentBars') && this.originalProperties.currentBars !== undefined) { this.targetProject.setCurrentBars(this.originalProperties.currentBars); restoredProperties.push(`currentBars: ${this.originalProperties.currentBars}`); } // Restore BPM (only if it was changed) if (this.changedProperties.has('bpm') && this.originalProperties.bpm !== undefined) { this.targetProject.setBpm(this.originalProperties.bpm); restoredProperties.push(`bpm: ${this.originalProperties.bpm}`); } // Restore time signature (only if it was changed) if (this.changedProperties.has('timeSignature') && this.originalProperties.timeSignature !== undefined) { this.targetProject.setTimeSignature(this.originalProperties.timeSignature); normalizeTempoRegionsForProject(this.targetProject); const ts = this.originalProperties.timeSignature; restoredProperties.push(`timeSignature: ${ts.numerator}/${ts.denominator}`); } // Restore key signature (only if it was changed) if (this.changedProperties.has('keySignature') && this.originalProperties.keySignature !== undefined) { this.targetProject.setKeySignature(this.originalProperties.keySignature); restoredProperties.push(`keySignature: "${this.originalProperties.keySignature}"`); } // Restore selected mode (only if it was changed) if (this.changedProperties.has('selectedMode') && this.originalProperties.selectedMode !== undefined) { this.targetProject.setSelectedMode(this.originalProperties.selectedMode); restoredProperties.push(`selectedMode: "${this.originalProperties.selectedMode}"`); } console.log(`Restored project: ${restoredProperties.join(', ')}`); } getDescription(): string { const updatedProps: string[] = []; if (this.newProperties.name !== undefined) { updatedProps.push('name'); } if (this.newProperties.maxBars !== undefined) { updatedProps.push('maxBars'); } if (this.newProperties.currentBars !== undefined) { updatedProps.push('currentBars'); } if (this.newProperties.bpm !== undefined) { updatedProps.push('BPM'); } if (this.newProperties.timeSignature !== undefined) { updatedProps.push('time signature'); } if (this.newProperties.keySignature !== undefined) { updatedProps.push('key signature'); } if (this.newProperties.selectedMode !== undefined) { updatedProps.push('selected mode'); } if (updatedProps.length === 1) { return `Change project ${updatedProps[0]}`; } else if (updatedProps.length > 1) { return `Change project properties (${updatedProps.join(', ')})`; } return `Change project properties`; } /** * Get the new properties being applied */ public getNewProperties(): ProjectUpdateProperties { return this.newProperties; } /** * Get the original properties (only available after execute) */ public getOriginalProperties(): ProjectUpdateProperties { return this.originalProperties; } /** * Get the target project instance (only available after execute) */ public getTargetProject(): KGProject | null { return this.targetProject; } /** * Get the properties that were actually changed (only available after execute) */ public getChangedProperties(): Set { return new Set(this.changedProperties); } }