Skip to content

Commit 3d040fd

Browse files
committed
Persist current selection and restore on app load
Bug fix: could not select more than one note group using the selection rectangle Signed-off-by: Mike Lischke <mike@lischke-online.de>
1 parent 6981985 commit 3d040fd

6 files changed

Lines changed: 240 additions & 45 deletions

File tree

src/components/ui/Bar/Staff/StaffBarViewer.tsx

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -256,26 +256,30 @@ export class StaffBarViewer extends UIComponent<IBarViewerProps, IBarViewerState
256256
}
257257
}
258258

259-
// If no individual notes/rests were hit, try beam → NoteGroup.
259+
// If no individual notes/rests were hit, try beams → NoteGroup entries.
260260
if (noteEntries.filter((e) => {
261261
return e.trackId === trackId;
262262
}).length === 0) {
263263
const beamElements = row.querySelectorAll<HTMLElement>(".staff-note-viewer-beam");
264-
let hitBeamStep: number | undefined;
264+
const hitBeamSteps = new Set<number>();
265265

266266
for (const beam of beamElements) {
267267
const beamRect = beam.getBoundingClientRect();
268268
if (rectsIntersect(rect, beamRect.left, beamRect.top, beamRect.right, beamRect.bottom, 0)) {
269-
const runParent = beam.closest<HTMLElement>(".staff-note-viewer-run[data-step-index]");
269+
const runParent = beam.closest<HTMLElement>(
270+
".staff-note-viewer-run[data-step-index]",
271+
);
272+
270273
if (runParent) {
271-
hitBeamStep = parseInt(runParent.getAttribute("data-step-index") ?? "", 10);
274+
const step = parseInt(runParent.getAttribute("data-step-index") ?? "", 10);
275+
if (!isNaN(step)) {
276+
hitBeamSteps.add(step);
277+
}
272278
}
273-
274-
break;
275279
}
276280
}
277281

278-
if (hitBeamStep !== undefined && !isNaN(hitBeamStep)) {
282+
if (hitBeamSteps.size > 0) {
279283
const allRuns = row.querySelectorAll<HTMLElement>(
280284
".staff-note-viewer-run[data-step-index]",
281285
);
@@ -310,24 +314,37 @@ export class StaffBarViewer extends UIComponent<IBarViewerProps, IBarViewerState
310314
reverseConnections.set(to, from);
311315
}
312316

313-
let groupStart = hitBeamStep;
314-
while (reverseConnections.has(groupStart)) {
315-
groupStart = reverseConnections.get(groupStart)!;
316-
}
317+
// Collect distinct beam groups from all hit steps.
318+
const beamGroups = new Set<string>();
319+
320+
for (const hitStep of hitBeamSteps) {
321+
let groupStart = hitStep;
322+
while (reverseConnections.has(groupStart)) {
323+
groupStart = reverseConnections.get(groupStart)!;
324+
}
317325

318-
let groupEnd = hitBeamStep;
319-
while (beamConnections.has(groupEnd)) {
320-
groupEnd = beamConnections.get(groupEnd)!;
326+
let groupEnd = hitStep;
327+
while (beamConnections.has(groupEnd)) {
328+
groupEnd = beamConnections.get(groupEnd)!;
329+
}
330+
331+
beamGroups.add(`${groupStart}-${groupEnd}`);
321332
}
322333

323-
noteEntries.push({
324-
granularity: SelectionGranularity.NoteGroup,
325-
bar: barNumber,
326-
trackId: trackId,
327-
startStep: groupStart,
328-
endStep: groupEnd,
329-
});
330-
rowHasSoundingNotes = true;
334+
for (const groupKey of beamGroups) {
335+
const [startStr, endStr] = groupKey.split("-");
336+
const groupStart = parseInt(startStr, 10);
337+
const groupEnd = parseInt(endStr, 10);
338+
339+
noteEntries.push({
340+
granularity: SelectionGranularity.NoteGroup,
341+
bar: barNumber,
342+
trackId: trackId,
343+
startStep: groupStart,
344+
endStep: groupEnd,
345+
});
346+
rowHasSoundingNotes = true;
347+
}
331348
}
332349
}
333350

src/core/AppStorage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export interface IArrangementViewSettings {
2929
export interface IViewSettings {
3030
/** Settings related to the arrangement viewer. */
3131
arrangementViewSettings?: IArrangementViewSettings;
32+
33+
/**
34+
* Serialised selection state for the current arrangement, stored as a JSON string.
35+
* Maintained by the SelectionManager so the selection survives page reloads.
36+
*/
37+
selectionState?: string;
3238
}
3339

3440
export interface IUISettings {

src/player/ArrangementPlayer.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class ArrangementPlayer {
129129
*/
130130
public onStop = (): void => {
131131
this.timing = undefined;
132-
void requisitions.execute("playerStateChanged", undefined);
132+
void requisitions.execute("playerStateChanged", this.#state);
133133
for (const player of this.trackPlayers.values()) {
134134
player.onStop();
135135
}
@@ -196,7 +196,7 @@ export class ArrangementPlayer {
196196

197197
this.#state = "counting";
198198
if (this.dataModel.arrangement!.countIn) {
199-
void requisitions.execute("playerStateChanged", undefined);
199+
void requisitions.execute("playerStateChanged", this.#state);
200200
this.offset = this.audioContext.currentTime;
201201
await this.countIn();
202202
}
@@ -216,7 +216,7 @@ export class ArrangementPlayer {
216216
// Pretend we have covered all events before the interval start.
217217
this.timeCovered = interval?.start ?? 0;
218218

219-
void requisitions.execute("playerStateChanged", undefined);
219+
void requisitions.execute("playerStateChanged", this.#state);
220220
void this.iteration();
221221
}
222222

@@ -417,7 +417,7 @@ export class ArrangementPlayer {
417417
}
418418

419419
if (somethingChanged) {
420-
void requisitions.execute("playerStateChanged", undefined);
420+
void requisitions.execute("playerStateChanged", this.#state);
421421
}
422422
};
423423

@@ -451,7 +451,6 @@ export class ArrangementPlayer {
451451
realTime: this.timeCoordinator.convertToRealTime(timing),
452452
callback: () => {
453453
this.timing = timing;
454-
void requisitions.execute("playerStateChanged", undefined);
455454
},
456455
identifier: timing
457456
};

src/supplement/Requisitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface IRequestTypeMap {
2121
// --- Playback topics ---
2222
"playRangeChanged": (range?: { from: number; to: number; }) => Promise<boolean>;
2323
"animationStateChanged": (state: PlayerPlayState) => Promise<boolean>;
24-
"playerStateChanged": SimpleCallback;
24+
"playerStateChanged": (state: PlayerPlayState) => Promise<boolean>;
2525

2626
// --- Core model topics ---
2727
"instrumentLoaded": (instrumentId: number) => Promise<boolean>;

0 commit comments

Comments
 (0)