Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
90155dd
first stab at collapsable toolbox
TrevorBurgoyne Oct 29, 2025
9b4e556
touch up and fix issues with collapse
TrevorBurgoyne Oct 29, 2025
0e4e13b
start keybinds
TrevorBurgoyne Oct 29, 2025
b9d76db
labels vs titles
TrevorBurgoyne Oct 29, 2025
68fe3ab
fix css clash
TrevorBurgoyne Oct 29, 2025
723d9b6
add flag while editing keybinds
TrevorBurgoyne Oct 29, 2025
ed76d51
handle chords
TrevorBurgoyne Oct 29, 2025
6533e9f
update all keybinds to use the chord logic
TrevorBurgoyne Oct 29, 2025
64e7d4b
store collapse state
TrevorBurgoyne Oct 29, 2025
b23d1c1
refactor 'default keybinds'
TrevorBurgoyne Oct 29, 2025
2e39bd1
Rename change zoom -> reset zoom
TrevorBurgoyne Oct 29, 2025
eddd576
configurable class keybinds
TrevorBurgoyne Oct 29, 2025
2b628d4
store keybinds that user sets
TrevorBurgoyne Oct 29, 2025
660897a
add collapse to keybind sections
TrevorBurgoyne Oct 29, 2025
ffff427
Reset to default
TrevorBurgoyne Oct 29, 2025
7a19c72
dont use local storage for og keybind check
TrevorBurgoyne Oct 29, 2025
e883802
fix original keybinds to use config options passed to constructor
TrevorBurgoyne Oct 29, 2025
f4c2494
centralize keybind config names
TrevorBurgoyne Oct 29, 2025
f79e925
update active class keybinds on subtask switch, and allow setting of …
TrevorBurgoyne Oct 29, 2025
3b2dff9
fix rendering collapsed keybind sections
TrevorBurgoyne Oct 29, 2025
a0d9b72
fix reseting class keybind to null
TrevorBurgoyne Oct 29, 2025
8556c65
use log message
TrevorBurgoyne Oct 29, 2025
729d50b
move reset button to the left of the keybind
TrevorBurgoyne Oct 29, 2025
d87c2df
separate reset zoom and show full image
TrevorBurgoyne Oct 29, 2025
a2564b6
check if user keybinds are actually different from default
TrevorBurgoyne Oct 29, 2025
6864e76
add some tests
TrevorBurgoyne Oct 30, 2025
16f2e14
fix tests
TrevorBurgoyne Oct 30, 2025
97ad330
tests for each keybinds
TrevorBurgoyne Oct 30, 2025
18b18a6
tests for non config keybinds
TrevorBurgoyne Oct 30, 2025
51c325d
add case for cancel annotation
TrevorBurgoyne Oct 30, 2025
78b89d2
test class keybinds
TrevorBurgoyne Oct 30, 2025
557dd8e
Bump version and update changelog
TrevorBurgoyne Oct 30, 2025
dc90f03
add more other keybinds
TrevorBurgoyne Oct 30, 2025
f8c7444
Apply suggestions from code review
TrevorBurgoyne Oct 30, 2025
4b2cc63
Merge branch 'feature/keybinds' of github.com:SenteraLLC/ulabel into …
TrevorBurgoyne Oct 30, 2025
0821168
minor tweaks to keybind descriptions
TrevorBurgoyne Oct 31, 2025
40ed0fc
Update src/toolbox_items/keybinds.ts
TrevorBurgoyne Nov 12, 2025
61d0b6f
Merge branch 'feature/annotation-list' into feature/keybinds
TrevorBurgoyne Nov 12, 2025
50d3254
change toggle target to header
TrevorBurgoyne Nov 12, 2025
cda9212
full collapse toolbox, button floats
TrevorBurgoyne Nov 12, 2025
079edc9
round out the button
TrevorBurgoyne Nov 12, 2025
750aa80
fix toolbox collapse css
TrevorBurgoyne Nov 12, 2025
2391f36
prevent scroll into view on toolbox collapse
TrevorBurgoyne Nov 12, 2025
8f92e93
Merge branch 'main' into feature/keybinds
TrevorBurgoyne Nov 17, 2025
8825931
update api spec
TrevorBurgoyne Nov 17, 2025
ef1ec21
changelog consistency
TrevorBurgoyne Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
- Do not add new dependencies unless given explicit permission.
- Do not modify the `package.json` or `package-lock.json` files unless instructed.


