Skip to content

Commit cf70f64

Browse files
Fix test fail due to missing keybind, update changelog wording and add helper function to build class def map
1 parent d10b304 commit cf70f64

3 files changed

Lines changed: 32 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented here.
55
## [unreleased]
66

77
## [0.23.6] - May 11th, 2026
8-
- Fix `AnnotationList` class ordering to match the `AnnotationID` toolbox item ordering, which is based on the order of annotations in the subtask's annotation array.
8+
- Fix `AnnotationList` class ordering to match the `AnnotationID` toolbox item ordering, which is based on the configured class definition order in the subtask's `classes` array
99
- Add local storage of checkbox options for the `AnnotationList` toolbox item
1010

1111
## [0.23.5] - May 6th, 2026

src/toolbox_items/annotation_list.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,13 @@ export class AnnotationListToolboxItem extends ToolboxItem {
409409
* Build HTML for flat (non-grouped) list
410410
*/
411411
private build_flat_list_html(annotations: ULabelAnnotation[], subtask: ULabelSubtask): string {
412+
const class_def_by_id = this.build_class_def_by_id(subtask);
412413
let html = "";
413414

414415
for (let i = 0; i < annotations.length; i++) {
415416
const annotation = annotations[i];
416417
const class_id = this.get_annotation_class_id(annotation);
417-
const class_def = subtask.class_defs.find((def) => def.id === class_id);
418+
const class_def = class_def_by_id.get(class_id);
418419
const class_name = class_def ? class_def.name : "Unknown";
419420
const color = this.ulabel.color_info[class_id] || "#cccccc";
420421
const svg = this.get_spatial_type_svg(annotation.spatial_type!, color);
@@ -439,6 +440,8 @@ export class AnnotationListToolboxItem extends ToolboxItem {
439440
* Build HTML for grouped (by class) list
440441
*/
441442
private build_grouped_list_html(annotations: ULabelAnnotation[], subtask: ULabelSubtask): string {
443+
const class_def_by_id = this.build_class_def_by_id(subtask);
444+
442445
// Group annotations by class
443446
const groups: { [class_id: number]: ULabelAnnotation[] } = {};
444447

@@ -475,7 +478,7 @@ export class AnnotationListToolboxItem extends ToolboxItem {
475478

476479
for (const class_id of ordered_class_ids) {
477480
const group_annotations = groups[class_id];
478-
const class_def = subtask.class_defs.find((def) => def.id === class_id);
481+
const class_def = class_def_by_id.get(class_id);
479482
const class_name = class_def ? class_def.name : "Unknown";
480483
const color = this.ulabel.color_info[class_id] || "#cccccc";
481484

@@ -584,6 +587,20 @@ export class AnnotationListToolboxItem extends ToolboxItem {
584587
return class_id;
585588
}
586589

590+
/**
591+
* Build a Map from class id to ClassDefinition for the given subtask. The
592+
* returned map is intended to be used for the duration of a single render so
593+
* that per-annotation class lookups don't repeat a linear search through
594+
* `subtask.class_defs`.
595+
*/
596+
private build_class_def_by_id(subtask: ULabelSubtask): Map<number, ULabelSubtask["class_defs"][number]> {
597+
const map = new Map<number, ULabelSubtask["class_defs"][number]>();
598+
for (const def of subtask.class_defs) {
599+
map.set(def.id, def);
600+
}
601+
return map;
602+
}
603+
587604
/**
588605
* Get the HTML for this toolbox item
589606
*/

tests/e2e/annotation_list.spec.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,27 @@ test.describe("Annotation List Grouping", () => {
126126
await wait_for_ulabel_init(page);
127127

128128
// multi-class.html defines class_defs as [Sedan(10), SUV(11), Truck(12)].
129-
// Draw two annotations: one with the default class (Sedan), one after
130-
// switching to the third class (Truck). SUV is intentionally left empty.
129+
// Use Sedan + SUV here because those are the two classes with default
130+
// keybinds ("1" and "2") in the demo. Truck (id 12) has no default
131+
// keybind, so pressing "3" would be a no-op and silently leave the
132+
// active class unchanged.
131133
await draw_bbox(page, [100, 100], [200, 200]);
132134
await page.mouse.move(300, 300);
133-
await page.keyboard.press("3");
135+
await page.keyboard.press("2");
134136
await draw_bbox(page, [300, 300], [400, 400]);
135137

136-
// Enable group-by-class and confirm the natural ordering matches class_defs
137-
// (Sedan before Truck).
138+
// Enable group-by-class and confirm the natural ordering matches
139+
// class_defs (Sedan before SUV).
138140
await toggle_group_by_class(page, true);
139141
let header_texts = await page.locator(".annotation-list-class-group-header").allTextContents();
140142
// headers contain the class name plus a count like "(1)"; strip whitespace
141143
let names_in_order = header_texts.map((t) => t.trim().split(/\s+/)[0]);
142-
expect(names_in_order).toEqual(["Sedan", "Truck"]);
144+
expect(names_in_order).toEqual(["Sedan", "SUV"]);
143145

144146
// Now reverse class_defs at runtime. With the old (buggy) implementation
145-
// the headers would still appear in numeric class_id order
146-
// ([Sedan(10), Truck(12)]). With the fix they should match the new
147-
// class_defs order ([Truck, Sedan]).
147+
// the headers would still appear in ascending numeric class_id order
148+
// ([Sedan(10), SUV(11)]). With the fix they should follow the new
149+
// class_defs order ([SUV, Sedan]).
148150
await page.evaluate(() => {
149151
const subtask = window.ulabel.subtasks[window.ulabel.state.current_subtask];
150152
subtask.class_defs.reverse();
@@ -157,6 +159,6 @@ test.describe("Annotation List Grouping", () => {
157159

158160
header_texts = await page.locator(".annotation-list-class-group-header").allTextContents();
159161
names_in_order = header_texts.map((t) => t.trim().split(/\s+/)[0]);
160-
expect(names_in_order).toEqual(["Truck", "Sedan"]);
162+
expect(names_in_order).toEqual(["SUV", "Sedan"]);
161163
});
162164
});

0 commit comments

Comments
 (0)