diff --git a/src/AnnotationControl/annotationPanel.js b/src/AnnotationControl/annotationPanel.js index 66ff909..280adc4 100644 --- a/src/AnnotationControl/annotationPanel.js +++ b/src/AnnotationControl/annotationPanel.js @@ -1,9 +1,21 @@ import { initAnnotationPersistence } from './persistence' /** - * Annotations Panel - * Injects a custom Annotations section for storing camera positions, - * that will (later) be used for letting users rapidly jump to saved views. + * Initialize and inject the Annotations sidebar panel, used for managing saved camera views. + * + * Creates (or re-uses) a container with id `annotations_list`, renders the + * saved annotation entries from the project's jsTree, wires UI controls + * (jump, delete, toggle, inline editing), and hooks into Potree's + * annotation events to keep the list in sync. + * + * Side effects: + * - Mutates DOM by injecting a panel and a "Add a location" button. + * - Registers event listeners on viewer.scene.annotations to refresh the list. + * - Attaches click/dblclick handlers and inline edit inputs that commit + * edits back to jsTree and the live annotation objects. + * + * @param {Object} viewer - Potree viewer instance (used to read camera/pivot, + * jump the view, and access viewer.scene.annotations). */ export function initAnnotationsPanel(viewer) { // Container management @@ -61,8 +73,19 @@ export function initAnnotationsPanel(viewer) { return } - // UI update - // Helper: normalize different vector shapes into [x,y,z] (necessary for handling both Three.js Vector3 and serialized data stored in the jsTree) + /** + * Normalize a vector-like input into an [x,y,z] array. (necessary for handling both Three.js Vector3 and serialized data stored in the jsTree) + * + * Accepts: + * - Array [x,y,z] + * - Three.js Vector3 with toArray() + * - Plain object {x,y,z} + * + * Returns null for invalid input. + * + * @param {*} v - value to normalize + * @returns {Array|null} an [x,y,z] array or null + */ function vecToArray(v) { if (!v) return null if (Array.isArray(v)) return v @@ -70,10 +93,20 @@ export function initAnnotationsPanel(viewer) { if (v.x != null && v.y != null && v.z != null) return [v.x, v.y, v.z] return null } - // Monotonic numbering: assign an increasing number to each UUID and never reuse numbers. + const _uuidToIndex = new Map() let _nextIndex = 1 + /** + * Return a monotonic index number for a UUID (Universally Unique Identifier). + * + * The function assigns an incrementing integer to each UUID the first time + * it is seen and never reuses numbers. Useful to produce human-readable + * default names (e.g. "Annotation #3") when nodes are missing titles. + * + * @param {string} uuid - UUID string for the annotation + * @returns {number|null} index assigned to the UUID, or null if uuid falsy + */ function _ensureIndexForUUID(uuid) { if (!uuid) return null if (!_uuidToIndex.has(uuid)) { @@ -107,6 +140,14 @@ export function initAnnotationsPanel(viewer) { function _getAnnotationsRoot() { const t = _getJSTree() + /** + * Find the live annotation object in the viewer.scene by UUID. + * + * Returns the live annotation or null. + * + * @param {string} uuid - annotation UUID + * @returns {Object|null} live annotation object or null if not found + */ try { return t ? t.get_json('annotations') : null } catch (e) { @@ -189,6 +230,23 @@ export function initAnnotationsPanel(viewer) { return null } function updateAnnotationsList() { + /** + * Rebuild the annotation list UI from the project's jsTree data. + * + * This function: + * - Clears and re-populates the `targetContainer` with a row per annotation. + * - Ensures default labels for unnamed annotations (using monotonic numbering). + * - Renders header, toggle triangle, edit label, jump/delete controls, description, + * and read-only camera/point info. + * - Wires events for inline editing, jump, delete, and header click-to-toggle. + * + * Side effects: + * - Mutates DOM inside `targetContainer`. + * - May call `_renameJSTreeNode` to rename nodes when needed. + * - Uses `_findLiveAnnotationByUUID` to prefer live object positions over serialized values. + * - Event listeners stop propagation where necessary so double-click and button + * behaviour are preserved. + */ // Implementation for listing annotations targetContainer.innerHTML = '' const annotationsTree = _getJSTree() @@ -280,7 +338,6 @@ export function initAnnotationsPanel(viewer) { toggle.title = 'Toggle details' // Jump button - const jumpBtn = document.createElement('button') jumpBtn.className = 'jump-btn' jumpBtn.title = 'Move to this position' @@ -310,7 +367,6 @@ export function initAnnotationsPanel(viewer) { try { jumpBtn.setAttribute('aria-pressed', 'true') jumpBtn.classList.add('recently-pressed') - // remove after short delay to allow CSS fade-out window.setTimeout(() => { try { jumpBtn.classList.remove('recently-pressed') @@ -332,7 +388,7 @@ export function initAnnotationsPanel(viewer) { const uuid = annData.uuid if (uuid && viewer && viewer.scene && viewer.scene.annotations) { - // Find the live annotation instance by UUID (Universally Unique Identifier) + // Find the live annotation instance by UUID let candidates = [] try { candidates = @@ -527,8 +583,21 @@ export function initAnnotationsPanel(viewer) { }) } - // Start inline editing for a given annotation UUID (opens an input in the sidebar) function startInlineEditForUUID(uuid) { + /** + * Start inline editing of an annotation title (opens an input in the sidebar). + * + * Replaces the `.annotation-label` with a text input and handles commit/abort: + * - On commit, updates the label in the sidebar, updates the jsTree node text, + * and updates the live annotation object's title (via `_commitEditedName`). + * - On abort, restores the original label without committing. + * + * Special handling: + * - If the label contains a decorative edit-hint span, the hint text is not + * included in the input value and is re-attached to the restored label. + * + * @param {string} uuid - annotation UUID to edit + */ if (!uuid) return const labelEl = targetContainer.querySelector( `.annotation-label[data-uuid="${uuid}"]` @@ -614,8 +683,16 @@ export function initAnnotationsPanel(viewer) { }) } - // Start inline editing for an annotation description (multiline) function startInlineDescriptionEditForUUID(uuid) { + /** + * Start inline editing of an annotation description (multiline). + * + * Replaces the `.annotation-desc` container with a textarea. Behavior mirrors + * `startInlineEditForUUID` but handles multiline input. Commit updates both + * the jsTree node data and the live annotation object's description. + * + * @param {string} uuid - annotation UUID to edit + */ if (!uuid) return const descEl = targetContainer.querySelector( `.annotation-desc[data-uuid="${uuid}"]` @@ -695,6 +772,14 @@ export function initAnnotationsPanel(viewer) { } function _commitEditedDescription(uuid, description) { + /** + * Commit a changed annotation description to jsTree and to the live annotation. + * + * Also updates any cached node data used by the sidebar. + * + * @param {string} uuid - annotation UUID + * @param {string} description - new description text + */ if (!uuid) return try { const tree = _getJSTree() @@ -737,6 +822,15 @@ export function initAnnotationsPanel(viewer) { } function _commitEditedName(uuid, name) { + /** + * Commit a changed annotation name to jsTree and to the live annotation object. + * + * This updates the jsTree node text (if accessible) and mutates the live + * annotation instance's title/name/data so the viewer label matches. + * + * @param {string} uuid - annotation UUID + * @param {string} name - new name to commit + */ if (!uuid) return // update jsTree node text try { @@ -769,8 +863,18 @@ export function initAnnotationsPanel(viewer) { setTimeout(updateAnnotationsList, 0) } - // Add Annotation UI (mimics Potree toolbar/pinpoint-button logic) function createAddButton() { + /** + * Create and insert the "Add a location" button for annotations. + * + * The button: + * - Captures the current camera view when clicked. + * - Starts Potree's annotation insertion mode. + * - Waits for placement completion and triggers `updateAnnotationsList`. + * + * Side effects: + * - Inserts a button into the DOM near the annotations list. + */ const btn = document.createElement('button') btn.className = 'annotation-add-button' btn.setAttribute('aria-label', 'Add a new saved location')