feat: misc behavior changes
This commit is contained in:
@@ -843,7 +843,9 @@ const RegionEventListTab: React.FC<RegionEventListTabProps> = ({ activeMidiRegio
|
||||
? lastSelectedNote.getEndBeat() - lastSelectedNote.getStartBeat()
|
||||
: KGPianoRollState.instance().getLastEditedNoteLength();
|
||||
const defaultPitch = lastSelectedNote ? lastSelectedNote.getPitch() : noteNameToPitch('C4');
|
||||
const defaultVelocity = lastSelectedNote ? lastSelectedNote.getVelocity() : 127;
|
||||
const defaultVelocity = lastSelectedNote
|
||||
? lastSelectedNote.getVelocity()
|
||||
: KGPianoRollState.instance().getLastEditedNoteVelocity();
|
||||
|
||||
const command = new CreateNoteCommand(
|
||||
activeMidiRegion.getId(),
|
||||
@@ -855,6 +857,7 @@ const RegionEventListTab: React.FC<RegionEventListTabProps> = ({ activeMidiRegio
|
||||
|
||||
KGCore.instance().executeCommand(command);
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(defaultLength);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(defaultVelocity);
|
||||
const createdNote = command.getCreatedNote();
|
||||
if (createdNote) {
|
||||
createdNote.select();
|
||||
|
||||
@@ -55,6 +55,7 @@ export class KGPianoRollState {
|
||||
private activeTool: string = "pointer";
|
||||
private currentSnap: PianoRollSnapValue = PIANO_ROLL_NO_SNAP;
|
||||
private lastEditedNoteLength: number = 1; // Default to 1 beat
|
||||
private lastEditedNoteVelocity: number = 127;
|
||||
private currentMode: string = "ionian"; // Default mode
|
||||
private automationViewEnabled: boolean = false;
|
||||
private currentAutomationType: string = "pitch-bend";
|
||||
@@ -108,6 +109,14 @@ export class KGPianoRollState {
|
||||
this.lastEditedNoteLength = length;
|
||||
}
|
||||
|
||||
public getLastEditedNoteVelocity(): number {
|
||||
return this.lastEditedNoteVelocity;
|
||||
}
|
||||
|
||||
public setLastEditedNoteVelocity(velocity: number): void {
|
||||
this.lastEditedNoteVelocity = velocity;
|
||||
}
|
||||
|
||||
public getCurrentMode(): string {
|
||||
return this.currentMode;
|
||||
}
|
||||
|
||||
@@ -59,10 +59,22 @@ vi.mock('../stores/projectStore', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../core/audio-interface/KGAudioInterface', () => ({
|
||||
KGAudioInterface: {
|
||||
instance: () => ({
|
||||
getIsInitialized: () => false,
|
||||
getIsAudioContextStarted: () => false,
|
||||
startAudioContext: vi.fn(),
|
||||
triggerNote: vi.fn(),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
import { useNoteOperations } from './useNoteOperations';
|
||||
import { KGCore } from '../core/KGCore';
|
||||
import { KGPianoRollState } from '../core/state/KGPianoRollState';
|
||||
import { MoveNotesCommand, ResizeNotesCommand } from '../core/commands';
|
||||
import { CreateNoteCommand, MoveNotesCommand, ResizeNotesCommand } from '../core/commands';
|
||||
import { CreateNotesCommand } from '../core/commands/note/CreateNotesCommand';
|
||||
import { useProjectStore } from '../stores/projectStore';
|
||||
import { createMockMidiNote, createMockMidiRegion, createMockMidiTrack } from '../test/utils/mock-data';
|
||||
|
||||
@@ -91,20 +103,157 @@ describe('useNoteOperations', () => {
|
||||
|
||||
KGPianoRollState.instance().setActiveTool('pointer');
|
||||
KGPianoRollState.instance().setCurrentSnap('1/4');
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(1);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(127);
|
||||
KGPianoRollState.instance().setCurrentMatchingChords([]);
|
||||
KGPianoRollState.instance().setCurrentSelectedChordIndex(0);
|
||||
KGPianoRollState.instance().setCurrentChordCursorPitch(null);
|
||||
});
|
||||
|
||||
const renderNoteOperations = (activeRegion: ReturnType<typeof createMockMidiRegion>, track = createMockMidiTrack({ id: 1, regions: [activeRegion] }), updateTrack = vi.fn()) => {
|
||||
const createPianoGridRef = () => ({
|
||||
current: {
|
||||
getBoundingClientRect: () => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 800,
|
||||
bottom: 600,
|
||||
width: 800,
|
||||
height: 600,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => ({}),
|
||||
}),
|
||||
} as HTMLDivElement,
|
||||
});
|
||||
|
||||
const createGridClickEvent = (overrides: Partial<React.MouseEvent> = {}): React.MouseEvent => ({
|
||||
clientX: 80,
|
||||
clientY: 120,
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
...overrides,
|
||||
} as React.MouseEvent);
|
||||
|
||||
const renderNoteOperations = (
|
||||
activeRegion: ReturnType<typeof createMockMidiRegion>,
|
||||
track = createMockMidiTrack({ id: 1, regions: [activeRegion] }),
|
||||
updateTrack = vi.fn(),
|
||||
pianoGridRef = createPianoGridRef(),
|
||||
) => {
|
||||
const hook = renderHook(() => useNoteOperations({
|
||||
activeRegion,
|
||||
timeSignature: { numerator: 4, denominator: 4 },
|
||||
updateTrack,
|
||||
tracks: [track],
|
||||
pianoGridRef: { current: null },
|
||||
pianoGridRef,
|
||||
}));
|
||||
|
||||
return { ...hook, track, updateTrack };
|
||||
return { ...hook, track, updateTrack, pianoGridRef };
|
||||
};
|
||||
|
||||
it('uses the last selected note velocity when creating after deselecting', () => {
|
||||
const selectedNote = createMockMidiNote({ id: 'note-a', velocity: 91, pitch: 60 });
|
||||
const activeRegion = createMockMidiRegion({
|
||||
id: 'region-1',
|
||||
trackId: '1',
|
||||
notes: [selectedNote],
|
||||
});
|
||||
|
||||
selectedNote.select();
|
||||
KGCore.instance().addSelectedItem(selectedNote);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(selectedNote.getVelocity());
|
||||
KGCore.instance().clearSelectedItems();
|
||||
|
||||
const { result } = renderNoteOperations(activeRegion);
|
||||
|
||||
act(() => {
|
||||
result.current.handleGridDoubleClick(createGridClickEvent());
|
||||
});
|
||||
|
||||
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
|
||||
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
|
||||
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
|
||||
expect(createCommand.velocity).toBe(91);
|
||||
});
|
||||
|
||||
it('uses the most recently selected note velocity after deselecting a multi-selection', () => {
|
||||
const noteA = createMockMidiNote({ id: 'note-a', velocity: 40, pitch: 60 });
|
||||
const noteB = createMockMidiNote({ id: 'note-b', velocity: 105, pitch: 64 });
|
||||
const activeRegion = createMockMidiRegion({
|
||||
id: 'region-1',
|
||||
trackId: '1',
|
||||
notes: [noteA, noteB],
|
||||
});
|
||||
|
||||
noteA.select();
|
||||
noteB.select();
|
||||
KGCore.instance().addSelectedItem(noteA);
|
||||
KGCore.instance().addSelectedItem(noteB);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(noteB.getVelocity());
|
||||
KGCore.instance().clearSelectedItems();
|
||||
|
||||
const { result } = renderNoteOperations(activeRegion);
|
||||
|
||||
act(() => {
|
||||
result.current.handleGridDoubleClick(createGridClickEvent());
|
||||
});
|
||||
|
||||
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
|
||||
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
|
||||
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
|
||||
expect(createCommand.velocity).toBe(105);
|
||||
});
|
||||
|
||||
it('falls back to velocity 127 when creating a manual note with no selection', () => {
|
||||
const activeRegion = createMockMidiRegion({
|
||||
id: 'region-1',
|
||||
trackId: '1',
|
||||
notes: [],
|
||||
});
|
||||
|
||||
const { result } = renderNoteOperations(activeRegion);
|
||||
|
||||
act(() => {
|
||||
result.current.handleGridDoubleClick(createGridClickEvent());
|
||||
});
|
||||
|
||||
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
|
||||
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNoteCommand & { velocity: number };
|
||||
expect(createCommand).toBeInstanceOf(CreateNoteCommand);
|
||||
expect(createCommand.velocity).toBe(127);
|
||||
});
|
||||
|
||||
it('applies the cached velocity to every note in manual chord creation after deselecting', () => {
|
||||
const selectedNote = createMockMidiNote({ id: 'note-a', velocity: 73, pitch: 60 });
|
||||
const activeRegion = createMockMidiRegion({
|
||||
id: 'region-1',
|
||||
trackId: '1',
|
||||
notes: [selectedNote],
|
||||
});
|
||||
|
||||
selectedNote.select();
|
||||
KGCore.instance().addSelectedItem(selectedNote);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(selectedNote.getVelocity());
|
||||
KGCore.instance().clearSelectedItems();
|
||||
KGPianoRollState.instance().setCurrentMatchingChords([[0, 4, 7]]);
|
||||
KGPianoRollState.instance().setCurrentSelectedChordIndex(0);
|
||||
KGPianoRollState.instance().setCurrentChordCursorPitch(72);
|
||||
|
||||
const { result } = renderNoteOperations(activeRegion);
|
||||
|
||||
act(() => {
|
||||
result.current.handleGridDoubleClick(createGridClickEvent({ clientY: 706 }));
|
||||
});
|
||||
|
||||
expect(coreState.executeCommand).toHaveBeenCalledTimes(1);
|
||||
const createCommand = coreState.executeCommand.mock.calls[0][0] as CreateNotesCommand;
|
||||
expect(createCommand).toBeInstanceOf(CreateNotesCommand);
|
||||
expect(createCommand.getNoteCreationData()).toHaveLength(3);
|
||||
expect(createCommand.getNoteCreationData().every(note => note.velocity === 73)).toBe(true);
|
||||
});
|
||||
|
||||
it('selects the grabbed note before resizing when it was not part of the current selection', () => {
|
||||
const noteA = createMockMidiNote({ id: 'note-a', startBeat: 0, endBeat: 1, pitch: 60 });
|
||||
const noteB = createMockMidiNote({ id: 'note-b', startBeat: 1, endBeat: 2, pitch: 62 });
|
||||
|
||||
@@ -192,10 +192,10 @@ export const useNoteOperations = ({
|
||||
// Calculate note timing relative to the region
|
||||
const regionStartBeat = activeRegion.getStartFromBeat();
|
||||
const noteStartBeat = beatNumber - regionStartBeat; // relative beat position
|
||||
const lastEditedLength = KGPianoRollState.instance().getLastEditedNoteLength();
|
||||
const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
|
||||
const velocity = 127; // Maximum velocity
|
||||
const pianoRollState = KGPianoRollState.instance();
|
||||
const lastEditedLength = pianoRollState.getLastEditedNoteLength();
|
||||
const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
|
||||
const velocity = pianoRollState.getLastEditedNoteVelocity();
|
||||
const matchingChordPitches = pianoRollState.getCurrentMatchingChords();
|
||||
const selectedChordIndex = pianoRollState.getCurrentSelectedChordIndex();
|
||||
const cursorChordPitch = pianoRollState.getCurrentChordCursorPitch();
|
||||
@@ -333,6 +333,7 @@ export const useNoteOperations = ({
|
||||
note.select();
|
||||
core.addSelectedItem(note);
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(note.getEndBeat() - note.getStartBeat());
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
|
||||
|
||||
const track = tracks.find(t => t.getId().toString() === activeRegion.getTrackId());
|
||||
if (track) {
|
||||
|
||||
@@ -108,6 +108,7 @@ export const useNoteSelection = ({
|
||||
// Update last edited note length
|
||||
const noteLength = note.getEndBeat() - note.getStartBeat();
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
|
||||
|
||||
if (DEBUG_MODE.PIANO_ROLL) {
|
||||
console.log(`Added note to selection: ${noteId}`);
|
||||
@@ -139,6 +140,7 @@ export const useNoteSelection = ({
|
||||
// Update last edited note length
|
||||
const noteLength = note.getEndBeat() - note.getStartBeat();
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(note.getVelocity());
|
||||
|
||||
if (DEBUG_MODE.PIANO_ROLL) {
|
||||
console.log(`Selected note (replacing previous selection): ${noteId}`);
|
||||
@@ -416,6 +418,7 @@ export const useNoteSelection = ({
|
||||
|
||||
const noteLength = closestNote.getEndBeat() - closestNote.getStartBeat();
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(noteLength);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(closestNote.getVelocity());
|
||||
|
||||
if (DEBUG_MODE.PIANO_ROLL) {
|
||||
console.log(`Updated last edited note length to ${noteLength} from closest note: ${closestNote.getId()}`);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { KGTrack } from '../core/track/KGTrack';
|
||||
import { KGMidiTrack } from '../core/track/KGMidiTrack';
|
||||
import { KGAudioTrack } from '../core/track/KGAudioTrack';
|
||||
import { KGAudioRegion } from '../core/region/KGAudioRegion';
|
||||
import { KGMidiRegion } from '../core/region/KGMidiRegion';
|
||||
import { KGMidiNote } from '../core/midi/KGMidiNote';
|
||||
import { createDefaultGlobalTracks } from '../core/global-track';
|
||||
import { getAudioRegionDisplayLengthBeats } from '../util/globalTrackUtil';
|
||||
|
||||
@@ -35,6 +37,9 @@ let mockTracks: KGTrack[] = [new KGMidiTrack('Track 1', 0, 'acoustic_grand_piano
|
||||
let mockIsMetronomeEnabled = false;
|
||||
let mockShowGlobalTracks = false;
|
||||
let mockPlayheadPosition = 0;
|
||||
let mockSelectedItems: Array<{ getId: () => string; select: () => void; deselect: () => void; isSelected: () => boolean }> = [];
|
||||
let mockCopiedItems: Array<{ getId: () => string }> = [];
|
||||
const selectionChangedCallbacks: Array<() => void> = [];
|
||||
const mockProject = {
|
||||
getTimeSignature: () => ({ numerator: 4, denominator: 4 }),
|
||||
getMaxBars: () => 32,
|
||||
@@ -90,8 +95,11 @@ const mockCore = {
|
||||
setPlayheadUpdateCallback: vi.fn(),
|
||||
setPlaybackStateChangeCallback: vi.fn(),
|
||||
setLoopBoundaryReachedCallback: vi.fn(),
|
||||
getSelectedItems: () => [],
|
||||
onSelectionChanged: vi.fn(),
|
||||
getSelectedItems: () => mockSelectedItems,
|
||||
getCopiedItems: () => mockCopiedItems,
|
||||
onSelectionChanged: vi.fn((callback: () => void) => {
|
||||
selectionChangedCallbacks.push(callback);
|
||||
}),
|
||||
canUndo: () => false,
|
||||
canRedo: () => false,
|
||||
getUndoDescription: () => '',
|
||||
@@ -100,7 +108,16 @@ const mockCore = {
|
||||
executeCommand: vi.fn(),
|
||||
undo: vi.fn(() => true),
|
||||
redo: vi.fn(() => true),
|
||||
clearSelectedItems: vi.fn(),
|
||||
clearSelectedItems: vi.fn(() => {
|
||||
mockSelectedItems = [];
|
||||
selectionChangedCallbacks.forEach(callback => callback());
|
||||
}),
|
||||
addSelectedItems: vi.fn((items: Array<{ getId: () => string; select: () => void; deselect: () => void; isSelected: () => boolean }>) => {
|
||||
const incomingIds = new Set(items.map(item => item.getId()));
|
||||
mockSelectedItems = mockSelectedItems.filter(item => !incomingIds.has(item.getId()));
|
||||
mockSelectedItems.push(...items);
|
||||
selectionChangedCallbacks.forEach(callback => callback());
|
||||
}),
|
||||
getStatus: () => 'Ready',
|
||||
setStatus: vi.fn(),
|
||||
getPlayheadPosition: () => mockPlayheadPosition,
|
||||
@@ -197,6 +214,12 @@ describe('projectStore piano roll state', () => {
|
||||
audioStorageMocks.loadAudioFile.mockReset();
|
||||
toneMocks.decodeAudioData.mockReset();
|
||||
toneMocks.toneBufferSet.mockReset();
|
||||
mockSelectedItems = [];
|
||||
mockCopiedItems = [];
|
||||
selectionChangedCallbacks.length = 0;
|
||||
mockCore.onSelectionChanged.mockClear();
|
||||
mockCore.clearSelectedItems.mockClear();
|
||||
mockCore.addSelectedItems.mockClear();
|
||||
mockIsMetronomeEnabled = false;
|
||||
mockShowGlobalTracks = false;
|
||||
mockProject.setIsMetronomeEnabled.mockClear();
|
||||
@@ -737,4 +760,81 @@ describe('projectStore piano roll state', () => {
|
||||
expect(state.maxBars).toBe(16);
|
||||
expect(state.playheadPosition).toBe(64);
|
||||
});
|
||||
|
||||
it('selects only the newly pasted notes after pasting into the active MIDI region', async () => {
|
||||
const { KGMidiTrack: TestMidiTrack } = await import('../core/track/KGMidiTrack');
|
||||
const { KGMidiRegion: TestMidiRegion } = await import('../core/region/KGMidiRegion');
|
||||
const { KGMidiNote: TestMidiNote } = await import('../core/midi/KGMidiNote');
|
||||
const track = new TestMidiTrack('Track 1', 1, 'acoustic_grand_piano');
|
||||
track.setTrackIndex(0);
|
||||
const region = new TestMidiRegion('region-1', '1', 0, 'Region 1', 0, 16);
|
||||
const existingSelectedNote = new TestMidiNote('existing-note', 0, 1, 60, 100);
|
||||
existingSelectedNote.select();
|
||||
region.addNote(existingSelectedNote);
|
||||
track.setRegions([region]);
|
||||
|
||||
mockTracks = [track];
|
||||
currentProject = {
|
||||
...mockProject,
|
||||
getTracks: () => mockTracks,
|
||||
} as typeof mockProject;
|
||||
|
||||
mockSelectedItems = [existingSelectedNote];
|
||||
mockCopiedItems = [
|
||||
new TestMidiNote('copied-a', 2, 3, 64, 110),
|
||||
new TestMidiNote('copied-b', 3, 4, 67, 120),
|
||||
];
|
||||
mockCore.executeCommand.mockImplementation((command: { execute: () => void }) => command.execute());
|
||||
|
||||
const { useProjectStore } = await import('./projectStore');
|
||||
|
||||
act(() => {
|
||||
useProjectStore.getState().pasteNotesToActiveRegion(region.getId(), 8);
|
||||
});
|
||||
|
||||
const pastedNotes = region.getNotes().filter(note => note.getId() !== existingSelectedNote.getId());
|
||||
const pastedNoteIds = pastedNotes.map(note => note.getId());
|
||||
const state = useProjectStore.getState();
|
||||
|
||||
expect(mockCore.executeCommand).toHaveBeenCalledTimes(1);
|
||||
expect(existingSelectedNote.isSelected()).toBe(false);
|
||||
expect(pastedNotes).toHaveLength(2);
|
||||
expect(pastedNotes.every(note => note.isSelected())).toBe(true);
|
||||
expect(mockCore.clearSelectedItems).toHaveBeenCalledTimes(1);
|
||||
expect(mockCore.addSelectedItems).toHaveBeenCalledWith(pastedNotes);
|
||||
expect(state.selectedNoteIds).toEqual(pastedNoteIds);
|
||||
});
|
||||
|
||||
it('keeps selection unchanged when note paste has no clipboard notes', async () => {
|
||||
const { KGMidiTrack: TestMidiTrack } = await import('../core/track/KGMidiTrack');
|
||||
const { KGMidiRegion: TestMidiRegion } = await import('../core/region/KGMidiRegion');
|
||||
const { KGMidiNote: TestMidiNote } = await import('../core/midi/KGMidiNote');
|
||||
const track = new TestMidiTrack('Track 1', 1, 'acoustic_grand_piano');
|
||||
track.setTrackIndex(0);
|
||||
const region = new TestMidiRegion('region-1', '1', 0, 'Region 1', 0, 16);
|
||||
const existingSelectedNote = new TestMidiNote('existing-note', 0, 1, 60, 100);
|
||||
existingSelectedNote.select();
|
||||
region.addNote(existingSelectedNote);
|
||||
track.setRegions([region]);
|
||||
|
||||
mockTracks = [track];
|
||||
currentProject = {
|
||||
...mockProject,
|
||||
getTracks: () => mockTracks,
|
||||
} as typeof mockProject;
|
||||
|
||||
mockSelectedItems = [existingSelectedNote];
|
||||
mockCopiedItems = [];
|
||||
|
||||
const { useProjectStore } = await import('./projectStore');
|
||||
|
||||
act(() => {
|
||||
useProjectStore.getState().pasteNotesToActiveRegion(region.getId(), 8);
|
||||
});
|
||||
|
||||
expect(mockCore.executeCommand).not.toHaveBeenCalled();
|
||||
expect(mockCore.clearSelectedItems).not.toHaveBeenCalled();
|
||||
expect(mockCore.addSelectedItems).not.toHaveBeenCalled();
|
||||
expect(existingSelectedNote.isSelected()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1052,6 +1052,7 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
|
||||
// Reset piano roll state for new/loaded project
|
||||
KGPianoRollState.instance().setLastEditedNoteLength(1);
|
||||
KGPianoRollState.instance().setLastEditedNoteVelocity(127);
|
||||
KGPianoRollState.instance().setPianoRollZoom(projectToLoad.getPianoRollZoom());
|
||||
|
||||
// Add a status message
|
||||
@@ -1904,7 +1905,26 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
}
|
||||
|
||||
try {
|
||||
KGCore.instance().executeCommand(command);
|
||||
const core = KGCore.instance();
|
||||
core.executeCommand(command);
|
||||
|
||||
const createdNoteIds = new Set(command.getCreatedNotes().map(note => note.noteId));
|
||||
const targetRegion = command.getTargetRegion();
|
||||
const createdNotes = targetRegion
|
||||
? targetRegion.getNotes().filter(note => createdNoteIds.has(note.getId()))
|
||||
: [];
|
||||
|
||||
if (createdNotes.length > 0) {
|
||||
core.getSelectedItems().forEach(item => {
|
||||
item.deselect();
|
||||
});
|
||||
core.clearSelectedItems();
|
||||
|
||||
createdNotes.forEach(note => {
|
||||
note.select();
|
||||
});
|
||||
core.addSelectedItems(createdNotes);
|
||||
}
|
||||
|
||||
// Update the store to trigger re-render
|
||||
const { tracks } = get();
|
||||
|
||||
Reference in New Issue
Block a user