Skip to content

Commit 2904229

Browse files
Merge pull request #237 from SenteraLLC/feature/keybinds
Feature/keybinds
2 parents 2001775 + ef1ec21 commit 2904229

20 files changed

Lines changed: 2750 additions & 248 deletions

.github/copilot-instructions.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@
66
- Do not add new dependencies unless given explicit permission.
77
- Do not modify the `package.json` or `package-lock.json` files unless instructed.
88

9-
109
## Task Tracking
1110
- Always start each step by referencing and updating your tasks in `.github/tasks.md`.
1211
- Use checkboxes to track progress.
1312
- Work on one task at a time.
14-
- Do not mark tasks as complete until they are fully done. Ask for confirmation if unsure.
13+
- Do not mark tasks as complete until they are fully done. Ask for confirmation if unsure.
14+
15+
## Repository Style
16+
- Follow existing code style and conventions.
17+
- Use `snake_case` for variable and function names.
18+
- Use `PascalCase` for class names.
19+
- Use `log_message` instead of `console.<method>` for logging messages.

.github/tasks.md

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,77 @@
11
## Tasks
2-
- [x] Read the description in [#234](https://github.com/SenteraLLC/ulabel/issues/234)
3-
- [x] Write a clear summary of the requested change
4-
- [x] Break the requested feature down into concrete steps. Add the steps to the tasks list, and then start working on then one by one.
2+
- [x] Add a sideways, clickable arrow to minimize the entire toolbox
3+
- [x] Add collapse button to toolbox HTML
4+
- [x] Add CSS styles for collapsed state
5+
- [x] Add click handler to toggle collapsed state
6+
- [x] Store collapsed state in localStorage
7+
- [x] Test functionality
8+
- [x] Move arrow to top of toolbox (instead of middle)
9+
- [x] Make annbox expand when toolbox is collapsed
10+
- [x] Make collapsed button visible
11+
- [x] Create a keybinds toolbox item
12+
- [x] Research existing keybinds in the codebase
13+
- [x] Create basic keybinds toolbox item file
14+
- [x] Register keybinds toolbox item in configuration
15+
- [x] Display list of all keybinds (the key) labeled with the name
16+
- [x] Add hover tooltips with detailed descriptions (using title attribute)
17+
- [x] Add collision detection and red highlighting
18+
- [x] Implement editing for configurable keybinds
19+
- [x] Test functionality
20+
- [x] Add support for keybind "chords" (ie, "shift+i")
21+
- [x] Update keybind edit handler to capture modifier keys (shift, ctrl, alt)
22+
- [x] Create chord string format (e.g., "shift+i", "ctrl+alt+d")
23+
- [x] Update key comparison logic in listeners to support chords
24+
- [x] Update display to show chords properly (displays captured chord automatically)
25+
- [x] Test chord functionality
26+
- [x] Store collapse/expand for applicable toolbox items
27+
- [x] Keybinds
28+
- [x] Annotation List
29+
- [x] Image Filters
30+
- [x] Make all keybinds configurable
31+
- [x] Minor changes to existing keybinds
32+
- [x] Rename "Change Zoom" keybind to "Reset Zoom"
33+
- [x] Change "Toggle Mode" label in the keybind toolbox item to "Toggle Annotation Mode"
34+
- [x] Make class keybinds configurable in the keybinds toolbox item
35+
- [x] Store keybinds in local storage
36+
- [x] Only save them when a user explicitly sets it
37+
- [x] For keybinds using a user setting, add a button to reset that keybind to default (should change keybind and delete stored keybind)
38+
- [x] Add "Reset All to Default" button in the keybinds toolbox item that resets all keybinds and deletes stored user keybinds
39+
- [x] Add a light yellow highlight on keybinds that are using a user setting instead of a default
40+
- [x] Make sure that we update collison highlights after resetting a keybind to default
41+
- [x] Only show the reset to default for keybinds with user settings, not on those already at the default
42+
- [x] Make sure the class keybinds also are included in the keybind collision checks
43+
- [x] Fix reset to default to use constructor-provided config values instead of hardcoded Configuration defaults
44+
- [x] Centralize keybind config property names in Configuration.KEYBIND_CONFIG_KEYS constant
45+
- [x] Make KEYBIND_CONFIG_KEYS dynamically generated from Configuration class properties
46+
- [x] Rename create_bbox_on_initial_crop to create_bbox_on_initial_crop_keybind for consistency
47+
- [x] Replace any console outputs with `log_message`
48+
- [x] Replace the "reset_zoom_keybind" with two separate keybinds:
49+
- [x] Add "show_full_image_keybind" property to Configuration
50+
- [x] Update listeners.ts to use both keybinds independently
51+
- [x] Update toolbox.ts to use both keybinds independently
52+
- [x] Update api_spec.md to document both keybinds
53+
- [x] Write e2e tests for the keybind toolbox item
54+
- [x] Ability to set keybind to a chord
55+
- [x] Ability to reset keybind
56+
- [x] Ability to set a class keybind
57+
- [x] Run tests to verify they pass
58+
- [x] Write a e2e test for each keybind
59+
- [x] reset_zoom_keybind (r)
60+
- [x] show_full_image_keybind (shift+r)
61+
- [x] create_point_annotation_keybind (c)
62+
- [x] delete_annotation_keybind (d)
63+
- [x] switch_subtask_keybind (z)
64+
- [x] toggle_annotation_mode_keybind (u)
65+
- [x] create_bbox_on_initial_crop_keybind (f)
66+
- [x] toggle_brush_mode_keybind (g)
67+
- [x] toggle_erase_mode_keybind (e)
68+
- [x] increase_brush_size_keybind (])
69+
- [x] decrease_brush_size_keybind ([)
70+
- [x] annotation_size_small_keybind (s)
71+
- [x] annotation_size_large_keybind (l)
72+
- [x] annotation_size_plus_keybind (=)
73+
- [x] annotation_size_minus_keybind (-)
74+
- [x] annotation_vanish_keybind (v)
75+
- [x] fly_to_next_annotation_keybind (tab)
76+
- [x] fly_to_previous_annotation_keybind (shift+tab)
577

6-
### Summary
7-
Create an annotation list toolbox item that displays all annotations in a list format, similar to other annotation tools. The list should:
8-
- Display each annotation (by ID or index)
9-
- Allow show/hide of deprecated annotations (default: hide)
10-
- Support grouping by class
11-
- Enable clicking to "fly to" the annotation
12-
- Show annotation labels/IDs (on hover or drawn on canvas)
13-
- Display "current idx / total" when navigating through annotations
14-
- Highlight annotations when hovering in the list
15-
16-
### Implementation Steps
17-
- [x] 1. Research existing toolbox items and understand the toolbox structure
18-
- [x] Read `src/toolbox.ts` to understand how toolbox items work
19-
- [x] Review existing toolbox items in `src/toolbox_items/`
20-
- [x] Understand how annotation data is accessed and structured
21-
- [x] 2. Create the basic annotation list toolbox item
22-
- [x] Create new file `src/toolbox_items/annotation_list.ts`
23-
- [x] Implement basic UI structure (container, list elements)
24-
- [x] Register the toolbox item in the main toolbox
25-
- [x] 3. Implement core list functionality
26-
- [x] Display all annotations with their ID/index
27-
- [x] Add show/hide toggle for deprecated annotations (default: hide)
28-
- [x] Add option to group by class
29-
- [x] 4. Implement click-to-fly functionality
30-
- [x] Integrate with existing "fly to" functionality from PR #230
31-
- [x] Add click handlers to list items
32-
- [x] Display "current idx / total" indicator
33-
- [x] 5. Implement hover highlighting
34-
- [x] Add hover handlers to list items
35-
- [x] Integrate with existing annotation highlighting system
36-
- [x] Ensure bidirectional highlighting (list hover → canvas, canvas hover → list)

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ All notable changes to this project will be documented here.
44

55
## [unreleased]
66

7+
## [0.22.0] - Oct 30th, 2025
8+
- Add collapsible toolbox with arrow button at top
9+
- Toolbox collapse state persists in browser
10+
- Annotation canvas expands to fill space when toolbox is collapsed
11+
- Add `Keybinds` toolbox item for viewing and customizing keybinds
12+
- Display all configurable keybinds with labels and descriptions
13+
- Edit keybinds by clicking and pressing new key combination
14+
- Support for modifier key chords (shift, ctrl, alt, meta)
15+
- Collision detection with red highlighting for duplicate keybinds
16+
- Reset individual keybinds or all keybinds to defaults
17+
- Visual indicator (yellow highlight) for user-customized keybinds
18+
- User keybind settings persist in browser
19+
- Rename `create_bbox_on_initial_crop` to `create_bbox_on_initial_crop_keybind` for consistency
20+
- Split `change_zoom_keybind` into two separate keybinds:
21+
- `reset_zoom_keybind` (default: `r`) - Reset zoom to fit image
22+
- `show_full_image_keybind` (default: `shift+r`) - Zoom to show full image
23+
- Store collapse/expand state for Keybinds, Annotation List, and Image Filters toolbox items
24+
- Add comprehensive e2e tests for keybind functionality and keybind toolbox item
25+
726
## [0.21.0] - Oct 27th, 2025
827
- Add toast notification that shows on `fly_to` calls and shows annotation position in the ordering (e.g., "3 / 10")
928
- Add `AnnotationList` toolbox item for managing and navigating annotations

api_spec.md

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This should eventually be replaced with a more comprehensive approach to documen
88
- `ctrl+shift+z` or `cmd+shift+z`: Redo
99
- `scroll`: Zoom -- up for in, down for out
1010
- `ctrl+scroll` or `shift+scroll` or `cmd+scroll`: Change frame -- down for next, up for previous
11-
- `scrollclick+drag` or `ctrl+drag`: Pan
11+
- `scrollclick+drag`: Pan
1212
- Hold `shift` when closing a polygon to continue annotating a new region or hole.
1313
- Hold `shift` when moving the cursor inside a polygon to begin annotating a new region or hole.
1414
- Press `Escape` or `crtl+z` to cancel the start of a new region or hole.
@@ -50,30 +50,29 @@ class ULabel({
5050
initial_line_size: number,
5151
instructions_url: string,
5252
toolbox_order: AllowedToolboxItem[],
53-
default_keybinds = {
54-
"annotation_size_small": string,
55-
"annotation_size_large": string,
56-
"annotation_size_plus": string,
57-
"annotation_size_minus": string,
58-
"annotation_vanish": string
59-
},
6053
distance_filter_toolbox_item: FilterDistanceConfig,
6154
image_filters_toolbox_item: ImageFiltersConfig,
62-
change_zoom_keybind: string,
55+
reset_zoom_keybind: string,
56+
show_full_image_keybind: string,
6357
create_point_annotation_keybind: string,
6458
default_annotation_size: number,
6559
delete_annotation_keybind: string,
6660
keypoint_slider_default_value: number,
6761
filter_annotations_on_load: boolean,
6862
switch_subtask_keybind: string,
6963
toggle_annotation_mode_keybind: string,
70-
create_bbox_on_initial_crop: string,
64+
create_bbox_on_initial_crop_keybind: string,
7165
toggle_brush_mode_keybind: string,
7266
toggle_erase_mode_keybind: string,
7367
increase_brush_size_keybind: string,
7468
decrease_brush_size_keybind: string,
7569
fly_to_next_annotation_keybind: string,
76-
fly_to_previous_annotation_keybind: string | null,
70+
fly_to_previous_annotation_keybind: string,
71+
annotation_size_small_keybind: string,
72+
annotation_size_large_keybind: string,
73+
annotation_size_plus_keybind: string,
74+
annotation_size_minus_keybind: string,
75+
annotation_vanish_keybind: string,
7776
fly_to_max_zoom: number,
7877
n_annos_per_canvas: number
7978
})
@@ -348,26 +347,15 @@ enum AllowedToolboxItem {
348347
FilterDistance, // 8
349348
Brush, // 9
350349
ImageFilters, // 10
351-
AnnotationList // 11
350+
AnnotationList, // 11
351+
Keybinds, // 12
352352
}
353353
```
354354
You can access the AllowedToolboxItem enum by calling the static method:
355355
```javascript
356356
const AllowedToolboxItem = ULabel.get_allowed_toolbox_item_enum();
357357
```
358358
359-
### `default_keybinds`
360-
Keybinds can be set to control the annotation session. The default values are:
361-
```javascript
362-
{
363-
"annotation_size_small": "s",
364-
"annotation_size_large": "l",
365-
"annotation_size_plus": "=",
366-
"annotation_size_minus": "-",
367-
"annotation_vanish": "v"
368-
}
369-
```
370-
371359
### `distance_filter_toolbox_item`
372360
Configuration object for the `FilterDistance` toolbox item with the following custom definitions:
373361
```javascript
@@ -437,8 +425,11 @@ The `AnnotationList` toolbox item displays all annotations in the current subtas
437425
438426
This toolbox item requires no configuration and can be added to the `toolbox_order` array using `AllowedToolboxItem.AnnotationList`.
439427
440-
### `change_zoom_keybind`
441-
Keybind to change the zoom level. Must be a letter, and the lowercase version of the letter will set the zoom level to the `initial_crop`, while the capitalized version will show the full image. Default is `r`.
428+
### `reset_zoom_keybind`
429+
Keybind to reset the zoom level to the `initial_crop`. Default is `r`.
430+
431+
### `show_full_image_keybind`
432+
Keybind to set the zoom level to show the full image. Default is `shift+r`.
442433
443434
### `create_point_annotation_keybind`
444435
Keybind to create a point annotation at the mouse location. Default is `c`. Requires the active subtask to have a `point` mode.
@@ -461,7 +452,7 @@ Keybind to switch between subtasks. Default is `z`.
461452
### `toggle_annotation_mode_keybind`
462453
Keybind to toggle between annotation and selection modes. Default is `u`.
463454
464-
### `create_bbox_on_initial_crop`
455+
### `create_bbox_on_initial_crop_keybind`
465456
Keybind to create a bounding box annotation around the `initial_crop`. Default is `f`. Requires the active subtask to have a `bbox` mode.
466457
467458
### `toggle_brush_mode_keybind`
@@ -480,7 +471,22 @@ Keybind to decrease the brush size. Default is `[`. Requires the active subtask
480471
Keybind to set the zoom to focus on the next annotation. Default is `Tab`, which also will disable any default browser behavior for `Tab`.
481472
482473
### `fly_to_previous_annotation_keybind`
483-
Keybind to set the zoom to focus on the previous annotation. Default is `<null>`, which will default to `Shift+<fly_to_next_annotation_keybind>`.
474+
Keybind to set the zoom to focus on the previous annotation. Default is `shift+tab`. Supports chord keybinds (e.g., `shift+p`, `ctrl+alt+n`).
475+
476+
### `annotation_size_small_keybind`
477+
Keybind to set the annotation size to small for the current subtask. Default is `s`.
478+
479+
### `annotation_size_large_keybind`
480+
Keybind to set the annotation size to large for the current subtask. Default is `l`.
481+
482+
### `annotation_size_plus_keybind`
483+
Keybind to increment the annotation size for the current subtask. Default is `=`.
484+
485+
### `annotation_size_minus_keybind`
486+
Keybind to decrement the annotation size for the current subtask. Default is `-`.
487+
488+
### `annotation_vanish_keybind`
489+
Keybind to toggle vanish mode for annotations in the current subtask (lowercase toggles current subtask, uppercase toggles all subtasks). Default is `v`.
484490
485491
### `fly_to_max_zoom`
486492
Maximum zoom factor used when flying-to an annotation. Default is `10`, value must be > `0`.

demo/multi-class.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"name": "Truck",
5252
"color": "orange",
5353
"id": 12,
54-
"keybind": "3",
5554
},
5655
],
5756
"allowed_modes": ["bbox", "polygon", "contour", "polyline", "point", "tbar", "delete_polygon", "delete_bbox"],
@@ -65,7 +64,8 @@
6564
{
6665
"name": "Blurry",
6766
"color": "gray",
68-
"id": 20
67+
"id": 20,
68+
"keybind": "5",
6969
},
7070
{
7171
"name": "Occluded",
@@ -119,6 +119,7 @@
119119
"initial_line_size": 2,
120120
"toolbox_order": [
121121
AllowedToolboxItem.SubmitButtons,
122+
AllowedToolboxItem.Keybinds,
122123
AllowedToolboxItem.ModeSelect,
123124
AllowedToolboxItem.AnnotationList,
124125
AllowedToolboxItem.ImageFilters,
@@ -131,7 +132,7 @@
131132
AllowedToolboxItem.RecolorActive,
132133
],
133134
"toggle_brush_mode_keybind": "f",
134-
"create_bbox_on_initial_crop": "|",
135+
"create_bbox_on_initial_crop_keybind": "|",
135136
"click_and_drag_poly_annotations": false,
136137
"anno_scaling_mode": "fixed",
137138
"allow_annotations_outside_image": false,

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ export class ULabel {
252252
last_brush_stroke: [number, number];
253253
line_size: number;
254254
anno_scaling_mode: AnnoScalingMode;
255+
// Keybind editing state
256+
is_editing_keybind: boolean;
255257
// Render state
256258
// TODO (joshua-dean): this is never assigned, is it used?
257259
demo_canvas_context: CanvasRenderingContext2D;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ulabel",
33
"description": "An image annotation tool.",
4-
"version": "0.21.0",
4+
"version": "0.22.0",
55
"main": "dist/ulabel.min.js",
66
"module": "dist/ulabel.min.js",
77
"exports": {

src/blobs.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2009,6 +2009,7 @@ div#${prntid} a.tbid-opt.sel {
20092009
div#${prntid} div.toolbox-name-header {
20102010
background-color: rgb(0, 128, 202);
20112011
margin: 0;
2012+
flex: 7;
20122013
}
20132014
div#${prntid}.ulabel-night div.toolbox-name-header {
20142015
background-color: rgb(0, 60, 95);
@@ -2036,7 +2037,7 @@ div#${prntid}.ulabel-night div.toolbox-name-header h1 span.version-number {
20362037
color: rgb(190, 190, 190);
20372038
}
20382039
div#${prntid} div.night-button-cont {
2039-
text-align: right;
2040+
text-align: left;
20402041
display: inline-block;
20412042
vertical-align: middle;
20422043
position: relative;

0 commit comments

Comments
 (0)