From ce0cf16ae591785eb9971f411846a82b19f2a86e Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 09:51:48 -0500 Subject: [PATCH 1/8] First batch of listeners with many type updates --- index.d.ts | 71 +++++++++ src/listeners.ts | 390 +++++++++++++++++++++++++++++++++++++++++++++++ src/subtask.ts | 1 + 3 files changed, 462 insertions(+) create mode 100644 src/listeners.ts diff --git a/index.d.ts b/index.d.ts index 6e6c6019..89f621aa 100644 --- a/index.d.ts +++ b/index.d.ts @@ -63,6 +63,7 @@ export type ClassDefinition = { name: string; id: number; color: string; + keybind?: string; }; export type SliderInfo = { @@ -194,6 +195,7 @@ export class ULabel { valid_class_ids: number[]; toolbox_order?: number[]; filter_distance_overlay?: FilterDistanceOverlay; + resize_observers: ResizeObserver[]; /** * @link https://github.com/SenteraLLC/ulabel/blob/main/api_spec.md#ulabel-constructor */ @@ -221,8 +223,13 @@ export class ULabel { public show_whole_image(): void; public swap_frame_image(new_src: string, frame?: number): string; public swap_anno_bg_color(new_bg_color: string): string; + // Subtasks public get_current_subtask_key(): string; public get_current_subtask(): ULabelSubtask; + public readjust_subtask_opacities(): void; + public set_subtask(st_key: string): void; + public switch_to_next_subtask(): void; + // Annotations public get_annotations(subtask: ULabelSubtask): ULabelAnnotation[]; public set_annotations(annotations: ULabelAnnotation[], subtask: ULabelSubtask); public set_saved(saved: boolean); @@ -245,14 +252,78 @@ export class ULabel { dist_prop: number; }, ): void; + // Brush public toggle_erase_mode(mouse_event: JQuery.TriggeredEvent): void; public toggle_brush_mode(mouse_event: JQuery.TriggeredEvent): void; public toggle_delete_class_id_in_toolbox(): void; public change_brush_size(scale_factor: number): void; + public recolor_brush_circle(): void; + public destroy_brush_circle(): void; + // Listeners public remove_listeners(): void; static get_allowed_toolbox_item_enum(): AllowedToolboxItem; static process_classes(ulabel_obj: ULabel, arg1: string, subtask_obj: ULabelSubtask); static build_id_dialogs(ulabel_obj: ULabel); + // Annotation lifecycle + // TODO (joshua-dean): type for redo_payload + public begin_annotation(mouse_event: JQuery.TriggeredEvent, redo_payload?: object): void; + public create_annotation( + spatial_type: ULabelSpatialType, + spatial_payload: ULabelSpatialPayload, + unique_id?: string, + ): void; + public create_nonspatial_annotation( + redo_payload?: object, + ): void; + public delete_annotation( + annotation_id: string, + redo_payload?: object, + record_action?: boolean, + ): void; + public get_active_class_id(): number; + public get_active_class_id_idx(): number; + // Mouse event handlers + public handle_mouse_down(mouse_event: JQuery.TriggeredEvent): void; + public handle_mouse_move(mouse_event: JQuery.TriggeredEvent): void; + public handle_mouse_up(mouse_event: JQuery.TriggeredEvent): void; + public handle_aux_click(mouse_event: JQuery.TriggeredEvent): void; + public handle_wheel(wheel_event: JQuery.TriggeredEvent): void; + public start_drag( + drag_key: string, + release_button: string, + mouse_event: JQuery.TriggeredEvent, + ): void; + public end_drag(mouse_event: JQuery.TriggeredEvent): void; + public drag_repan(mouse_event: JQuery.TriggeredEvent): void; + public drag_rezoom(mouse_event: JQuery.TriggeredEvent): void; + // Drawing + public rezoom( + foc_x?: number, + foc_y?: number, + abs?: boolean, + ): void; + public reposition_dialogs(): void; + public handle_toolbox_overflow(): void; + // ID Dialog + public set_id_dialog_payload_nopin( + class_ind: number, + dist_prop: number + ): void; + public update_id_dialog_display( + front?: boolean, + ): void; + public handle_id_dialog_click( + mouse_event: JQuery.TriggeredEvent, + annotation_id?: string, + new_class_idx?: number, + ): void; + public show_id_dialog( + gbx: number, + gby: number, + active_ann: string, // annotation id + thumbnail?: boolean, + nonspatial?: boolean, + ): void; } declare global { diff --git a/src/listeners.ts b/src/listeners.ts new file mode 100644 index 00000000..e66b39d9 --- /dev/null +++ b/src/listeners.ts @@ -0,0 +1,390 @@ +/** + * ULabel listener utilities. + * + * These primarily use JQuery, within the namespace "ulabel". + */ + +import { ULabel } from ".."; +import { DELETE_CLASS_ID, DELETE_MODES } from "./annotation"; + +const ULABEL_NAMESPACE = ".ulabel"; + +/** + * Create the handler for keypress events. + * @param ulabel ULabel instance + */ +function create_keypress_handler( + ulabel: ULabel, +) { + $(document).on( + "keypress" + ULABEL_NAMESPACE, + function (key_event) { + const current_subtask = ulabel.get_current_subtask(); + switch (key_event.key) { + // Create a point annotation at the mouse's current location + case ulabel.config.create_point_annotation_keybind: + // Only allow keypress to create point annotations + if (current_subtask.state.annotation_mode === "point") { + // Create an annotation based on the last mouse position + ulabel.begin_annotation(ulabel.state["last_move"]); + } + break; + // Create a bbox annotation around the initial_crop, + // or the whole image if inital_crop does not exist + case ulabel.config.create_bbox_on_initial_crop: + if (current_subtask.state.annotation_mode === "bbox") { + // Default to an annotation with size of image + // Create the coordinates for the bbox's spatial payload + let bbox_top_left: [number, number] = [0, 0]; + let bbox_bottom_right: [number, number] = [ + ulabel.config.image_width, + ulabel.config.image_height, + ]; + + // If an initial crop exists, use that instead + // TODO (joshua-dean): can't this just be "if (ulabel.config.initial_crop)"? + if (ulabel.config.initial_crop !== null && ulabel.config.initial_crop !== undefined) { + // Convenience + const initial_crop = ulabel.config.initial_crop; + + // Create the coordinates for the bbox's spatial payload + bbox_top_left = [initial_crop.left, initial_crop.top]; + bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; + } + + // Create the annotation + ulabel.create_annotation( + current_subtask.state.annotation_mode, + [bbox_top_left, bbox_bottom_right], + ); + } + break; + // Change to brush mode (for now, polygon only) + case ulabel.config.toggle_brush_mode_keybind: + ulabel.toggle_brush_mode(ulabel.state["last_move"]); + break; + // Change to erase mode (will also set the is_in_brush_mode state) + case ulabel.config.toggle_erase_mode_keybind: + ulabel.toggle_erase_mode(ulabel.state["last_move"]); + break; + // Increase brush size by 10% + case ulabel.config.increase_brush_size_keybind: + ulabel.change_brush_size(1.1); + break; + // Decrease brush size by 10% + case ulabel.config.decrease_brush_size_keybind: + ulabel.change_brush_size(1 / 1.1); + break; + case ulabel.config.change_zoom_keybind.toLowerCase(): + ulabel.show_initial_crop(); + break; + case ulabel.config.change_zoom_keybind.toUpperCase(): + ulabel.show_whole_image(); + break; + default: + // TODO (joshua-dean): break this out + if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { + // Check for class keybinds + for (let i = 0; i < current_subtask.class_defs.length; i++) { + const class_def = current_subtask.class_defs[i]; + if (class_def.keybind !== null && key_event.key === class_def.keybind) { + const st_key = ulabel.get_current_subtask_key(); + const class_button = $(`#tb-id-app--${st_key} a.tbid-opt`).eq(i); + if (class_button.hasClass("sel")) { + // If the class button is already selected, + // check if there is an active annotation, and if so, get it + let target_id = null; + if (current_subtask.state.active_id !== null) { + target_id = current_subtask.state.active_id; + } else if (current_subtask.state.move_candidate !== null) { + target_id = current_subtask.state.move_candidate["annid"]; + } + // Update the class of the active annotation + if (target_id !== null) { + // Set the annotation's class to the selected class + ulabel.handle_id_dialog_click( + ulabel.state["last_move"], + target_id, + ulabel.get_active_class_id_idx(), + ); + } + } else { + // Click the class button if not already selected + class_button.trigger("click"); + } + return; + } + } + } + break; + } + }, + ); +} + +/** + * Create a listener for the soft ID toolbox button. + * @param ulabel ULabel instance + */ +function create_soft_id_toolbox_button_listener( + ulabel: ULabel, +) { + $(document).on( + "click" + ULABEL_NAMESPACE, + `#${ulabel.config["toolbox_id"]} a.tbid-opt`, + function (click_event) { + const tgt_jq = $(click_event.currentTarget); + const pfx = "div#tb-id-app--" + ulabel.get_current_subtask_key(); + const current_subtask = ulabel.get_current_subtask(); + if (tgt_jq.attr("href") === "#") { + const current_id_button = $(pfx + " a.tbid-opt.sel"); + current_id_button.attr("href", "#"); + current_id_button.removeClass("sel"); + const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); + tgt_jq.addClass("sel"); + tgt_jq.removeAttr("href"); + const idarr = tgt_jq.attr("id").split("_"); + const rawid = parseInt(idarr[idarr.length - 1]); + ulabel.set_id_dialog_payload_nopin( + current_subtask["class_ids"].indexOf(rawid), + 1.0, + ); + ulabel.update_id_dialog_display(); + + // Update the class of the active annotation, + // except when toggling on the delete class + if (rawid !== DELETE_CLASS_ID) { + // Get the active annotation, if any + let target_id = null; + if (current_subtask.state.active_id !== null) { + target_id = current_subtask.state.active_id; + } else if (current_subtask.state.move_candidate !== null) { + target_id = current_subtask.state.move_candidate["annid"]; + } + + // Update the class of the active annotation + if (target_id !== null) { + // Set the annotation's class to the selected class + ulabel.handle_id_dialog_click( + ulabel.state["last_move"], + target_id, + ulabel.get_active_class_id_idx(), + ); + } else { + // If there is not active annotation, + // still update the brush circle if in brush mode + ulabel.recolor_brush_circle(); + } + } + + /* + If toggling off a delete class while still in delete mode, + re-toggle the delete class. + This occurs when using a keybind to change a hovered annotation's + class while in delete mode. + */ + if ( + old_id === DELETE_CLASS_ID && + DELETE_MODES.includes(current_subtask.state.annotation_mode) + ) { + $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); + } + } + }, + ); +} + +/** + * Create listeners for a ULabel instance. + * Consider breaking out anything longer than 10 lines. + * + * @param ulabel ULabel instance + */ +export function create_ulabel_listeners( + ulabel: ULabel, +) { + // ================= Mouse Events in the ID Dialog ================= + const id_dialog = $(".id_dialog"); + id_dialog.on( + "mousemove" + ULABEL_NAMESPACE, + function (mouse_event) { + if (!ulabel.get_current_subtask()["state"]["idd_thumbnail"]) { + ulabel.handle_id_dialog_hover(mouse_event); + } + }, + ); + + // ================= Mouse Events in the Annotation Container ================= + const annbox = $("#" + ulabel.config["annbox_id"]); + + // Detect and record mousedown + annbox.on( + "mousedown" + ULABEL_NAMESPACE, + function (click_event) { + ulabel.handle_mouse_down(click_event); + }, + ); + + // Prevent default for auxclick + $(document).on( + "auxclick" + ULABEL_NAMESPACE, + ulabel.handle_aux_click, + ); + + // Detect and record mouseup + $(document).on( + "mouseup" + ULABEL_NAMESPACE, + ulabel.handle_mouse_up.bind(ulabel), + ); + + $(window).on( + "click" + ULABEL_NAMESPACE, + function (click_event) { + if (click_event.shiftKey) { + click_event.preventDefault(); + } + }, + ); + + // Mouse movement has meaning in certain cases + annbox.on( + "mousemove" + ULABEL_NAMESPACE, + function (move_event) { + ulabel.handle_mouse_move(move_event); + }, + ); + + // ================= Uncategorized ================= + + create_keypress_handler(ulabel); + + // This listener does not use jquery because it requires being able to prevent default + // There are maybe some hacky ways to do this with jquery + // https://stackoverflow.com/questions/60357083/does-not-use-passive-listeners-to-improve-scrolling-performance-lighthouse-repo + // Detection ctrl+scroll + document.getElementById( + ulabel.config["annbox_id"], + ).addEventListener( + "wheel", + ulabel.handle_wheel.bind(ulabel), + ); + + // Create a resize observer to reposition dialogs + const dialog_resize_observer = new ResizeObserver(function () { + ulabel.reposition_dialogs(); + }); + + // Observe the changes on the imwrap_id element + dialog_resize_observer.observe( + document.getElementById(ulabel.config["imwrap_id"]), + ); + + // Store a reference + ulabel.resize_observers.push(dialog_resize_observer); + + // Create a resize observer to handle toolbox overflow + const tb_overflow_resize_observer = new ResizeObserver(function () { + ulabel.handle_toolbox_overflow(); + }); + + // Observe the changes on the ulabel container + tb_overflow_resize_observer.observe( + document.getElementById(ulabel.config["container_id"]), + ); + + // Store a reference + ulabel.resize_observers.push(tb_overflow_resize_observer); + + create_soft_id_toolbox_button_listener(ulabel); + + $(document).on( + "click" + ULABEL_NAMESPACE, + "a.tb-st-switch[href]", + function (click_event) { + const switch_to = $(click_event.target).attr("id").split("--")[1]; + + // Ignore if in the middle of annotation + if (ulabel.get_current_subtask()["state"]["is_in_progress"]) return; + + ulabel.set_subtask(switch_to); + }, + ); + + // Keybind to switch active subtask + $(document).on( + "keypress" + ULABEL_NAMESPACE, + function (e) { + // Ignore if in the middle of annotation + if (ulabel.get_current_subtask()["state"]["is_in_progress"]) return; + + // Check for the right keypress + if (e.key === ulabel.config.switch_subtask_keybind) { + ulabel.switch_to_next_subtask(); + } + }, + ); + + $(document).on( + "input" + ULABEL_NAMESPACE, + "input.frame_input", + () => ulabel.update_frame(), + ); + + $(document).on( + "input" + ULABEL_NAMESPACE, + "span.tb-st-range input", + () => ulabel.readjust_subtask_opacities(), + ); + + $(document).on( + "click" + ULABEL_NAMESPACE, + "div.fad_row.add a.add-glob-button", + () => ulabel.create_nonspatial_annotation(), + ); + + $(document).on( + "focus" + ULABEL_NAMESPACE, + "textarea.nonspatial_note", + () => $("div.frame_annotation_dialog.active").addClass("permopen"), + ); + + $(document).on( + "focusout" + ULABEL_NAMESPACE, + "textarea.nonspatial_note", + () => $("div.frame_annotation_dialog.permopen").removeClass("permopen"), + ); + + $(document).on( + "input" + ULABEL_NAMESPACE, + "textarea.nonspatial_note", + function (input_event) { + // Update annotation's text field + const annos = ulabel.get_current_subtask()["annotations"]["access"]; + const text_payload_anno_id = input_event.target.id.substring("note__".length); + annos[text_payload_anno_id]["text_payload"] = input_event.target.value; + }, + ); + + $(document).on( + "click" + ULABEL_NAMESPACE, + "a.fad_button.delete", + function (click_event) { + ulabel.delete_annotation(click_event.target.id.substring("delete__".length)); + }, + ); + + $(document).on( + "click" + ULABEL_NAMESPACE, + "a.fad_button.reclf", + function (click_event) { + // Show idd + ulabel.show_id_dialog( + click_event.pageX, + click_event.pageY, + click_event.target.id.substring("reclf__".length), + false, + true, + ); + }, + ); +} diff --git a/src/subtask.ts b/src/subtask.ts index cf3b44e1..bedb4038 100644 --- a/src/subtask.ts +++ b/src/subtask.ts @@ -69,6 +69,7 @@ export class ULabelSubtask { visible_dialogs: { [key: string]: ULabelDialogPosition; }; + spatial_type: ULabelSpatialType; }; constructor( From 572d921d2f4d7168564a7214a047a72c88cf8e2e Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 11:06:35 -0500 Subject: [PATCH 2/8] Add remainder of listeners --- index.d.ts | 34 +++++++++- src/listeners.ts | 163 ++++++++++++++++++++++++++++++++++++++++++++++- src/subtask.ts | 3 +- 3 files changed, 196 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 89f621aa..bab7ee7b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -252,18 +252,22 @@ export class ULabel { dist_prop: number; }, ): void; + // Brush - public toggle_erase_mode(mouse_event: JQuery.TriggeredEvent): void; - public toggle_brush_mode(mouse_event: JQuery.TriggeredEvent): void; + // TODO (joshua-dean): should these actually be optional? + public toggle_erase_mode(mouse_event?: JQuery.TriggeredEvent): void; + public toggle_brush_mode(mouse_event?: JQuery.TriggeredEvent): void; public toggle_delete_class_id_in_toolbox(): void; public change_brush_size(scale_factor: number): void; public recolor_brush_circle(): void; public destroy_brush_circle(): void; + // Listeners public remove_listeners(): void; static get_allowed_toolbox_item_enum(): AllowedToolboxItem; static process_classes(ulabel_obj: ULabel, arg1: string, subtask_obj: ULabelSubtask); static build_id_dialogs(ulabel_obj: ULabel); + // Annotation lifecycle // TODO (joshua-dean): type for redo_payload public begin_annotation(mouse_event: JQuery.TriggeredEvent, redo_payload?: object): void; @@ -280,8 +284,12 @@ export class ULabel { redo_payload?: object, record_action?: boolean, ): void; + public cancel_annotation(redo_payload?: object): void; public get_active_class_id(): number; public get_active_class_id_idx(): number; + public undo(is_internal_undo?: boolean): void; + public redo(): void; + // Mouse event handlers public handle_mouse_down(mouse_event: JQuery.TriggeredEvent): void; public handle_mouse_move(mouse_event: JQuery.TriggeredEvent): void; @@ -296,6 +304,24 @@ export class ULabel { public end_drag(mouse_event: JQuery.TriggeredEvent): void; public drag_repan(mouse_event: JQuery.TriggeredEvent): void; public drag_rezoom(mouse_event: JQuery.TriggeredEvent): void; + // "Mouse event interpreters" + public get_global_mouse_x(mouse_event: JQuery.TriggeredEvent): number; + public get_global_mouse_y(mouse_event: JQuery.TriggeredEvent): number; + // Edit suggestions + public suggest_edits( + mouse_event?: JQuery.TriggeredEvent, + nonspatial_id?: string, + ): void; + public show_global_edit_suggestion( + annid: string, + offset?: { + diffX: number; + diffY: number; + diffZ?: number; + }, + nonspatial_id?: string, + ): void; + public hide_global_edit_suggestion(): void; // Drawing public rezoom( foc_x?: number, @@ -324,6 +350,10 @@ export class ULabel { thumbnail?: boolean, nonspatial?: boolean, ): void; + // Cookies + static has_night_mode_cookie(): boolean; + static set_night_mode_cookie(): void; + static destroy_night_mode_cookie(): void; } declare global { diff --git a/src/listeners.ts b/src/listeners.ts index e66b39d9..2eb59dc1 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -5,7 +5,7 @@ */ import { ULabel } from ".."; -import { DELETE_CLASS_ID, DELETE_MODES } from "./annotation"; +import { DELETE_CLASS_ID, DELETE_MODES, NONSPATIAL_MODES } from "./annotation"; const ULABEL_NAMESPACE = ".ulabel"; @@ -194,6 +194,54 @@ function create_soft_id_toolbox_button_listener( ); } +/** + * Handler for ULabel keydown events. + * + * @param keydown_event Event to handle + * @param ulabel ULabel instance + * @returns Whether the event was handled + */ +function handle_keydown_event( + keydown_event: JQuery.KeyDownEvent, + ulabel: ULabel, +): boolean { + const shift = keydown_event.shiftKey; + const ctrl = keydown_event.ctrlKey || keydown_event.metaKey; + const key_is_z = ( + keydown_event.key === "z" || + keydown_event.key === "Z" || + keydown_event.code === "KeyZ" + ); + + if (ctrl && key_is_z) { + keydown_event.preventDefault(); + if (shift) { + ulabel.redo(); + } else { + ulabel.undo(); + } + return false; + } else { + const current_subtask = ulabel.get_current_subtask(); + switch (keydown_event.key) { + case "Escape": + // If in erase or brush mode, cancel the brush + if (current_subtask.state.is_in_erase_mode) { + ulabel.toggle_erase_mode(); + } else if (current_subtask.state.is_in_brush_mode) { + ulabel.toggle_brush_mode(); + } else if (current_subtask.state.starting_complex_polygon) { + // If starting a complex polygon, undo + ulabel.undo(); + } else if (current_subtask.state.is_in_progress) { + // If in the middle of drawing an annotation, cancel the annotation + ulabel.cancel_annotation(); + } + break; + } + } +} + /** * Create listeners for a ULabel instance. * Consider breaking out anything longer than 10 lines. @@ -387,4 +435,117 @@ export function create_ulabel_listeners( ); }, ); + + $(document).on( + "mouseenter" + ULABEL_NAMESPACE, + "div.fad_annotation_rows div.fad_row", + function (mouse_event) { + // Show thumbnail for idd + ulabel.suggest_edits( + null, + $(mouse_event.currentTarget).attr("id").substring("row__".length), + ); + }, + ); + + $(document).on( + "mouseleave" + ULABEL_NAMESPACE, + "div.fad_annotation_rows div.fad_row", + function () { + // Show thumbnail for idd + if ( + ulabel.get_current_subtask()["state"]["idd_visible"] && + !ulabel.get_current_subtask()["state"]["idd_thumbnail"] + ) { + return; + } + ulabel.suggest_edits(null); + }, + ); + + $(document).on( + "keypress" + ULABEL_NAMESPACE, + function (keypress_event) { + // Check the key pressed against the delete annotation keybind in the config + if (keypress_event.key === ulabel.config.delete_annotation_keybind) { + // Check the edit_candidate to make sure its not null and isn't nonspatial + const edit_cand = ulabel.get_current_subtask().state.edit_candidate; + if (edit_cand !== null && !NONSPATIAL_MODES.includes(edit_cand.spatial_type)) { + ulabel.delete_annotation(edit_cand.annid); + } + } + }, + ); + + // Listener for id_dialog click interactions + $(document).on( + "click" + ULABEL_NAMESPACE, + "#" + ulabel.config["container_id"] + " a.id-dialog-clickable-indicator", + function (click_event) { + if (!ulabel.get_current_subtask()["state"]["idd_thumbnail"]) { + ulabel.handle_id_dialog_click(click_event); + } + }, + ); + + $(document).on( + "click" + ULABEL_NAMESPACE, + ".global_edit_suggestion a.reid_suggestion", + function (e) { + const crst = ulabel.get_current_subtask(); + const annid = crst["state"]["idd_associated_annotation"]; + ulabel.hide_global_edit_suggestion(); + ulabel.show_id_dialog( + ulabel.get_global_mouse_x(e), + ulabel.get_global_mouse_y(e), + annid, + false, + ); + }, + ); + + $(document).on( + "click" + ULABEL_NAMESPACE, + "#" + ulabel.config["annbox_id"] + " .delete_suggestion", + function () { + const crst = ulabel.get_current_subtask(); + ulabel.delete_annotation(crst["state"]["move_candidate"]["annid"]); + }, + ); + + // Button to save annotations + $(document).on( + "click" + ULABEL_NAMESPACE, + "#" + ulabel.config["toolbox_id"] + " a.night-button", + function () { + const root_container = $("#" + ulabel.config["container_id"]); + if (root_container.hasClass("ulabel-night")) { + root_container.removeClass("ulabel-night"); + ULabel.destroy_night_mode_cookie(); + } else { + root_container.addClass("ulabel-night"); + ULabel.set_night_mode_cookie(); + } + }, + ); + + // Keyboard only events + $(document).on( + "keydown" + ULABEL_NAMESPACE, + function (keydown_event: JQuery.KeyDownEvent) { + handle_keydown_event(keydown_event, ulabel); + }, + ); + + $(window).on( + "beforeunload" + ULABEL_NAMESPACE, + function () { + if (ulabel.state["edited"]) { + // Return of anything other than `undefined` + // will trigger the browser's confirmation dialog + // Custom messages are not supported + return 1; + } + }, + ); } diff --git a/src/subtask.ts b/src/subtask.ts index bedb4038..a271a430 100644 --- a/src/subtask.ts +++ b/src/subtask.ts @@ -47,6 +47,7 @@ export class ULabelSubtask { access: string | number | [number, number]; distance: number; point: [number, number]; // Mouse location + spatial_type: ULabelSpatialType; }; first_explicit_assignment: boolean; front_context: CanvasRenderingContext2D; @@ -54,7 +55,7 @@ export class ULabelSubtask { class_id: number; confidence: number; }[]; - idd_associated_annotation: unknown; // TODO: figure out what type this is + idd_associated_annotation: string; idd_id: string; idd_id_front: string; idd_thumbnail: boolean; From 348b9c136c492dd291b938909e4a2c49f07ae6a1 Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 11:56:19 -0500 Subject: [PATCH 3/8] Fix build issues by pulling night mode cookie --- src/cookies.ts | 45 +++ src/index.js | 760 +++++++++++++++++++++++------------------------ src/listeners.ts | 7 +- 3 files changed, 422 insertions(+), 390 deletions(-) create mode 100644 src/cookies.ts diff --git a/src/cookies.ts b/src/cookies.ts new file mode 100644 index 00000000..d1fc2dcf --- /dev/null +++ b/src/cookies.ts @@ -0,0 +1,45 @@ +/** + * ULabel cookie utilities. + */ + +export abstract class NightModeCookie { + /** + * The name of the cookie that stores the night mode preference. + */ + public static readonly COOKIE_NAME: string = "nightmode"; + + /** + * Return whether the document has a night mode cookie. + */ + public static exists_in_document(): boolean { + const cookie_components = document.cookie.split(";"); + const night_mode_comp = cookie_components.find( + row => row.trim().startsWith(`${NightModeCookie.COOKIE_NAME}=true`), + ); + return night_mode_comp !== undefined; + } + + /** + * Set the night mode cookie. + */ + public static set_cookie(): void { + const d = new Date(); + d.setTime(d.getTime() + (10000 * 24 * 60 * 60 * 1000)); + document.cookie = [ + NightModeCookie.COOKIE_NAME + "=true", + "expires=" + d.toUTCString(), + "path=/", + ].join(";"); + } + + /** + * Destroy the night mode cookie. + */ + public static destroy_cookie() { + document.cookie = [ + NightModeCookie.COOKIE_NAME + "=true", + "expires=Thu, 01 Jan 1970 00:00:00 UTC", + "path=/", + ].join(";"); + } +} diff --git a/src/index.js b/src/index.js index ac51857e..ac956c93 100644 --- a/src/index.js +++ b/src/index.js @@ -35,6 +35,9 @@ import { build_confidence_dialog, } from "../build/html_builder"; +import { create_ulabel_listeners } from "../build/listeners"; +import { NightModeCookie } from "../build/cookies"; + import $ from "jquery"; const jQuery = $; @@ -84,25 +87,6 @@ export class ULabel { return (new Date()).toISOString(); } - // =========================== NIGHT MODE COOKIES ======================================= - - static has_night_mode_cookie() { - if (document.cookie.split(";").find(row => row.trim().startsWith("nightmode=true"))) { - return true; - } - return false; - } - - static set_night_mode_cookie() { - let d = new Date(); - d.setTime(d.getTime() + (10000 * 24 * 60 * 60 * 1000)); - document.cookie = "nightmode=true;expires=" + d.toUTCString() + ";path=/"; - } - - static destroy_night_mode_cookie() { - document.cookie = "nightmode=true;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/"; - } - static get_allowed_toolbox_item_enum() { return AllowedToolboxItem; } @@ -176,373 +160,375 @@ export class ULabel { } static create_listeners(ul) { - // ================= Mouse Events in the ID Dialog ================= - - var iddg = $(".id_dialog"); - - // Hover interactions - - iddg.on("mousemove.ulabel", function (mouse_event) { - if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { - ul.handle_id_dialog_hover(mouse_event); - } - }); - - // ================= Mouse Events in the Annotation Container ================= - - var annbox = $("#" + ul.config["annbox_id"]); - - // Detect and record mousedown - annbox.on("mousedown.ulabel", (e) => { - ul.handle_mouse_down(e); - }); - - // Prevent default for auxclick - $(document).on("auxclick.ulabel", ul.handle_aux_click); - - // Detect and record mouseup - $(document).on("mouseup.ulabel", ul.handle_mouse_up.bind(ul)); - - $(window).on("click.ulabel", (e) => { - if (e.shiftKey) { - e.preventDefault(); - } - }); - - // Mouse movement has meaning in certain cases - annbox.on("mousemove.ulabel", (e) => { - ul.handle_mouse_move(e); - }); - - $(document).on("keypress.ulabel", (e) => { - // Check for the correct keypress - // Grab current subtask - const current_subtask = ul.get_current_subtask(); - switch (e.key) { - // Create a point annotation at the mouse's current location - case ul.config.create_point_annotation_keybind: - // Only allow keypress to create point annotations - if (current_subtask.state.annotation_mode === "point") { - // Create an annotation based on the last mouse position - ul.begin_annotation(ul.state["last_move"]); - } - break; - // Create a bbox annotation around the initial_crop. Or the whole image if inital_crop does not exist - case ul.config.create_bbox_on_initial_crop: - if (current_subtask.state.annotation_mode === "bbox") { - // Default to an annotation with size of image - // Create the coordinates for the bbox's spatial payload - let bbox_top_left = [0, 0]; - let bbox_bottom_right = [ul.config.image_width, ul.config.image_height]; - - // If an initial crop exists, use that instead - if (ul.config.initial_crop !== null && ul.config.initial_crop !== undefined) { - // Convenience - const initial_crop = ul.config.initial_crop; - - // Create the coordinates for the bbox's spatial payload - bbox_top_left = [initial_crop.left, initial_crop.top]; - bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; - } - - // Create the annotation - ul.create_annotation(current_subtask.state.annotation_mode, [bbox_top_left, bbox_bottom_right]); - } - break; - // Change to brush mode (for now, polygon only) - case ul.config.toggle_brush_mode_keybind: - ul.toggle_brush_mode(ul.state["last_move"]); - break; - // Change to erase mode (will also set the is_in_brush_mode state) - case ul.config.toggle_erase_mode_keybind: - ul.toggle_erase_mode(ul.state["last_move"]); - break; - // Increase brush size by 10% - case ul.config.increase_brush_size_keybind: - ul.change_brush_size(1.1); - break; - // Decrease brush size by 10% - case ul.config.decrease_brush_size_keybind: - ul.change_brush_size(1 / 1.1); - break; - case ul.config.change_zoom_keybind.toLowerCase(): - ul.show_initial_crop(); - break; - case ul.config.change_zoom_keybind.toUpperCase(): - ul.show_whole_image(); - break; - default: - if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { - // Check for class keybinds - for (let i = 0; i < current_subtask.class_defs.length; i++) { - const class_def = current_subtask.class_defs[i]; - if (class_def.keybind !== null && e.key === class_def.keybind) { - let class_button = $(`#tb-id-app--${ul.get_current_subtask_key()} a.tbid-opt`).eq(i); - if (class_button.hasClass("sel")) { - // If the class button is already selected, check if there is an active annotation - // Get the active annotation, if any - let target_id = null; - if (current_subtask.state.active_id !== null) { - target_id = current_subtask.state.active_id; - } else if (current_subtask.state.move_candidate !== null) { - target_id = current_subtask.state.move_candidate["annid"]; - } - // Update the class of the active annotation - if (target_id !== null) { - // Set the annotation's class to the selected class - ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); - } - } else { - // Click the class button if not already selected - class_button.trigger("click"); - } - return; - } - } - } - - break; - } - }); - - // This listener does not use jquery because it requires being able to prevent default - // There are maybe some hacky ways to do this with jquery - // https://stackoverflow.com/questions/60357083/does-not-use-passive-listeners-to-improve-scrolling-performance-lighthouse-repo - // Detection ctrl+scroll - document.getElementById(ul.config["annbox_id"]).addEventListener("wheel", ul.handle_wheel.bind(ul)); - - // Create a resize observer to reposition dialogs - let dialog_resize_observer = new ResizeObserver(function () { - ul.reposition_dialogs(); - }); - - // Observe the changes on the imwrap_id element - dialog_resize_observer.observe(document.getElementById(ul.config["imwrap_id"])); - - // Store a reference - ul.resize_observers.push(dialog_resize_observer); - - // Create a resize observer to handle toolbox overflow - let tb_overflow_resize_observer = new ResizeObserver(function () { - ul.handle_toolbox_overflow(); - }); - - // Observe the changes on the ulabel container - tb_overflow_resize_observer.observe(document.getElementById(ul.config["container_id"])); - - // Store a reference - ul.resize_observers.push(tb_overflow_resize_observer); - - // Listener for soft id toolbox buttons - $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.tbid-opt", (e) => { - let tgt_jq = $(e.currentTarget); - let pfx = "div#tb-id-app--" + ul.get_current_subtask_key(); - const current_subtask = ul.get_current_subtask(); - if (tgt_jq.attr("href") === "#") { - const current_id_button = $(pfx + " a.tbid-opt.sel"); - current_id_button.attr("href", "#"); - current_id_button.removeClass("sel"); - const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); - tgt_jq.addClass("sel"); - tgt_jq.removeAttr("href"); - let idarr = tgt_jq.attr("id").split("_"); - let rawid = parseInt(idarr[idarr.length - 1]); - ul.set_id_dialog_payload_nopin(current_subtask["class_ids"].indexOf(rawid), 1.0); - ul.update_id_dialog_display(); - - // Update the class of the active annotation, except when toggling on the delete class - if (rawid !== DELETE_CLASS_ID) { - // Get the active annotation, if any - let target_id = null; - if (current_subtask.state.active_id !== null) { - target_id = current_subtask.state.active_id; - } else if (current_subtask.state.move_candidate !== null) { - target_id = current_subtask.state.move_candidate["annid"]; - } - - // Update the class of the active annotation - if (target_id !== null) { - // Set the annotation's class to the selected class - ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); - } else { - // If there is not active annotation, still update the brush circle if in brush mode - ul.recolor_brush_circle(); - } - } - - // If toggling off a delete class while still in delete mode, re-toggle the delete class - // This occurs when using a keybind to change a hovered annotation's class while in delete mode - if (old_id === DELETE_CLASS_ID && DELETE_MODES.includes(current_subtask.state.annotation_mode)) { - $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); - } - } - }); - - $(document).on("click.ulabel", "a.tb-st-switch[href]", (e) => { - let switch_to = $(e.target).attr("id").split("--")[1]; - - // Ignore if in the middle of annotation - if (ul.get_current_subtask()["state"]["is_in_progress"]) { - return; - } - - ul.set_subtask(switch_to); - }); - - // Keybind to switch active subtask - $(document).on("keypress.ulabel", (e) => { - // Ignore if in the middle of annotation - if (ul.get_current_subtask()["state"]["is_in_progress"]) { - return; - } - - // Check for the right keypress - if (e.key === ul.config.switch_subtask_keybind) { - ul.switch_to_next_subtask(); - } - }); - - $(document).on("input.ulabel", "input.frame_input", () => { - ul.update_frame(); - }); - - $(document).on("input.ulabel", "span.tb-st-range input", () => { - ul.readjust_subtask_opacities(); - }); - - $(document).on("click.ulabel", "div.fad_row.add a.add-glob-button", () => { - ul.create_nonspatial_annotation(); - }); - $(document).on("focus.ulabel", "textarea.nonspatial_note", () => { - $("div.frame_annotation_dialog.active").addClass("permopen"); - }); - $(document).on("focusout.ulabel", "textarea.nonspatial_note", () => { - $("div.frame_annotation_dialog.permopen").removeClass("permopen"); - }); - $(document).on("input.ulabel", "textarea.nonspatial_note", (e) => { - // Update annotation's text field - ul.get_current_subtask()["annotations"]["access"][e.target.id.substring("note__".length)]["text_payload"] = e.target.value; - }); - $(document).on("click.ulabel", "a.fad_button.delete", (e) => { - ul.delete_annotation(e.target.id.substring("delete__".length)); - }); - $(document).on("click.ulabel", "a.fad_button.reclf", (e) => { - // Show idd - ul.show_id_dialog(e.pageX, e.pageY, e.target.id.substring("reclf__".length), false, true); - }); - - $(document).on("mouseenter.ulabel", "div.fad_annotation_rows div.fad_row", (e) => { - // Show thumbnail for idd - ul.suggest_edits(null, $(e.currentTarget).attr("id").substring("row__".length)); - }); - $(document).on("mouseleave.ulabel", "div.fad_annotation_rows div.fad_row", () => { - // Show thumbnail for idd - if ( - ul.get_current_subtask()["state"]["idd_visible"] && - !ul.get_current_subtask()["state"]["idd_thumbnail"] - ) { - return; - } - ul.suggest_edits(null); - }); - $(document).on("keypress.ulabel", (e) => { - // Check the key pressed against the delete annotation keybind in the config - if (e.key === ul.config.delete_annotation_keybind) { - // Check the edit_candidate to make sure its not null and isn't nonspatial - if ( - ul.get_current_subtask().state.edit_candidate != null && - !NONSPATIAL_MODES.includes(ul.get_current_subtask().state.edit_candidate.spatial_type) - ) { - // Delete the active annotation - ul.delete_annotation(ul.get_current_subtask().state.edit_candidate.annid); - } - } - }); - - // Listener for id_dialog click interactions - $(document).on("click.ulabel", "#" + ul.config["container_id"] + " a.id-dialog-clickable-indicator", (e) => { - if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { - ul.handle_id_dialog_click(e); - } else { - // It's always covered up as a thumbnail. See below - } - }); - $(document).on("click.ulabel", ".global_edit_suggestion a.reid_suggestion", (e) => { - let crst = ul.get_current_subtask(); - let annid = crst["state"]["idd_associated_annotation"]; - ul.hide_global_edit_suggestion(); - ul.show_id_dialog( - ul.get_global_mouse_x(e), - ul.get_global_mouse_y(e), - annid, - false, - ); - }); - - $(document).on("click.ulabel", "#" + ul.config["annbox_id"] + " .delete_suggestion", () => { - let crst = ul.get_current_subtask(); - ul.delete_annotation(crst["state"]["move_candidate"]["annid"]); - }); - - // Button to save annotations - $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.night-button", function () { - if ($("#" + ul.config["container_id"]).hasClass("ulabel-night")) { - $("#" + ul.config["container_id"]).removeClass("ulabel-night"); - // Destroy any night cookie - ULabel.destroy_night_mode_cookie(); - } else { - $("#" + ul.config["container_id"]).addClass("ulabel-night"); - // Drop a night cookie - ULabel.set_night_mode_cookie(); - } - }); - - // Keyboard only events - $(document).on("keydown.ulabel", (keypress_event) => { - const shift = keypress_event.shiftKey; - const ctrl = keypress_event.ctrlKey || keypress_event.metaKey; - if (ctrl && - ( - keypress_event.key === "z" || - keypress_event.key === "Z" || - keypress_event.code === "KeyZ" - ) - ) { - keypress_event.preventDefault(); - if (shift) { - ul.redo(); - } else { - ul.undo(); - } - return false; - } else { - const current_subtask = ul.get_current_subtask(); - switch (keypress_event.key) { - case "Escape": - // If in erase or brush mode, cancel the brush - if (current_subtask.state.is_in_erase_mode) { - ul.toggle_erase_mode(); - } else if (current_subtask.state.is_in_brush_mode) { - ul.toggle_brush_mode(); - } else if (current_subtask.state.starting_complex_polygon) { - // If starting a complex polygon, undo - ul.undo(); - } else if (current_subtask.state.is_in_progress) { - // If in the middle of drawing an annotation, cancel the annotation - ul.cancel_annotation(); - } - break; - } - } - }); - - $(window).on("beforeunload.ulabel", () => { - if (ul.state["edited"]) { - // Return of anything other than `undefined` will trigger the browser's confirmation dialog - // Custom messages are not supported - return 1; - } - }); + create_ulabel_listeners(ul); + + // // ================= Mouse Events in the ID Dialog ================= + + // var iddg = $(".id_dialog"); + + // // Hover interactions + + // iddg.on("mousemove.ulabel", function (mouse_event) { + // if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { + // ul.handle_id_dialog_hover(mouse_event); + // } + // }); + + // // ================= Mouse Events in the Annotation Container ================= + + // var annbox = $("#" + ul.config["annbox_id"]); + + // // Detect and record mousedown + // annbox.on("mousedown.ulabel", (e) => { + // ul.handle_mouse_down(e); + // }); + + // // Prevent default for auxclick + // $(document).on("auxclick.ulabel", ul.handle_aux_click); + + // // Detect and record mouseup + // $(document).on("mouseup.ulabel", ul.handle_mouse_up.bind(ul)); + + // $(window).on("click.ulabel", (e) => { + // if (e.shiftKey) { + // e.preventDefault(); + // } + // }); + + // // Mouse movement has meaning in certain cases + // annbox.on("mousemove.ulabel", (e) => { + // ul.handle_mouse_move(e); + // }); + + // $(document).on("keypress.ulabel", (e) => { + // // Check for the correct keypress + // // Grab current subtask + // const current_subtask = ul.get_current_subtask(); + // switch (e.key) { + // // Create a point annotation at the mouse's current location + // case ul.config.create_point_annotation_keybind: + // // Only allow keypress to create point annotations + // if (current_subtask.state.annotation_mode === "point") { + // // Create an annotation based on the last mouse position + // ul.begin_annotation(ul.state["last_move"]); + // } + // break; + // // Create a bbox annotation around the initial_crop. Or the whole image if inital_crop does not exist + // case ul.config.create_bbox_on_initial_crop: + // if (current_subtask.state.annotation_mode === "bbox") { + // // Default to an annotation with size of image + // // Create the coordinates for the bbox's spatial payload + // let bbox_top_left = [0, 0]; + // let bbox_bottom_right = [ul.config.image_width, ul.config.image_height]; + + // // If an initial crop exists, use that instead + // if (ul.config.initial_crop !== null && ul.config.initial_crop !== undefined) { + // // Convenience + // const initial_crop = ul.config.initial_crop; + + // // Create the coordinates for the bbox's spatial payload + // bbox_top_left = [initial_crop.left, initial_crop.top]; + // bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; + // } + + // // Create the annotation + // ul.create_annotation(current_subtask.state.annotation_mode, [bbox_top_left, bbox_bottom_right]); + // } + // break; + // // Change to brush mode (for now, polygon only) + // case ul.config.toggle_brush_mode_keybind: + // ul.toggle_brush_mode(ul.state["last_move"]); + // break; + // // Change to erase mode (will also set the is_in_brush_mode state) + // case ul.config.toggle_erase_mode_keybind: + // ul.toggle_erase_mode(ul.state["last_move"]); + // break; + // // Increase brush size by 10% + // case ul.config.increase_brush_size_keybind: + // ul.change_brush_size(1.1); + // break; + // // Decrease brush size by 10% + // case ul.config.decrease_brush_size_keybind: + // ul.change_brush_size(1 / 1.1); + // break; + // case ul.config.change_zoom_keybind.toLowerCase(): + // ul.show_initial_crop(); + // break; + // case ul.config.change_zoom_keybind.toUpperCase(): + // ul.show_whole_image(); + // break; + // default: + // if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { + // // Check for class keybinds + // for (let i = 0; i < current_subtask.class_defs.length; i++) { + // const class_def = current_subtask.class_defs[i]; + // if (class_def.keybind !== null && e.key === class_def.keybind) { + // let class_button = $(`#tb-id-app--${ul.get_current_subtask_key()} a.tbid-opt`).eq(i); + // if (class_button.hasClass("sel")) { + // // If the class button is already selected, check if there is an active annotation + // // Get the active annotation, if any + // let target_id = null; + // if (current_subtask.state.active_id !== null) { + // target_id = current_subtask.state.active_id; + // } else if (current_subtask.state.move_candidate !== null) { + // target_id = current_subtask.state.move_candidate["annid"]; + // } + // // Update the class of the active annotation + // if (target_id !== null) { + // // Set the annotation's class to the selected class + // ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); + // } + // } else { + // // Click the class button if not already selected + // class_button.trigger("click"); + // } + // return; + // } + // } + // } + + // break; + // } + // }); + + // // This listener does not use jquery because it requires being able to prevent default + // // There are maybe some hacky ways to do this with jquery + // // https://stackoverflow.com/questions/60357083/does-not-use-passive-listeners-to-improve-scrolling-performance-lighthouse-repo + // // Detection ctrl+scroll + // document.getElementById(ul.config["annbox_id"]).addEventListener("wheel", ul.handle_wheel.bind(ul)); + + // // Create a resize observer to reposition dialogs + // let dialog_resize_observer = new ResizeObserver(function () { + // ul.reposition_dialogs(); + // }); + + // // Observe the changes on the imwrap_id element + // dialog_resize_observer.observe(document.getElementById(ul.config["imwrap_id"])); + + // // Store a reference + // ul.resize_observers.push(dialog_resize_observer); + + // // Create a resize observer to handle toolbox overflow + // let tb_overflow_resize_observer = new ResizeObserver(function () { + // ul.handle_toolbox_overflow(); + // }); + + // // Observe the changes on the ulabel container + // tb_overflow_resize_observer.observe(document.getElementById(ul.config["container_id"])); + + // // Store a reference + // ul.resize_observers.push(tb_overflow_resize_observer); + + // // Listener for soft id toolbox buttons + // $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.tbid-opt", (e) => { + // let tgt_jq = $(e.currentTarget); + // let pfx = "div#tb-id-app--" + ul.get_current_subtask_key(); + // const current_subtask = ul.get_current_subtask(); + // if (tgt_jq.attr("href") === "#") { + // const current_id_button = $(pfx + " a.tbid-opt.sel"); + // current_id_button.attr("href", "#"); + // current_id_button.removeClass("sel"); + // const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); + // tgt_jq.addClass("sel"); + // tgt_jq.removeAttr("href"); + // let idarr = tgt_jq.attr("id").split("_"); + // let rawid = parseInt(idarr[idarr.length - 1]); + // ul.set_id_dialog_payload_nopin(current_subtask["class_ids"].indexOf(rawid), 1.0); + // ul.update_id_dialog_display(); + + // // Update the class of the active annotation, except when toggling on the delete class + // if (rawid !== DELETE_CLASS_ID) { + // // Get the active annotation, if any + // let target_id = null; + // if (current_subtask.state.active_id !== null) { + // target_id = current_subtask.state.active_id; + // } else if (current_subtask.state.move_candidate !== null) { + // target_id = current_subtask.state.move_candidate["annid"]; + // } + + // // Update the class of the active annotation + // if (target_id !== null) { + // // Set the annotation's class to the selected class + // ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); + // } else { + // // If there is not active annotation, still update the brush circle if in brush mode + // ul.recolor_brush_circle(); + // } + // } + + // // If toggling off a delete class while still in delete mode, re-toggle the delete class + // // This occurs when using a keybind to change a hovered annotation's class while in delete mode + // if (old_id === DELETE_CLASS_ID && DELETE_MODES.includes(current_subtask.state.annotation_mode)) { + // $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); + // } + // } + // }); + + // $(document).on("click.ulabel", "a.tb-st-switch[href]", (e) => { + // let switch_to = $(e.target).attr("id").split("--")[1]; + + // // Ignore if in the middle of annotation + // if (ul.get_current_subtask()["state"]["is_in_progress"]) { + // return; + // } + + // ul.set_subtask(switch_to); + // }); + + // // Keybind to switch active subtask + // $(document).on("keypress.ulabel", (e) => { + // // Ignore if in the middle of annotation + // if (ul.get_current_subtask()["state"]["is_in_progress"]) { + // return; + // } + + // // Check for the right keypress + // if (e.key === ul.config.switch_subtask_keybind) { + // ul.switch_to_next_subtask(); + // } + // }); + + // $(document).on("input.ulabel", "input.frame_input", () => { + // ul.update_frame(); + // }); + + // $(document).on("input.ulabel", "span.tb-st-range input", () => { + // ul.readjust_subtask_opacities(); + // }); + + // $(document).on("click.ulabel", "div.fad_row.add a.add-glob-button", () => { + // ul.create_nonspatial_annotation(); + // }); + // $(document).on("focus.ulabel", "textarea.nonspatial_note", () => { + // $("div.frame_annotation_dialog.active").addClass("permopen"); + // }); + // $(document).on("focusout.ulabel", "textarea.nonspatial_note", () => { + // $("div.frame_annotation_dialog.permopen").removeClass("permopen"); + // }); + // $(document).on("input.ulabel", "textarea.nonspatial_note", (e) => { + // // Update annotation's text field + // ul.get_current_subtask()["annotations"]["access"][e.target.id.substring("note__".length)]["text_payload"] = e.target.value; + // }); + // $(document).on("click.ulabel", "a.fad_button.delete", (e) => { + // ul.delete_annotation(e.target.id.substring("delete__".length)); + // }); + // $(document).on("click.ulabel", "a.fad_button.reclf", (e) => { + // // Show idd + // ul.show_id_dialog(e.pageX, e.pageY, e.target.id.substring("reclf__".length), false, true); + // }); + + // $(document).on("mouseenter.ulabel", "div.fad_annotation_rows div.fad_row", (e) => { + // // Show thumbnail for idd + // ul.suggest_edits(null, $(e.currentTarget).attr("id").substring("row__".length)); + // }); + // $(document).on("mouseleave.ulabel", "div.fad_annotation_rows div.fad_row", () => { + // // Show thumbnail for idd + // if ( + // ul.get_current_subtask()["state"]["idd_visible"] && + // !ul.get_current_subtask()["state"]["idd_thumbnail"] + // ) { + // return; + // } + // ul.suggest_edits(null); + // }); + // $(document).on("keypress.ulabel", (e) => { + // // Check the key pressed against the delete annotation keybind in the config + // if (e.key === ul.config.delete_annotation_keybind) { + // // Check the edit_candidate to make sure its not null and isn't nonspatial + // if ( + // ul.get_current_subtask().state.edit_candidate != null && + // !NONSPATIAL_MODES.includes(ul.get_current_subtask().state.edit_candidate.spatial_type) + // ) { + // // Delete the active annotation + // ul.delete_annotation(ul.get_current_subtask().state.edit_candidate.annid); + // } + // } + // }); + + // // Listener for id_dialog click interactions + // $(document).on("click.ulabel", "#" + ul.config["container_id"] + " a.id-dialog-clickable-indicator", (e) => { + // if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { + // ul.handle_id_dialog_click(e); + // } else { + // // It's always covered up as a thumbnail. See below + // } + // }); + // $(document).on("click.ulabel", ".global_edit_suggestion a.reid_suggestion", (e) => { + // let crst = ul.get_current_subtask(); + // let annid = crst["state"]["idd_associated_annotation"]; + // ul.hide_global_edit_suggestion(); + // ul.show_id_dialog( + // ul.get_global_mouse_x(e), + // ul.get_global_mouse_y(e), + // annid, + // false, + // ); + // }); + + // $(document).on("click.ulabel", "#" + ul.config["annbox_id"] + " .delete_suggestion", () => { + // let crst = ul.get_current_subtask(); + // ul.delete_annotation(crst["state"]["move_candidate"]["annid"]); + // }); + + // // Button to save annotations + // $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.night-button", function () { + // if ($("#" + ul.config["container_id"]).hasClass("ulabel-night")) { + // $("#" + ul.config["container_id"]).removeClass("ulabel-night"); + // // Destroy any night cookie + // ULabel.destroy_night_mode_cookie(); + // } else { + // $("#" + ul.config["container_id"]).addClass("ulabel-night"); + // // Drop a night cookie + // ULabel.set_night_mode_cookie(); + // } + // }); + + // // Keyboard only events + // $(document).on("keydown.ulabel", (keypress_event) => { + // const shift = keypress_event.shiftKey; + // const ctrl = keypress_event.ctrlKey || keypress_event.metaKey; + // if (ctrl && + // ( + // keypress_event.key === "z" || + // keypress_event.key === "Z" || + // keypress_event.code === "KeyZ" + // ) + // ) { + // keypress_event.preventDefault(); + // if (shift) { + // ul.redo(); + // } else { + // ul.undo(); + // } + // return false; + // } else { + // const current_subtask = ul.get_current_subtask(); + // switch (keypress_event.key) { + // case "Escape": + // // If in erase or brush mode, cancel the brush + // if (current_subtask.state.is_in_erase_mode) { + // ul.toggle_erase_mode(); + // } else if (current_subtask.state.is_in_brush_mode) { + // ul.toggle_brush_mode(); + // } else if (current_subtask.state.starting_complex_polygon) { + // // If starting a complex polygon, undo + // ul.undo(); + // } else if (current_subtask.state.is_in_progress) { + // // If in the middle of drawing an annotation, cancel the annotation + // ul.cancel_annotation(); + // } + // break; + // } + // } + // }); + + // $(window).on("beforeunload.ulabel", () => { + // if (ul.state["edited"]) { + // // Return of anything other than `undefined` will trigger the browser's confirmation dialog + // // Custom messages are not supported + // return 1; + // } + // }); } static process_allowed_modes(ul, subtask_key, subtask) { @@ -1094,7 +1080,7 @@ export class ULabel { prep_window_html(this, this.config.toolbox_order); // Detect night cookie - if (ULabel.has_night_mode_cookie()) { + if (NightModeCookie.exists_in_document()) { $("#" + this.config["container_id"]).addClass("ulabel-night"); } diff --git a/src/listeners.ts b/src/listeners.ts index 2eb59dc1..8882de0f 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -4,7 +4,8 @@ * These primarily use JQuery, within the namespace "ulabel". */ -import { ULabel } from ".."; +import type { ULabel } from ".."; +import { NightModeCookie } from "./cookies"; import { DELETE_CLASS_ID, DELETE_MODES, NONSPATIAL_MODES } from "./annotation"; const ULABEL_NAMESPACE = ".ulabel"; @@ -521,10 +522,10 @@ export function create_ulabel_listeners( const root_container = $("#" + ulabel.config["container_id"]); if (root_container.hasClass("ulabel-night")) { root_container.removeClass("ulabel-night"); - ULabel.destroy_night_mode_cookie(); + NightModeCookie.destroy_cookie(); } else { root_container.addClass("ulabel-night"); - ULabel.set_night_mode_cookie(); + NightModeCookie.set_cookie(); } }, ); From 160c5d2fcfe943f949574bf20e4f3473e1c0a437 Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 12:03:19 -0500 Subject: [PATCH 4/8] Refactor additional listeners to handlers --- src/listeners.ts | 343 ++++++++++++++++++++++++----------------------- 1 file changed, 176 insertions(+), 167 deletions(-) diff --git a/src/listeners.ts b/src/listeners.ts index 8882de0f..06a6444a 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -2,6 +2,8 @@ * ULabel listener utilities. * * These primarily use JQuery, within the namespace "ulabel". + * Selectors and `.on` calls are intentionally kept in `create_ulabel_listeners`. + * Long handlers are broken out into separate functions. */ import type { ULabel } from ".."; @@ -11,188 +13,183 @@ import { DELETE_CLASS_ID, DELETE_MODES, NONSPATIAL_MODES } from "./annotation"; const ULABEL_NAMESPACE = ".ulabel"; /** - * Create the handler for keypress events. + * Handle keypress events. + * + * @param keypress_event Key event to handle * @param ulabel ULabel instance */ -function create_keypress_handler( +function handle_keypress_event( + keypress_event: JQuery.KeyPressEvent, ulabel: ULabel, ) { - $(document).on( - "keypress" + ULABEL_NAMESPACE, - function (key_event) { - const current_subtask = ulabel.get_current_subtask(); - switch (key_event.key) { - // Create a point annotation at the mouse's current location - case ulabel.config.create_point_annotation_keybind: - // Only allow keypress to create point annotations - if (current_subtask.state.annotation_mode === "point") { - // Create an annotation based on the last mouse position - ulabel.begin_annotation(ulabel.state["last_move"]); - } - break; - // Create a bbox annotation around the initial_crop, - // or the whole image if inital_crop does not exist - case ulabel.config.create_bbox_on_initial_crop: - if (current_subtask.state.annotation_mode === "bbox") { - // Default to an annotation with size of image - // Create the coordinates for the bbox's spatial payload - let bbox_top_left: [number, number] = [0, 0]; - let bbox_bottom_right: [number, number] = [ - ulabel.config.image_width, - ulabel.config.image_height, - ]; - - // If an initial crop exists, use that instead - // TODO (joshua-dean): can't this just be "if (ulabel.config.initial_crop)"? - if (ulabel.config.initial_crop !== null && ulabel.config.initial_crop !== undefined) { - // Convenience - const initial_crop = ulabel.config.initial_crop; - - // Create the coordinates for the bbox's spatial payload - bbox_top_left = [initial_crop.left, initial_crop.top]; - bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; - } + const current_subtask = ulabel.get_current_subtask(); + switch (keypress_event.key) { + // Create a point annotation at the mouse's current location + case ulabel.config.create_point_annotation_keybind: + // Only allow keypress to create point annotations + if (current_subtask.state.annotation_mode === "point") { + // Create an annotation based on the last mouse position + ulabel.begin_annotation(ulabel.state["last_move"]); + } + break; + // Create a bbox annotation around the initial_crop, + // or the whole image if inital_crop does not exist + case ulabel.config.create_bbox_on_initial_crop: + if (current_subtask.state.annotation_mode === "bbox") { + // Default to an annotation with size of image + // Create the coordinates for the bbox's spatial payload + let bbox_top_left: [number, number] = [0, 0]; + let bbox_bottom_right: [number, number] = [ + ulabel.config.image_width, + ulabel.config.image_height, + ]; + + // If an initial crop exists, use that instead + // TODO (joshua-dean): can't this just be "if (ulabel.config.initial_crop)"? + if (ulabel.config.initial_crop !== null && ulabel.config.initial_crop !== undefined) { + // Convenience + const initial_crop = ulabel.config.initial_crop; + + // Create the coordinates for the bbox's spatial payload + bbox_top_left = [initial_crop.left, initial_crop.top]; + bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; + } - // Create the annotation - ulabel.create_annotation( - current_subtask.state.annotation_mode, - [bbox_top_left, bbox_bottom_right], - ); - } - break; - // Change to brush mode (for now, polygon only) - case ulabel.config.toggle_brush_mode_keybind: - ulabel.toggle_brush_mode(ulabel.state["last_move"]); - break; - // Change to erase mode (will also set the is_in_brush_mode state) - case ulabel.config.toggle_erase_mode_keybind: - ulabel.toggle_erase_mode(ulabel.state["last_move"]); - break; - // Increase brush size by 10% - case ulabel.config.increase_brush_size_keybind: - ulabel.change_brush_size(1.1); - break; - // Decrease brush size by 10% - case ulabel.config.decrease_brush_size_keybind: - ulabel.change_brush_size(1 / 1.1); - break; - case ulabel.config.change_zoom_keybind.toLowerCase(): - ulabel.show_initial_crop(); - break; - case ulabel.config.change_zoom_keybind.toUpperCase(): - ulabel.show_whole_image(); - break; - default: - // TODO (joshua-dean): break this out - if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { - // Check for class keybinds - for (let i = 0; i < current_subtask.class_defs.length; i++) { - const class_def = current_subtask.class_defs[i]; - if (class_def.keybind !== null && key_event.key === class_def.keybind) { - const st_key = ulabel.get_current_subtask_key(); - const class_button = $(`#tb-id-app--${st_key} a.tbid-opt`).eq(i); - if (class_button.hasClass("sel")) { - // If the class button is already selected, - // check if there is an active annotation, and if so, get it - let target_id = null; - if (current_subtask.state.active_id !== null) { - target_id = current_subtask.state.active_id; - } else if (current_subtask.state.move_candidate !== null) { - target_id = current_subtask.state.move_candidate["annid"]; - } - // Update the class of the active annotation - if (target_id !== null) { - // Set the annotation's class to the selected class - ulabel.handle_id_dialog_click( - ulabel.state["last_move"], - target_id, - ulabel.get_active_class_id_idx(), - ); - } - } else { - // Click the class button if not already selected - class_button.trigger("click"); - } - return; + // Create the annotation + ulabel.create_annotation( + current_subtask.state.annotation_mode, + [bbox_top_left, bbox_bottom_right], + ); + } + break; + // Change to brush mode (for now, polygon only) + case ulabel.config.toggle_brush_mode_keybind: + ulabel.toggle_brush_mode(ulabel.state["last_move"]); + break; + // Change to erase mode (will also set the is_in_brush_mode state) + case ulabel.config.toggle_erase_mode_keybind: + ulabel.toggle_erase_mode(ulabel.state["last_move"]); + break; + // Increase brush size by 10% + case ulabel.config.increase_brush_size_keybind: + ulabel.change_brush_size(1.1); + break; + // Decrease brush size by 10% + case ulabel.config.decrease_brush_size_keybind: + ulabel.change_brush_size(1 / 1.1); + break; + case ulabel.config.change_zoom_keybind.toLowerCase(): + ulabel.show_initial_crop(); + break; + case ulabel.config.change_zoom_keybind.toUpperCase(): + ulabel.show_whole_image(); + break; + default: + // TODO (joshua-dean): break this out + if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { + // Check for class keybinds + for (let i = 0; i < current_subtask.class_defs.length; i++) { + const class_def = current_subtask.class_defs[i]; + if (class_def.keybind !== null && keypress_event.key === class_def.keybind) { + const st_key = ulabel.get_current_subtask_key(); + const class_button = $(`#tb-id-app--${st_key} a.tbid-opt`).eq(i); + if (class_button.hasClass("sel")) { + // If the class button is already selected, + // check if there is an active annotation, and if so, get it + let target_id = null; + if (current_subtask.state.active_id !== null) { + target_id = current_subtask.state.active_id; + } else if (current_subtask.state.move_candidate !== null) { + target_id = current_subtask.state.move_candidate["annid"]; + } + // Update the class of the active annotation + if (target_id !== null) { + // Set the annotation's class to the selected class + ulabel.handle_id_dialog_click( + ulabel.state["last_move"], + target_id, + ulabel.get_active_class_id_idx(), + ); } + } else { + // Click the class button if not already selected + class_button.trigger("click"); } + return; } - break; + } } - }, - ); + break; + } } /** - * Create a listener for the soft ID toolbox button. + * Handle a click on a soft ID toolbox button. + * + * @param click_event Click event * @param ulabel ULabel instance */ -function create_soft_id_toolbox_button_listener( +function handle_soft_id_toolbox_button_click( + click_event: JQuery.ClickEvent, ulabel: ULabel, ) { - $(document).on( - "click" + ULABEL_NAMESPACE, - `#${ulabel.config["toolbox_id"]} a.tbid-opt`, - function (click_event) { - const tgt_jq = $(click_event.currentTarget); - const pfx = "div#tb-id-app--" + ulabel.get_current_subtask_key(); - const current_subtask = ulabel.get_current_subtask(); - if (tgt_jq.attr("href") === "#") { - const current_id_button = $(pfx + " a.tbid-opt.sel"); - current_id_button.attr("href", "#"); - current_id_button.removeClass("sel"); - const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); - tgt_jq.addClass("sel"); - tgt_jq.removeAttr("href"); - const idarr = tgt_jq.attr("id").split("_"); - const rawid = parseInt(idarr[idarr.length - 1]); - ulabel.set_id_dialog_payload_nopin( - current_subtask["class_ids"].indexOf(rawid), - 1.0, - ); - ulabel.update_id_dialog_display(); - - // Update the class of the active annotation, - // except when toggling on the delete class - if (rawid !== DELETE_CLASS_ID) { - // Get the active annotation, if any - let target_id = null; - if (current_subtask.state.active_id !== null) { - target_id = current_subtask.state.active_id; - } else if (current_subtask.state.move_candidate !== null) { - target_id = current_subtask.state.move_candidate["annid"]; - } - - // Update the class of the active annotation - if (target_id !== null) { - // Set the annotation's class to the selected class - ulabel.handle_id_dialog_click( - ulabel.state["last_move"], - target_id, - ulabel.get_active_class_id_idx(), - ); - } else { - // If there is not active annotation, - // still update the brush circle if in brush mode - ulabel.recolor_brush_circle(); - } - } + const tgt_jq = $(click_event.currentTarget); + const pfx = "div#tb-id-app--" + ulabel.get_current_subtask_key(); + const current_subtask = ulabel.get_current_subtask(); + if (tgt_jq.attr("href") === "#") { + const current_id_button = $(pfx + " a.tbid-opt.sel"); + current_id_button.attr("href", "#"); + current_id_button.removeClass("sel"); + const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); + tgt_jq.addClass("sel"); + tgt_jq.removeAttr("href"); + const idarr = tgt_jq.attr("id").split("_"); + const rawid = parseInt(idarr[idarr.length - 1]); + ulabel.set_id_dialog_payload_nopin( + current_subtask["class_ids"].indexOf(rawid), + 1.0, + ); + ulabel.update_id_dialog_display(); + + // Update the class of the active annotation, + // except when toggling on the delete class + if (rawid !== DELETE_CLASS_ID) { + // Get the active annotation, if any + let target_id = null; + if (current_subtask.state.active_id !== null) { + target_id = current_subtask.state.active_id; + } else if (current_subtask.state.move_candidate !== null) { + target_id = current_subtask.state.move_candidate["annid"]; + } - /* - If toggling off a delete class while still in delete mode, - re-toggle the delete class. - This occurs when using a keybind to change a hovered annotation's - class while in delete mode. - */ - if ( - old_id === DELETE_CLASS_ID && - DELETE_MODES.includes(current_subtask.state.annotation_mode) - ) { - $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); - } + // Update the class of the active annotation + if (target_id !== null) { + // Set the annotation's class to the selected class + ulabel.handle_id_dialog_click( + ulabel.state["last_move"], + target_id, + ulabel.get_active_class_id_idx(), + ); + } else { + // If there is not active annotation, + // still update the brush circle if in brush mode + ulabel.recolor_brush_circle(); } - }, - ); + } + + /* + If toggling off a delete class while still in delete mode, + re-toggle the delete class. + This occurs when using a keybind to change a hovered annotation's + class while in delete mode. + */ + if ( + old_id === DELETE_CLASS_ID && + DELETE_MODES.includes(current_subtask.state.annotation_mode) + ) { + $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); + } + } } /** @@ -245,7 +242,7 @@ function handle_keydown_event( /** * Create listeners for a ULabel instance. - * Consider breaking out anything longer than 10 lines. + * Consider breaking out the handler if longer than 10 lines. * * @param ulabel ULabel instance */ @@ -305,7 +302,12 @@ export function create_ulabel_listeners( // ================= Uncategorized ================= - create_keypress_handler(ulabel); + $(document).on( + "keypress" + ULABEL_NAMESPACE, + function (keypress_event: JQuery.KeyPressEvent) { + handle_keypress_event(keypress_event, ulabel); + }, + ); // This listener does not use jquery because it requires being able to prevent default // There are maybe some hacky ways to do this with jquery @@ -344,7 +346,14 @@ export function create_ulabel_listeners( // Store a reference ulabel.resize_observers.push(tb_overflow_resize_observer); - create_soft_id_toolbox_button_listener(ulabel); + // create_soft_id_toolbox_button_listener(ulabel); + $(document).on( + "click" + ULABEL_NAMESPACE, + `#${ulabel.config["toolbox_id"]} a.tbid-opt`, + function (click_event: JQuery.ClickEvent) { + handle_soft_id_toolbox_button_click(click_event, ulabel); + }, + ); $(document).on( "click" + ULABEL_NAMESPACE, From 5514d4695cd2b391f03856fd5d5fc30895114ad2 Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 12:09:13 -0500 Subject: [PATCH 5/8] Update corresponding listener calls --- src/index.js | 388 +---------------------------------------------- src/listeners.ts | 23 ++- 2 files changed, 25 insertions(+), 386 deletions(-) diff --git a/src/index.js b/src/index.js index ac956c93..4ce2c7e5 100644 --- a/src/index.js +++ b/src/index.js @@ -35,7 +35,7 @@ import { build_confidence_dialog, } from "../build/html_builder"; -import { create_ulabel_listeners } from "../build/listeners"; +import { create_ulabel_listeners, remove_ulabel_listeners } from "../build/listeners"; import { NightModeCookie } from "../build/cookies"; import $ from "jquery"; @@ -146,389 +146,7 @@ export class ULabel { * Note that ULabel will not function properly after this method is called. */ remove_listeners() { - // Remove jquery event listeners - $(document).off(".ulabel"); // Unbind all events in the ulabel namespace from document - $(window).off(".ulabel"); // Unbind all events in the ulabel namespace from window - $(".id_dialog").off(".ulabel"); // Unbind all events in the ulabel namespace from .id_dialog - - // Go through each resize observer and disconnect them - if (this.resize_observers != null) { - this.resize_observers.forEach((observer) => { - observer.disconnect(); - }); - } - } - - static create_listeners(ul) { - create_ulabel_listeners(ul); - - // // ================= Mouse Events in the ID Dialog ================= - - // var iddg = $(".id_dialog"); - - // // Hover interactions - - // iddg.on("mousemove.ulabel", function (mouse_event) { - // if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { - // ul.handle_id_dialog_hover(mouse_event); - // } - // }); - - // // ================= Mouse Events in the Annotation Container ================= - - // var annbox = $("#" + ul.config["annbox_id"]); - - // // Detect and record mousedown - // annbox.on("mousedown.ulabel", (e) => { - // ul.handle_mouse_down(e); - // }); - - // // Prevent default for auxclick - // $(document).on("auxclick.ulabel", ul.handle_aux_click); - - // // Detect and record mouseup - // $(document).on("mouseup.ulabel", ul.handle_mouse_up.bind(ul)); - - // $(window).on("click.ulabel", (e) => { - // if (e.shiftKey) { - // e.preventDefault(); - // } - // }); - - // // Mouse movement has meaning in certain cases - // annbox.on("mousemove.ulabel", (e) => { - // ul.handle_mouse_move(e); - // }); - - // $(document).on("keypress.ulabel", (e) => { - // // Check for the correct keypress - // // Grab current subtask - // const current_subtask = ul.get_current_subtask(); - // switch (e.key) { - // // Create a point annotation at the mouse's current location - // case ul.config.create_point_annotation_keybind: - // // Only allow keypress to create point annotations - // if (current_subtask.state.annotation_mode === "point") { - // // Create an annotation based on the last mouse position - // ul.begin_annotation(ul.state["last_move"]); - // } - // break; - // // Create a bbox annotation around the initial_crop. Or the whole image if inital_crop does not exist - // case ul.config.create_bbox_on_initial_crop: - // if (current_subtask.state.annotation_mode === "bbox") { - // // Default to an annotation with size of image - // // Create the coordinates for the bbox's spatial payload - // let bbox_top_left = [0, 0]; - // let bbox_bottom_right = [ul.config.image_width, ul.config.image_height]; - - // // If an initial crop exists, use that instead - // if (ul.config.initial_crop !== null && ul.config.initial_crop !== undefined) { - // // Convenience - // const initial_crop = ul.config.initial_crop; - - // // Create the coordinates for the bbox's spatial payload - // bbox_top_left = [initial_crop.left, initial_crop.top]; - // bbox_bottom_right = [initial_crop.left + initial_crop.width, initial_crop.top + initial_crop.height]; - // } - - // // Create the annotation - // ul.create_annotation(current_subtask.state.annotation_mode, [bbox_top_left, bbox_bottom_right]); - // } - // break; - // // Change to brush mode (for now, polygon only) - // case ul.config.toggle_brush_mode_keybind: - // ul.toggle_brush_mode(ul.state["last_move"]); - // break; - // // Change to erase mode (will also set the is_in_brush_mode state) - // case ul.config.toggle_erase_mode_keybind: - // ul.toggle_erase_mode(ul.state["last_move"]); - // break; - // // Increase brush size by 10% - // case ul.config.increase_brush_size_keybind: - // ul.change_brush_size(1.1); - // break; - // // Decrease brush size by 10% - // case ul.config.decrease_brush_size_keybind: - // ul.change_brush_size(1 / 1.1); - // break; - // case ul.config.change_zoom_keybind.toLowerCase(): - // ul.show_initial_crop(); - // break; - // case ul.config.change_zoom_keybind.toUpperCase(): - // ul.show_whole_image(); - // break; - // default: - // if (!DELETE_MODES.includes(current_subtask.state.spatial_type)) { - // // Check for class keybinds - // for (let i = 0; i < current_subtask.class_defs.length; i++) { - // const class_def = current_subtask.class_defs[i]; - // if (class_def.keybind !== null && e.key === class_def.keybind) { - // let class_button = $(`#tb-id-app--${ul.get_current_subtask_key()} a.tbid-opt`).eq(i); - // if (class_button.hasClass("sel")) { - // // If the class button is already selected, check if there is an active annotation - // // Get the active annotation, if any - // let target_id = null; - // if (current_subtask.state.active_id !== null) { - // target_id = current_subtask.state.active_id; - // } else if (current_subtask.state.move_candidate !== null) { - // target_id = current_subtask.state.move_candidate["annid"]; - // } - // // Update the class of the active annotation - // if (target_id !== null) { - // // Set the annotation's class to the selected class - // ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); - // } - // } else { - // // Click the class button if not already selected - // class_button.trigger("click"); - // } - // return; - // } - // } - // } - - // break; - // } - // }); - - // // This listener does not use jquery because it requires being able to prevent default - // // There are maybe some hacky ways to do this with jquery - // // https://stackoverflow.com/questions/60357083/does-not-use-passive-listeners-to-improve-scrolling-performance-lighthouse-repo - // // Detection ctrl+scroll - // document.getElementById(ul.config["annbox_id"]).addEventListener("wheel", ul.handle_wheel.bind(ul)); - - // // Create a resize observer to reposition dialogs - // let dialog_resize_observer = new ResizeObserver(function () { - // ul.reposition_dialogs(); - // }); - - // // Observe the changes on the imwrap_id element - // dialog_resize_observer.observe(document.getElementById(ul.config["imwrap_id"])); - - // // Store a reference - // ul.resize_observers.push(dialog_resize_observer); - - // // Create a resize observer to handle toolbox overflow - // let tb_overflow_resize_observer = new ResizeObserver(function () { - // ul.handle_toolbox_overflow(); - // }); - - // // Observe the changes on the ulabel container - // tb_overflow_resize_observer.observe(document.getElementById(ul.config["container_id"])); - - // // Store a reference - // ul.resize_observers.push(tb_overflow_resize_observer); - - // // Listener for soft id toolbox buttons - // $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.tbid-opt", (e) => { - // let tgt_jq = $(e.currentTarget); - // let pfx = "div#tb-id-app--" + ul.get_current_subtask_key(); - // const current_subtask = ul.get_current_subtask(); - // if (tgt_jq.attr("href") === "#") { - // const current_id_button = $(pfx + " a.tbid-opt.sel"); - // current_id_button.attr("href", "#"); - // current_id_button.removeClass("sel"); - // const old_id = parseInt(current_id_button.attr("id").split("_").at(-1)); - // tgt_jq.addClass("sel"); - // tgt_jq.removeAttr("href"); - // let idarr = tgt_jq.attr("id").split("_"); - // let rawid = parseInt(idarr[idarr.length - 1]); - // ul.set_id_dialog_payload_nopin(current_subtask["class_ids"].indexOf(rawid), 1.0); - // ul.update_id_dialog_display(); - - // // Update the class of the active annotation, except when toggling on the delete class - // if (rawid !== DELETE_CLASS_ID) { - // // Get the active annotation, if any - // let target_id = null; - // if (current_subtask.state.active_id !== null) { - // target_id = current_subtask.state.active_id; - // } else if (current_subtask.state.move_candidate !== null) { - // target_id = current_subtask.state.move_candidate["annid"]; - // } - - // // Update the class of the active annotation - // if (target_id !== null) { - // // Set the annotation's class to the selected class - // ul.handle_id_dialog_click(ul.state["last_move"], target_id, ul.get_active_class_id_idx()); - // } else { - // // If there is not active annotation, still update the brush circle if in brush mode - // ul.recolor_brush_circle(); - // } - // } - - // // If toggling off a delete class while still in delete mode, re-toggle the delete class - // // This occurs when using a keybind to change a hovered annotation's class while in delete mode - // if (old_id === DELETE_CLASS_ID && DELETE_MODES.includes(current_subtask.state.annotation_mode)) { - // $("#toolbox_sel_" + DELETE_CLASS_ID).trigger("click"); - // } - // } - // }); - - // $(document).on("click.ulabel", "a.tb-st-switch[href]", (e) => { - // let switch_to = $(e.target).attr("id").split("--")[1]; - - // // Ignore if in the middle of annotation - // if (ul.get_current_subtask()["state"]["is_in_progress"]) { - // return; - // } - - // ul.set_subtask(switch_to); - // }); - - // // Keybind to switch active subtask - // $(document).on("keypress.ulabel", (e) => { - // // Ignore if in the middle of annotation - // if (ul.get_current_subtask()["state"]["is_in_progress"]) { - // return; - // } - - // // Check for the right keypress - // if (e.key === ul.config.switch_subtask_keybind) { - // ul.switch_to_next_subtask(); - // } - // }); - - // $(document).on("input.ulabel", "input.frame_input", () => { - // ul.update_frame(); - // }); - - // $(document).on("input.ulabel", "span.tb-st-range input", () => { - // ul.readjust_subtask_opacities(); - // }); - - // $(document).on("click.ulabel", "div.fad_row.add a.add-glob-button", () => { - // ul.create_nonspatial_annotation(); - // }); - // $(document).on("focus.ulabel", "textarea.nonspatial_note", () => { - // $("div.frame_annotation_dialog.active").addClass("permopen"); - // }); - // $(document).on("focusout.ulabel", "textarea.nonspatial_note", () => { - // $("div.frame_annotation_dialog.permopen").removeClass("permopen"); - // }); - // $(document).on("input.ulabel", "textarea.nonspatial_note", (e) => { - // // Update annotation's text field - // ul.get_current_subtask()["annotations"]["access"][e.target.id.substring("note__".length)]["text_payload"] = e.target.value; - // }); - // $(document).on("click.ulabel", "a.fad_button.delete", (e) => { - // ul.delete_annotation(e.target.id.substring("delete__".length)); - // }); - // $(document).on("click.ulabel", "a.fad_button.reclf", (e) => { - // // Show idd - // ul.show_id_dialog(e.pageX, e.pageY, e.target.id.substring("reclf__".length), false, true); - // }); - - // $(document).on("mouseenter.ulabel", "div.fad_annotation_rows div.fad_row", (e) => { - // // Show thumbnail for idd - // ul.suggest_edits(null, $(e.currentTarget).attr("id").substring("row__".length)); - // }); - // $(document).on("mouseleave.ulabel", "div.fad_annotation_rows div.fad_row", () => { - // // Show thumbnail for idd - // if ( - // ul.get_current_subtask()["state"]["idd_visible"] && - // !ul.get_current_subtask()["state"]["idd_thumbnail"] - // ) { - // return; - // } - // ul.suggest_edits(null); - // }); - // $(document).on("keypress.ulabel", (e) => { - // // Check the key pressed against the delete annotation keybind in the config - // if (e.key === ul.config.delete_annotation_keybind) { - // // Check the edit_candidate to make sure its not null and isn't nonspatial - // if ( - // ul.get_current_subtask().state.edit_candidate != null && - // !NONSPATIAL_MODES.includes(ul.get_current_subtask().state.edit_candidate.spatial_type) - // ) { - // // Delete the active annotation - // ul.delete_annotation(ul.get_current_subtask().state.edit_candidate.annid); - // } - // } - // }); - - // // Listener for id_dialog click interactions - // $(document).on("click.ulabel", "#" + ul.config["container_id"] + " a.id-dialog-clickable-indicator", (e) => { - // if (!ul.get_current_subtask()["state"]["idd_thumbnail"]) { - // ul.handle_id_dialog_click(e); - // } else { - // // It's always covered up as a thumbnail. See below - // } - // }); - // $(document).on("click.ulabel", ".global_edit_suggestion a.reid_suggestion", (e) => { - // let crst = ul.get_current_subtask(); - // let annid = crst["state"]["idd_associated_annotation"]; - // ul.hide_global_edit_suggestion(); - // ul.show_id_dialog( - // ul.get_global_mouse_x(e), - // ul.get_global_mouse_y(e), - // annid, - // false, - // ); - // }); - - // $(document).on("click.ulabel", "#" + ul.config["annbox_id"] + " .delete_suggestion", () => { - // let crst = ul.get_current_subtask(); - // ul.delete_annotation(crst["state"]["move_candidate"]["annid"]); - // }); - - // // Button to save annotations - // $(document).on("click.ulabel", "#" + ul.config["toolbox_id"] + " a.night-button", function () { - // if ($("#" + ul.config["container_id"]).hasClass("ulabel-night")) { - // $("#" + ul.config["container_id"]).removeClass("ulabel-night"); - // // Destroy any night cookie - // ULabel.destroy_night_mode_cookie(); - // } else { - // $("#" + ul.config["container_id"]).addClass("ulabel-night"); - // // Drop a night cookie - // ULabel.set_night_mode_cookie(); - // } - // }); - - // // Keyboard only events - // $(document).on("keydown.ulabel", (keypress_event) => { - // const shift = keypress_event.shiftKey; - // const ctrl = keypress_event.ctrlKey || keypress_event.metaKey; - // if (ctrl && - // ( - // keypress_event.key === "z" || - // keypress_event.key === "Z" || - // keypress_event.code === "KeyZ" - // ) - // ) { - // keypress_event.preventDefault(); - // if (shift) { - // ul.redo(); - // } else { - // ul.undo(); - // } - // return false; - // } else { - // const current_subtask = ul.get_current_subtask(); - // switch (keypress_event.key) { - // case "Escape": - // // If in erase or brush mode, cancel the brush - // if (current_subtask.state.is_in_erase_mode) { - // ul.toggle_erase_mode(); - // } else if (current_subtask.state.is_in_brush_mode) { - // ul.toggle_brush_mode(); - // } else if (current_subtask.state.starting_complex_polygon) { - // // If starting a complex polygon, undo - // ul.undo(); - // } else if (current_subtask.state.is_in_progress) { - // // If in the middle of drawing an annotation, cancel the annotation - // ul.cancel_annotation(); - // } - // break; - // } - // } - // }); - - // $(window).on("beforeunload.ulabel", () => { - // if (ul.state["edited"]) { - // // Return of anything other than `undefined` will trigger the browser's confirmation dialog - // // Custom messages are not supported - // return 1; - // } - // }); + remove_ulabel_listeners(this); } static process_allowed_modes(ul, subtask_key, subtask) { @@ -1140,7 +758,7 @@ export class ULabel { build_confidence_dialog(that); // Create listers to manipulate and export this object - ULabel.create_listeners(that); + create_ulabel_listeners(that); that.handle_toolbox_overflow(); diff --git a/src/listeners.ts b/src/listeners.ts index 06a6444a..61be0e68 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -242,7 +242,7 @@ function handle_keydown_event( /** * Create listeners for a ULabel instance. - * Consider breaking out the handler if longer than 10 lines. + * Consider breaking out long handlers. * * @param ulabel ULabel instance */ @@ -559,3 +559,24 @@ export function create_ulabel_listeners( }, ); } + +/** + * Remove listeners from a ULabel instance. + * + * @param ulabel ULabel instance. + */ +export function remove_ulabel_listeners( + ulabel: ULabel, +) { + // Remove jquery event listeners with the ulabel namespace + $(document).off(ULABEL_NAMESPACE); + $(window).off(ULABEL_NAMESPACE); + $(".id_dialog").off(ULABEL_NAMESPACE); + + // Go through each resize observer and disconnect them + if (ulabel.resize_observers != null) { + ulabel.resize_observers.forEach((observer) => { + observer.disconnect(); + }); + } +} From 355d1320cc2668e80a85c741c48f80083d2cad0c Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 12:24:37 -0500 Subject: [PATCH 6/8] Nicer formatting for `index.d.ts` --- index.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index bab7ee7b..156778e4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -223,12 +223,14 @@ export class ULabel { public show_whole_image(): void; public swap_frame_image(new_src: string, frame?: number): string; public swap_anno_bg_color(new_bg_color: string): string; + // Subtasks public get_current_subtask_key(): string; public get_current_subtask(): ULabelSubtask; public readjust_subtask_opacities(): void; public set_subtask(st_key: string): void; public switch_to_next_subtask(): void; + // Annotations public get_annotations(subtask: ULabelSubtask): ULabelAnnotation[]; public set_annotations(annotations: ULabelAnnotation[], subtask: ULabelSubtask); @@ -304,9 +306,11 @@ export class ULabel { public end_drag(mouse_event: JQuery.TriggeredEvent): void; public drag_repan(mouse_event: JQuery.TriggeredEvent): void; public drag_rezoom(mouse_event: JQuery.TriggeredEvent): void; + // "Mouse event interpreters" public get_global_mouse_x(mouse_event: JQuery.TriggeredEvent): number; public get_global_mouse_y(mouse_event: JQuery.TriggeredEvent): number; + // Edit suggestions public suggest_edits( mouse_event?: JQuery.TriggeredEvent, @@ -322,6 +326,7 @@ export class ULabel { nonspatial_id?: string, ): void; public hide_global_edit_suggestion(): void; + // Drawing public rezoom( foc_x?: number, @@ -330,6 +335,7 @@ export class ULabel { ): void; public reposition_dialogs(): void; public handle_toolbox_overflow(): void; + // ID Dialog public set_id_dialog_payload_nopin( class_ind: number, @@ -350,10 +356,6 @@ export class ULabel { thumbnail?: boolean, nonspatial?: boolean, ): void; - // Cookies - static has_night_mode_cookie(): boolean; - static set_night_mode_cookie(): void; - static destroy_night_mode_cookie(): void; } declare global { From 0077afa896d296cf0e77854ae5ecc4f0537c3118 Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Mon, 7 Oct 2024 12:29:34 -0500 Subject: [PATCH 7/8] More verbose event listener function args --- src/listeners.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/listeners.ts b/src/listeners.ts index 61be0e68..3cd98298 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -371,12 +371,12 @@ export function create_ulabel_listeners( // Keybind to switch active subtask $(document).on( "keypress" + ULABEL_NAMESPACE, - function (e) { + function (keypress_event) { // Ignore if in the middle of annotation if (ulabel.get_current_subtask()["state"]["is_in_progress"]) return; // Check for the right keypress - if (e.key === ulabel.config.switch_subtask_keybind) { + if (keypress_event.key === ulabel.config.switch_subtask_keybind) { ulabel.switch_to_next_subtask(); } }, @@ -501,13 +501,13 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, ".global_edit_suggestion a.reid_suggestion", - function (e) { + function (click_event) { const crst = ulabel.get_current_subtask(); const annid = crst["state"]["idd_associated_annotation"]; ulabel.hide_global_edit_suggestion(); ulabel.show_id_dialog( - ulabel.get_global_mouse_x(e), - ulabel.get_global_mouse_y(e), + ulabel.get_global_mouse_x(click_event), + ulabel.get_global_mouse_y(click_event), annid, false, ); From da9b15198e0fa33ab7701c6a90298b17a5559447 Mon Sep 17 00:00:00 2001 From: joshua-dean Date: Wed, 9 Oct 2024 14:09:16 -0500 Subject: [PATCH 8/8] Update inline handlers to be arrow functions --- eslint.config.mjs | 1 + index.d.ts | 2 +- src/listeners.ts | 63 ++++++++++++++++++++++------------------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index c3ab7f5b..c7566b08 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,6 +23,7 @@ export default [ }, stylistic.configs.customize( { + arrowParens: true, braceStyle: "1tbs", commaDangle: "always-multiline", indent: 4, diff --git a/index.d.ts b/index.d.ts index 156778e4..43d73f0e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -297,7 +297,7 @@ export class ULabel { public handle_mouse_move(mouse_event: JQuery.TriggeredEvent): void; public handle_mouse_up(mouse_event: JQuery.TriggeredEvent): void; public handle_aux_click(mouse_event: JQuery.TriggeredEvent): void; - public handle_wheel(wheel_event: JQuery.TriggeredEvent): void; + public handle_wheel(wheel_event: WheelEvent): void; public start_drag( drag_key: string, release_button: string, diff --git a/src/listeners.ts b/src/listeners.ts index 3cd98298..49ba6630 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -242,6 +242,7 @@ function handle_keydown_event( /** * Create listeners for a ULabel instance. + * Inline handlers must be arrow functions. * Consider breaking out long handlers. * * @param ulabel ULabel instance @@ -253,7 +254,7 @@ export function create_ulabel_listeners( const id_dialog = $(".id_dialog"); id_dialog.on( "mousemove" + ULABEL_NAMESPACE, - function (mouse_event) { + (mouse_event) => { if (!ulabel.get_current_subtask()["state"]["idd_thumbnail"]) { ulabel.handle_id_dialog_hover(mouse_event); } @@ -266,26 +267,24 @@ export function create_ulabel_listeners( // Detect and record mousedown annbox.on( "mousedown" + ULABEL_NAMESPACE, - function (click_event) { - ulabel.handle_mouse_down(click_event); - }, + (click_event) => ulabel.handle_mouse_down(click_event), ); // Prevent default for auxclick $(document).on( "auxclick" + ULABEL_NAMESPACE, - ulabel.handle_aux_click, + (mouse_event) => ulabel.handle_aux_click(mouse_event), ); // Detect and record mouseup $(document).on( "mouseup" + ULABEL_NAMESPACE, - ulabel.handle_mouse_up.bind(ulabel), + (mouseup_event) => ulabel.handle_mouse_up(mouseup_event), ); $(window).on( "click" + ULABEL_NAMESPACE, - function (click_event) { + (click_event) => { if (click_event.shiftKey) { click_event.preventDefault(); } @@ -295,16 +294,14 @@ export function create_ulabel_listeners( // Mouse movement has meaning in certain cases annbox.on( "mousemove" + ULABEL_NAMESPACE, - function (move_event) { - ulabel.handle_mouse_move(move_event); - }, + (move_event) => ulabel.handle_mouse_move(move_event), ); // ================= Uncategorized ================= $(document).on( "keypress" + ULABEL_NAMESPACE, - function (keypress_event: JQuery.KeyPressEvent) { + (keypress_event: JQuery.KeyPressEvent) => { handle_keypress_event(keypress_event, ulabel); }, ); @@ -317,13 +314,13 @@ export function create_ulabel_listeners( ulabel.config["annbox_id"], ).addEventListener( "wheel", - ulabel.handle_wheel.bind(ulabel), + (wheel_event) => ulabel.handle_wheel(wheel_event), ); // Create a resize observer to reposition dialogs - const dialog_resize_observer = new ResizeObserver(function () { - ulabel.reposition_dialogs(); - }); + const dialog_resize_observer = new ResizeObserver( + () => ulabel.reposition_dialogs(), + ); // Observe the changes on the imwrap_id element dialog_resize_observer.observe( @@ -334,9 +331,9 @@ export function create_ulabel_listeners( ulabel.resize_observers.push(dialog_resize_observer); // Create a resize observer to handle toolbox overflow - const tb_overflow_resize_observer = new ResizeObserver(function () { - ulabel.handle_toolbox_overflow(); - }); + const tb_overflow_resize_observer = new ResizeObserver( + () => ulabel.handle_toolbox_overflow(), + ); // Observe the changes on the ulabel container tb_overflow_resize_observer.observe( @@ -350,7 +347,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, `#${ulabel.config["toolbox_id"]} a.tbid-opt`, - function (click_event: JQuery.ClickEvent) { + (click_event: JQuery.ClickEvent) => { handle_soft_id_toolbox_button_click(click_event, ulabel); }, ); @@ -358,7 +355,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "a.tb-st-switch[href]", - function (click_event) { + (click_event) => { const switch_to = $(click_event.target).attr("id").split("--")[1]; // Ignore if in the middle of annotation @@ -371,7 +368,7 @@ export function create_ulabel_listeners( // Keybind to switch active subtask $(document).on( "keypress" + ULABEL_NAMESPACE, - function (keypress_event) { + (keypress_event) => { // Ignore if in the middle of annotation if (ulabel.get_current_subtask()["state"]["is_in_progress"]) return; @@ -415,7 +412,7 @@ export function create_ulabel_listeners( $(document).on( "input" + ULABEL_NAMESPACE, "textarea.nonspatial_note", - function (input_event) { + (input_event) => { // Update annotation's text field const annos = ulabel.get_current_subtask()["annotations"]["access"]; const text_payload_anno_id = input_event.target.id.substring("note__".length); @@ -426,7 +423,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "a.fad_button.delete", - function (click_event) { + (click_event) => { ulabel.delete_annotation(click_event.target.id.substring("delete__".length)); }, ); @@ -434,7 +431,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "a.fad_button.reclf", - function (click_event) { + (click_event) => { // Show idd ulabel.show_id_dialog( click_event.pageX, @@ -449,7 +446,7 @@ export function create_ulabel_listeners( $(document).on( "mouseenter" + ULABEL_NAMESPACE, "div.fad_annotation_rows div.fad_row", - function (mouse_event) { + (mouse_event) => { // Show thumbnail for idd ulabel.suggest_edits( null, @@ -461,7 +458,7 @@ export function create_ulabel_listeners( $(document).on( "mouseleave" + ULABEL_NAMESPACE, "div.fad_annotation_rows div.fad_row", - function () { + () => { // Show thumbnail for idd if ( ulabel.get_current_subtask()["state"]["idd_visible"] && @@ -475,7 +472,7 @@ export function create_ulabel_listeners( $(document).on( "keypress" + ULABEL_NAMESPACE, - function (keypress_event) { + (keypress_event) => { // Check the key pressed against the delete annotation keybind in the config if (keypress_event.key === ulabel.config.delete_annotation_keybind) { // Check the edit_candidate to make sure its not null and isn't nonspatial @@ -491,7 +488,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "#" + ulabel.config["container_id"] + " a.id-dialog-clickable-indicator", - function (click_event) { + (click_event) => { if (!ulabel.get_current_subtask()["state"]["idd_thumbnail"]) { ulabel.handle_id_dialog_click(click_event); } @@ -501,7 +498,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, ".global_edit_suggestion a.reid_suggestion", - function (click_event) { + (click_event) => { const crst = ulabel.get_current_subtask(); const annid = crst["state"]["idd_associated_annotation"]; ulabel.hide_global_edit_suggestion(); @@ -517,7 +514,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "#" + ulabel.config["annbox_id"] + " .delete_suggestion", - function () { + () => { const crst = ulabel.get_current_subtask(); ulabel.delete_annotation(crst["state"]["move_candidate"]["annid"]); }, @@ -527,7 +524,7 @@ export function create_ulabel_listeners( $(document).on( "click" + ULABEL_NAMESPACE, "#" + ulabel.config["toolbox_id"] + " a.night-button", - function () { + () => { const root_container = $("#" + ulabel.config["container_id"]); if (root_container.hasClass("ulabel-night")) { root_container.removeClass("ulabel-night"); @@ -542,14 +539,14 @@ export function create_ulabel_listeners( // Keyboard only events $(document).on( "keydown" + ULABEL_NAMESPACE, - function (keydown_event: JQuery.KeyDownEvent) { + (keydown_event: JQuery.KeyDownEvent) => { handle_keydown_event(keydown_event, ulabel); }, ); $(window).on( "beforeunload" + ULABEL_NAMESPACE, - function () { + () => { if (ulabel.state["edited"]) { // Return of anything other than `undefined` // will trigger the browser's confirmation dialog