## Task Tracking
- Always start each step by referencing and updating your tasks in `.github/tasks.md`.
- Use checkboxes to track progress.
- Work on one task at a time.
- Do not mark tasks as complete until they are fully done. Ask for confirmation if unsure.
- Do not mark tasks as complete until they are fully done. Ask for confirmation if unsure.

## Repository Style
- Follow existing code style and conventions.
- Use `snake_case` for variable and function names.
- Use `PascalCase` for class names.
- Use `log_message` instead of `console.<method>` for logging messages.
109 changes: 75 additions & 34 deletions .github/tasks.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,77 @@
## Tasks
- [x] Read the description in [#234](https://github.com/SenteraLLC/ulabel/issues/234)
- [x] Write a clear summary of the requested change
- [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.
- [x] Add a sideways, clickable arrow to minimize the entire toolbox
- [x] Add collapse button to toolbox HTML
- [x] Add CSS styles for collapsed state
- [x] Add click handler to toggle collapsed state
- [x] Store collapsed state in localStorage
- [x] Test functionality
- [x] Move arrow to top of toolbox (instead of middle)
- [x] Make annbox expand when toolbox is collapsed
- [x] Make collapsed button visible
Comment on lines +2 to +10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my machine, this just expands the window sideways and adds a scrollbar on the bottom:
scrollbar

Hovering annotations will cause the dual-highlight functionality of the annotation list to trigger, and scroll sideways to bring the toolbox into view. Ideally this would physically collapse the toolbox instead of expanding like this.

Copy link
Member Author

@TrevorBurgoyne TrevorBurgoyne Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ive tried reworking this a bit, should definitely not do the "scroll to annotation list" when collapsed anymore. and now the toolbox should be fully hidden and not just partially obscured

- [x] Create a keybinds toolbox item
- [x] Research existing keybinds in the codebase
- [x] Create basic keybinds toolbox item file
- [x] Register keybinds toolbox item in configuration
- [x] Display list of all keybinds (the key) labeled with the name
- [x] Add hover tooltips with detailed descriptions (using title attribute)
- [x] Add collision detection and red highlighting
- [x] Implement editing for configurable keybinds
- [x] Test functionality
- [x] Add support for keybind "chords" (ie, "shift+i")
- [x] Update keybind edit handler to capture modifier keys (shift, ctrl, alt)
- [x] Create chord string format (e.g., "shift+i", "ctrl+alt+d")
- [x] Update key comparison logic in listeners to support chords
- [x] Update display to show chords properly (displays captured chord automatically)
- [x] Test chord functionality
- [x] Store collapse/expand for applicable toolbox items
- [x] Keybinds
- [x] Annotation List
- [x] Image Filters
- [x] Make all keybinds configurable
- [x] Minor changes to existing keybinds
- [x] Rename "Change Zoom" keybind to "Reset Zoom"
- [x] Change "Toggle Mode" label in the keybind toolbox item to "Toggle Annotation Mode"
- [x] Make class keybinds configurable in the keybinds toolbox item
- [x] Store keybinds in local storage
- [x] Only save them when a user explicitly sets it
- [x] For keybinds using a user setting, add a button to reset that keybind to default (should change keybind and delete stored keybind)
- [x] Add "Reset All to Default" button in the keybinds toolbox item that resets all keybinds and deletes stored user keybinds
- [x] Add a light yellow highlight on keybinds that are using a user setting instead of a default
- [x] Make sure that we update collison highlights after resetting a keybind to default
- [x] Only show the reset to default for keybinds with user settings, not on those already at the default
- [x] Make sure the class keybinds also are included in the keybind collision checks
- [x] Fix reset to default to use constructor-provided config values instead of hardcoded Configuration defaults
- [x] Centralize keybind config property names in Configuration.KEYBIND_CONFIG_KEYS constant
- [x] Make KEYBIND_CONFIG_KEYS dynamically generated from Configuration class properties
- [x] Rename create_bbox_on_initial_crop to create_bbox_on_initial_crop_keybind for consistency
- [x] Replace any console outputs with `log_message`
- [x] Replace the "reset_zoom_keybind" with two separate keybinds:
- [x] Add "show_full_image_keybind" property to Configuration
- [x] Update listeners.ts to use both keybinds independently
- [x] Update toolbox.ts to use both keybinds independently
- [x] Update api_spec.md to document both keybinds
- [x] Write e2e tests for the keybind toolbox item
- [x] Ability to set keybind to a chord
- [x] Ability to reset keybind
- [x] Ability to set a class keybind
- [x] Run tests to verify they pass
- [x] Write a e2e test for each keybind
- [x] reset_zoom_keybind (r)
- [x] show_full_image_keybind (shift+r)
- [x] create_point_annotation_keybind (c)
- [x] delete_annotation_keybind (d)
- [x] switch_subtask_keybind (z)
- [x] toggle_annotation_mode_keybind (u)
- [x] create_bbox_on_initial_crop_keybind (f)
- [x] toggle_brush_mode_keybind (g)
- [x] toggle_erase_mode_keybind (e)
- [x] increase_brush_size_keybind (])
- [x] decrease_brush_size_keybind ([)
- [x] annotation_size_small_keybind (s)
- [x] annotation_size_large_keybind (l)
- [x] annotation_size_plus_keybind (=)
- [x] annotation_size_minus_keybind (-)
- [x] annotation_vanish_keybind (v)
- [x] fly_to_next_annotation_keybind (tab)
- [x] fly_to_previous_annotation_keybind (shift+tab)

### Summary
Create an annotation list toolbox item that displays all annotations in a list format, similar to other annotation tools. The list should:
- Display each annotation (by ID or index)
- Allow show/hide of deprecated annotations (default: hide)
- Support grouping by class
- Enable clicking to "fly to" the annotation
- Show annotation labels/IDs (on hover or drawn on canvas)
- Display "current idx / total" when navigating through annotations
- Highlight annotations when hovering in the list

### Implementation Steps
- [x] 1. Research existing toolbox items and understand the toolbox structure
- [x] Read `src/toolbox.ts` to understand how toolbox items work
- [x] Review existing toolbox items in `src/toolbox_items/`
- [x] Understand how annotation data is accessed and structured
- [x] 2. Create the basic annotation list toolbox item
- [x] Create new file `src/toolbox_items/annotation_list.ts`
- [x] Implement basic UI structure (container, list elements)
- [x] Register the toolbox item in the main toolbox
- [x] 3. Implement core list functionality
- [x] Display all annotations with their ID/index
- [x] Add show/hide toggle for deprecated annotations (default: hide)
- [x] Add option to group by class
- [x] 4. Implement click-to-fly functionality
- [x] Integrate with existing "fly to" functionality from PR #230
- [x] Add click handlers to list items
- [x] Display "current idx / total" indicator
- [x] 5. Implement hover highlighting
- [x] Add hover handlers to list items
- [x] Integrate with existing annotation highlighting system
- [x] Ensure bidirectional highlighting (list hover → canvas, canvas hover → list)
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to this project will be documented here.

## [unreleased]

## [0.22.0] - Oct 30th, 2025
- Add collapsible toolbox with arrow button at top
- Toolbox collapse state persists in browser
- Annotation canvas expands to fill space when toolbox is collapsed
- Add `Keybinds` toolbox item for viewing and customizing keybinds
- Display all configurable keybinds with labels and descriptions
- Edit keybinds by clicking and pressing new key combination
- Support for modifier key chords (shift, ctrl, alt, meta)
- Collision detection with red highlighting for duplicate keybinds
- Reset individual keybinds or all keybinds to defaults
- Visual indicator (yellow highlight) for user-customized keybinds
- User keybind settings persist in browser
- Rename `create_bbox_on_initial_crop` to `create_bbox_on_initial_crop_keybind` for consistency
- Split `change_zoom_keybind` into two separate keybinds:
- `reset_zoom_keybind` (default: `r`) - Reset zoom to fit image
- `show_full_image_keybind` (default: `shift+r`) - Zoom to show full image
- Store collapse/expand state for Keybinds, Annotation List, and Image Filters toolbox items
- Add comprehensive e2e tests for keybind functionality and keybind toolbox item

## [0.21.0] - Oct 27th, 2025
- Add toast notification that shows on `fly_to` calls and shows annotation position in the ordering (e.g., "3 / 10")
- Add `AnnotationList` toolbox item for managing and navigating annotations
Expand Down
62 changes: 34 additions & 28 deletions api_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This should eventually be replaced with a more comprehensive approach to documen
- `ctrl+shift+z` or `cmd+shift+z`: Redo
- `scroll`: Zoom -- up for in, down for out
- `ctrl+scroll` or `shift+scroll` or `cmd+scroll`: Change frame -- down for next, up for previous
- `scrollclick+drag` or `ctrl+drag`: Pan
- `scrollclick+drag`: Pan
- Hold `shift` when closing a polygon to continue annotating a new region or hole.
- Hold `shift` when moving the cursor inside a polygon to begin annotating a new region or hole.
- Press `Escape` or `crtl+z` to cancel the start of a new region or hole.
Expand Down Expand Up @@ -50,30 +50,29 @@ class ULabel({
initial_line_size: number,
instructions_url: string,
toolbox_order: AllowedToolboxItem[],
default_keybinds = {
"annotation_size_small": string,
"annotation_size_large": string,
"annotation_size_plus": string,
"annotation_size_minus": string,
"annotation_vanish": string
},
distance_filter_toolbox_item: FilterDistanceConfig,
image_filters_toolbox_item: ImageFiltersConfig,
change_zoom_keybind: string,
reset_zoom_keybind: string,
show_full_image_keybind: string,
create_point_annotation_keybind: string,
default_annotation_size: number,
delete_annotation_keybind: string,
keypoint_slider_default_value: number,
filter_annotations_on_load: boolean,
switch_subtask_keybind: string,
toggle_annotation_mode_keybind: string,
create_bbox_on_initial_crop: string,
create_bbox_on_initial_crop_keybind: string,
toggle_brush_mode_keybind: string,
toggle_erase_mode_keybind: string,
increase_brush_size_keybind: string,
decrease_brush_size_keybind: string,
fly_to_next_annotation_keybind: string,
fly_to_previous_annotation_keybind: string | null,
fly_to_previous_annotation_keybind: string,
annotation_size_small_keybind: string,
annotation_size_large_keybind: string,
annotation_size_plus_keybind: string,
annotation_size_minus_keybind: string,
annotation_vanish_keybind: string,
fly_to_max_zoom: number,
n_annos_per_canvas: number
})
Expand Down Expand Up @@ -348,26 +347,15 @@ enum AllowedToolboxItem {
FilterDistance, // 8
Brush, // 9
ImageFilters, // 10
AnnotationList // 11
AnnotationList, // 11
Keybinds, // 12
}
```
You can access the AllowedToolboxItem enum by calling the static method:
```javascript
const AllowedToolboxItem = ULabel.get_allowed_toolbox_item_enum();
```

### `default_keybinds`
Keybinds can be set to control the annotation session. The default values are:
```javascript
{
"annotation_size_small": "s",
"annotation_size_large": "l",
"annotation_size_plus": "=",
"annotation_size_minus": "-",
"annotation_vanish": "v"
}
```

### `distance_filter_toolbox_item`
Configuration object for the `FilterDistance` toolbox item with the following custom definitions:
```javascript
Expand Down Expand Up @@ -437,8 +425,11 @@ The `AnnotationList` toolbox item displays all annotations in the current subtas

This toolbox item requires no configuration and can be added to the `toolbox_order` array using `AllowedToolboxItem.AnnotationList`.

### `change_zoom_keybind`
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`.
### `reset_zoom_keybind`
Keybind to reset the zoom level to the `initial_crop`. Default is `r`.

### `show_full_image_keybind`
Keybind to set the zoom level to show the full image. Default is `shift+r`.

### `create_point_annotation_keybind`
Keybind to create a point annotation at the mouse location. Default is `c`. Requires the active subtask to have a `point` mode.
Expand All @@ -461,7 +452,7 @@ Keybind to switch between subtasks. Default is `z`.
### `toggle_annotation_mode_keybind`
Keybind to toggle between annotation and selection modes. Default is `u`.

### `create_bbox_on_initial_crop`
### `create_bbox_on_initial_crop_keybind`
Keybind to create a bounding box annotation around the `initial_crop`. Default is `f`. Requires the active subtask to have a `bbox` mode.

### `toggle_brush_mode_keybind`
Expand All @@ -480,7 +471,22 @@ Keybind to decrease the brush size. Default is `[`. Requires the active subtask
Keybind to set the zoom to focus on the next annotation. Default is `Tab`, which also will disable any default browser behavior for `Tab`.

### `fly_to_previous_annotation_keybind`
Keybind to set the zoom to focus on the previous annotation. Default is `<null>`, which will default to `Shift+<fly_to_next_annotation_keybind>`.
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`).

### `annotation_size_small_keybind`
Keybind to set the annotation size to small for the current subtask. Default is `s`.

### `annotation_size_large_keybind`
Keybind to set the annotation size to large for the current subtask. Default is `l`.

### `annotation_size_plus_keybind`
Keybind to increment the annotation size for the current subtask. Default is `=`.

### `annotation_size_minus_keybind`
Keybind to decrement the annotation size for the current subtask. Default is `-`.

### `annotation_vanish_keybind`
Keybind to toggle vanish mode for annotations in the current subtask (lowercase toggles current subtask, uppercase toggles all subtasks). Default is `v`.

### `fly_to_max_zoom`
Maximum zoom factor used when flying-to an annotation. Default is `10`, value must be > `0`.
Expand Down
7 changes: 4 additions & 3 deletions demo/multi-class.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"name": "Truck",
"color": "orange",
"id": 12,
"keybind": "3",
},
],
"allowed_modes": ["bbox", "polygon", "contour", "polyline", "point", "tbar", "delete_polygon", "delete_bbox"],
Expand All @@ -65,7 +64,8 @@
{
"name": "Blurry",
"color": "gray",
"id": 20
"id": 20,
"keybind": "5",
},
{
"name": "Occluded",
Expand Down Expand Up @@ -119,6 +119,7 @@
"initial_line_size": 2,
"toolbox_order": [
AllowedToolboxItem.SubmitButtons,
AllowedToolboxItem.Keybinds,
AllowedToolboxItem.ModeSelect,
AllowedToolboxItem.AnnotationList,
AllowedToolboxItem.ImageFilters,
Expand All @@ -131,7 +132,7 @@
AllowedToolboxItem.RecolorActive,
],
"toggle_brush_mode_keybind": "f",
"create_bbox_on_initial_crop": "|",
"create_bbox_on_initial_crop_keybind": "|",
"click_and_drag_poly_annotations": false,
"anno_scaling_mode": "fixed",
"allow_annotations_outside_image": false,
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ export class ULabel {
last_brush_stroke: [number, number];
line_size: number;
anno_scaling_mode: AnnoScalingMode;
// Keybind editing state
is_editing_keybind: boolean;
// Render state
// TODO (joshua-dean): this is never assigned, is it used?
demo_canvas_context: CanvasRenderingContext2D;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ulabel",
"description": "An image annotation tool.",
"version": "0.21.0",
"version": "0.22.0",
"main": "dist/ulabel.min.js",
"module": "dist/ulabel.min.js",
"exports": {
Expand Down
3 changes: 2 additions & 1 deletion src/blobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,7 @@ div#${prntid} a.tbid-opt.sel {
div#${prntid} div.toolbox-name-header {
background-color: rgb(0, 128, 202);
margin: 0;
flex: 7;
}
div#${prntid}.ulabel-night div.toolbox-name-header {
background-color: rgb(0, 60, 95);
Expand Down Expand Up @@ -2036,7 +2037,7 @@ div#${prntid}.ulabel-night div.toolbox-name-header h1 span.version-number {
color: rgb(190, 190, 190);
}
div#${prntid} div.night-button-cont {
text-align: right;
text-align: left;
display: inline-block;
vertical-align: middle;
position: relative;
Expand Down
Loading