feat: added region color customization feature
This commit is contained in:
@@ -8,6 +8,7 @@ import { KGMidiPitchBend } from '../../midi/KGMidiPitchBend';
|
||||
import { KGTrack } from '../../track/KGTrack';
|
||||
import { generateUniqueId } from '../../../util/miscUtil';
|
||||
import { useProjectStore } from '../../../stores/projectStore';
|
||||
import { KGAudioRegion } from '../../region/KGAudioRegion';
|
||||
|
||||
/**
|
||||
* Command to paste regions with their notes to a target track at a specific position
|
||||
@@ -71,6 +72,7 @@ export class PasteRegionsCommand extends KGCommand {
|
||||
newPosition,
|
||||
originalRegion.getLength()
|
||||
);
|
||||
newRegion.setColor(originalRegion.getColor());
|
||||
|
||||
// Copy all notes from the original region
|
||||
const originalNotes = originalRegion.getNotes();
|
||||
@@ -102,6 +104,22 @@ export class PasteRegionsCommand extends KGCommand {
|
||||
});
|
||||
|
||||
console.log(`Created MIDI region "${newRegion.getName()}" with ${originalNotes.length} notes`);
|
||||
} else if (originalRegion instanceof KGAudioRegion) {
|
||||
newRegion = new KGAudioRegion(
|
||||
generateUniqueId('KGAudioRegion'),
|
||||
targetTrack.getId().toString(),
|
||||
targetTrack.getTrackIndex(),
|
||||
`${originalRegion.getName()} (Copy)`,
|
||||
newPosition,
|
||||
originalRegion.getLength(),
|
||||
originalRegion.getAudioFileId(),
|
||||
originalRegion.getAudioFileName(),
|
||||
originalRegion.getAudioDurationSeconds(),
|
||||
originalRegion.getClipStartOffsetSeconds()
|
||||
);
|
||||
newRegion.setColor(originalRegion.getColor());
|
||||
|
||||
console.log(`Created audio region "${newRegion.getName()}"`);
|
||||
} else {
|
||||
// Fallback for other region types
|
||||
newRegion = new KGRegion(
|
||||
@@ -112,6 +130,7 @@ export class PasteRegionsCommand extends KGCommand {
|
||||
newPosition,
|
||||
originalRegion.getLength()
|
||||
);
|
||||
newRegion.setColor(originalRegion.getColor());
|
||||
|
||||
console.log(`Created region "${newRegion.getName()}"`);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ export class SplitRegionCommand extends KGCommand {
|
||||
this.splitAtBeat,
|
||||
regionLength - splitOffsetBeats
|
||||
);
|
||||
region1.setColor(originalRegion.getColor());
|
||||
region2.setColor(originalRegion.getColor());
|
||||
|
||||
for (const note of originalRegion.getNotes()) {
|
||||
if (note.getStartBeat() < splitOffsetBeats) {
|
||||
@@ -167,6 +169,7 @@ export class SplitRegionCommand extends KGCommand {
|
||||
originalRegion.getAudioDurationSeconds(),
|
||||
originalRegion.getClipStartOffsetSeconds()
|
||||
);
|
||||
this.region1.setColor(originalRegion.getColor());
|
||||
|
||||
this.region2 = new KGAudioRegion(
|
||||
generateUniqueId('KGAudioRegion'),
|
||||
@@ -180,6 +183,7 @@ export class SplitRegionCommand extends KGCommand {
|
||||
originalRegion.getAudioDurationSeconds(),
|
||||
originalRegion.getClipStartOffsetSeconds() + splitOffsetSeconds
|
||||
);
|
||||
this.region2.setColor(originalRegion.getColor());
|
||||
|
||||
} else {
|
||||
throw new Error(`Unsupported region type for splitting: ${originalRegion.getCurrentType()}`);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { KGCore } from '../../KGCore';
|
||||
import { KGProject } from '../../KGProject';
|
||||
import { KGTrack } from '../../track/KGTrack';
|
||||
import { KGMidiRegion } from '../../region/KGMidiRegion';
|
||||
import { UpdateRegionCommand } from './UpdateRegionCommand';
|
||||
|
||||
vi.mock('../../KGCore', () => ({
|
||||
KGCore: {
|
||||
instance: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('UpdateRegionCommand', () => {
|
||||
let track: KGTrack;
|
||||
let region: KGMidiRegion;
|
||||
let project: KGProject;
|
||||
const mockCore = {
|
||||
getCurrentProject: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
track = new KGTrack('Track 1', 1);
|
||||
region = new KGMidiRegion('region-1', '1', 0, 'Verse', 0, 4);
|
||||
track.setRegions([region]);
|
||||
project = new KGProject('Test', 32, 0, 125, undefined, undefined, undefined, undefined, undefined, 1, [track], 11);
|
||||
mockCore.getCurrentProject.mockReturnValue(project);
|
||||
vi.mocked(KGCore.instance).mockReturnValue(mockCore as unknown as KGCore);
|
||||
});
|
||||
|
||||
it('updates color and restores it on undo', () => {
|
||||
const command = new UpdateRegionCommand('region-1', { color: '#3D77C9' });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(region.getColor()).toBe('#3D77C9');
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['color']));
|
||||
|
||||
command.undo();
|
||||
|
||||
expect(region.getColor()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('clears an existing region color override', () => {
|
||||
region.setColor('#B43F1D');
|
||||
const command = new UpdateRegionCommand('region-1', { color: null });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(region.getColor()).toBeUndefined();
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['color']));
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { KGTrack } from '../../track/KGTrack';
|
||||
*/
|
||||
export interface RegionUpdateProperties {
|
||||
name?: string;
|
||||
// Future properties can be added here (e.g., color, instrument, etc.)
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +58,7 @@ export class UpdateRegionCommand extends KGCommand {
|
||||
// Store original properties for undo
|
||||
this.originalProperties = {
|
||||
name: this.targetRegion.getName(),
|
||||
color: this.targetRegion.getColor(),
|
||||
};
|
||||
|
||||
// Apply updates and track what actually changes
|
||||
@@ -70,6 +71,12 @@ export class UpdateRegionCommand extends KGCommand {
|
||||
updatedProperties.push(`name: "${this.originalProperties.name}" → "${this.newProperties.name}"`);
|
||||
}
|
||||
|
||||
if ('color' in this.newProperties && this.newProperties.color !== this.originalProperties.color) {
|
||||
this.targetRegion.setColor(this.newProperties.color ?? undefined);
|
||||
this.changedProperties.add('color');
|
||||
updatedProperties.push(`color: ${this.originalProperties.color ?? 'none'} → ${this.newProperties.color ?? 'none'}`);
|
||||
}
|
||||
|
||||
if (updatedProperties.length > 0) {
|
||||
console.log(`Updated region ${this.regionId}: ${updatedProperties.join(', ')}`);
|
||||
} else {
|
||||
@@ -91,6 +98,11 @@ export class UpdateRegionCommand extends KGCommand {
|
||||
restoredProperties.push(`name: "${this.originalProperties.name}"`);
|
||||
}
|
||||
|
||||
if (this.changedProperties.has('color')) {
|
||||
this.targetRegion.setColor(this.originalProperties.color ?? undefined);
|
||||
restoredProperties.push(`color: ${this.originalProperties.color ?? 'none'}`);
|
||||
}
|
||||
|
||||
console.log(`Restored region ${this.regionId}: ${restoredProperties.join(', ')}`);
|
||||
}
|
||||
|
||||
@@ -101,6 +113,9 @@ export class UpdateRegionCommand extends KGCommand {
|
||||
if (this.newProperties.name !== undefined) {
|
||||
updatedProps.push('name');
|
||||
}
|
||||
if ('color' in this.newProperties) {
|
||||
updatedProps.push('color');
|
||||
}
|
||||
|
||||
if (updatedProps.length === 1) {
|
||||
return `Update region "${regionName}" ${updatedProps[0]}`;
|
||||
@@ -152,4 +167,4 @@ export class UpdateRegionCommand extends KGCommand {
|
||||
public getChangedProperties(): Set<keyof RegionUpdateProperties> {
|
||||
return new Set(this.changedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,4 +84,27 @@ describe('UpdateTrackCommand', () => {
|
||||
expect(mockAudioInterface.setTrackSolo).not.toHaveBeenCalled();
|
||||
expect(command.getChangedProperties()).toEqual(new Set());
|
||||
});
|
||||
|
||||
it('updates color and restores it on undo', () => {
|
||||
const command = new UpdateTrackCommand(1, { color: '#3C8AC4' });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(track.getColor()).toBe('#3C8AC4');
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['color']));
|
||||
|
||||
command.undo();
|
||||
|
||||
expect(track.getColor()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('clears an existing color override', () => {
|
||||
track.setColor('#B43F1D');
|
||||
const command = new UpdateTrackCommand(1, { color: null });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(track.getColor()).toBeUndefined();
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['color']));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface TrackUpdateProperties {
|
||||
volume?: number;
|
||||
muted?: boolean;
|
||||
solo?: boolean;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,7 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
volume: this.targetTrack.getVolume(),
|
||||
muted: this.targetTrack.getMuted(),
|
||||
solo: this.targetTrack.getSolo(),
|
||||
color: this.targetTrack.getColor(),
|
||||
};
|
||||
|
||||
// Store original instrument if it's a MIDI track
|
||||
@@ -133,6 +135,16 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
updatedProperties.push(`solo: ${originalSolo} → ${newSolo}`);
|
||||
}
|
||||
|
||||
if ('color' in this.newProperties && this.newProperties.color !== this.originalProperties.color) {
|
||||
const newColor = this.newProperties.color ?? undefined;
|
||||
const originalColor = this.originalProperties.color;
|
||||
|
||||
this.targetTrack.setColor(newColor);
|
||||
|
||||
this.changedProperties.add('color');
|
||||
updatedProperties.push(`color: ${originalColor ?? 'none'} → ${newColor}`);
|
||||
}
|
||||
|
||||
if (updatedProperties.length > 0) {
|
||||
console.log(`Updated track ${this.trackId}: ${updatedProperties.join(', ')}`);
|
||||
} else {
|
||||
@@ -204,6 +216,11 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
restoredProperties.push(`solo: ${this.originalProperties.solo}`);
|
||||
}
|
||||
|
||||
if (this.changedProperties.has('color')) {
|
||||
this.targetTrack.setColor(this.originalProperties.color ?? undefined);
|
||||
restoredProperties.push(`color: ${this.originalProperties.color ?? 'none'}`);
|
||||
}
|
||||
|
||||
console.log(`Restored track ${this.trackId}: ${restoredProperties.join(', ')}`);
|
||||
}
|
||||
|
||||
@@ -229,6 +246,9 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
if (this.newProperties.solo !== undefined) {
|
||||
updatedProps.push('solo');
|
||||
}
|
||||
if ('color' in this.newProperties) {
|
||||
updatedProps.push('color');
|
||||
}
|
||||
|
||||
if (updatedProps.length === 1) {
|
||||
return `Update track "${trackName}" ${updatedProps[0]}`;
|
||||
|
||||
Reference in New Issue
Block a user