Skip to content

4 save camera positions us5 #38

Merged
merged 37 commits into from
Oct 24, 2025
Merged

4 save camera positions us5 #38

merged 37 commits into from
Oct 24, 2025

Conversation

gautegf
Copy link
Member

@gautegf gautegf commented Oct 23, 2025

Issue number

Closes #4

Description

  • Move annotations from scene to its own section/folder in the sidebar, and the tool for making annotations from tools to this new section
  • Update the UI of displaying, creating, deleting and editing annotations. Renamed annotations to "saved locations" in the UI
  • Added a button for travelling between saved locations
  • Set up a small express server with one API endpoint to save annotations to a json file
  • Update README, explaining hwo to start the server

Testing steps

When testing this feature, remember to run "npm install", start the server in a separate terminal using "npm run server" and you also have to restart the dev server with "npm run dev". Then go to the "Saved locations" section in the sidebar and try saving, editing and deleting annotations

Screenshots (optional)

Summary by CodeRabbit

  • New Features

    • Sidebar for saving/managing camera-location annotations with inline edit, descriptions, jump-to-view, add and delete actions.
    • Client-side persistence with optional sync to a local API, plus a runnable local server and npm script to start it.
    • Added initial annotations storage file seeded for use.
  • Style

    • New annotation panel styles: layout, states, transitions, editable description, and accessibility touches.
  • Chores

    • Dev tooling: local dev-server proxy configured to forward /api to the local backend.
  • Documentation

    • README updated with project structure and server run instructions.

franmagn and others added 30 commits October 10, 2025 21:00
This new menu section includes a button to add new annotations and lists already-existing annotations
…isualization

Now is possible to jump between different saved positions (annotations) and to actually delete one. Also added the coordinates of the camera and of the pivot point (to be fixed)
add annotationPanel.css file and just inserted the css code to make the original annotation button not visible in the toolbar
…ions

add an increasing number to each new annotation created. As for now, only in the sidebar (jstree)
…tations

add an increasing number to each new annotation created. Now works also in the live scene (for each UUID)
…rom the sidebar

it is now possible to double-click on a saved position's name to rename it. The update is cascaded to the live-scene elements
This fix solves the NotFoundError, throwed whenever there was an attempt of removing an already removed target
fixed behaviour conflict triggered by "Add a new location" button
…r label placement to update

Before of this fix, whenever there was the creation of a new saved position, it had Placeholder values from potree's build, that were updated only after the creation/deletion of another position. Now plaeholder value is substituted by "---" and there is a eventListener that waits for the label to be placed before updating its values
…all refactor

Now it is possible to see the Annotation Description in the sidebar, and if double-clicked, it can be edited. The Annotation is mirrored in the liev scene object. Lastly, there is a small refactor of the code to have some helpers
The data is sent by API to the small express server and stored in a json. the objects in the json will be fetched when opening the app and displayed
- made it possible to click everywhere on the single annotation panel to open it, and not only on the toggle triangle - added a small pencil icon to indicate the possibility to edit the title text - switched “camera coordinates” with “saved point coordinates” in the annotation panel - made it that the coordinates are listed below the “type of coordinate” title, and are on different lines
…up-4/MolloyExplorer into 4-save-camera-positions-us5
@gautegf gautegf linked an issue Oct 23, 2025 that may be closed by this pull request
Copy link
Member

@kleinc kleinc left a comment

Choose a reason for hiding this comment

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

It looks really good, and the movement between the annotations are really smooth, good job!

Just two things I noticed:

  • When I try to move to either annotation 3 and 4, it zooms out quite far (as shown on the picture below). If I do the same for annotation 1 and 2, the camera moves closer to them, as intended. Not sure if the camera behaviour for 3 and 4 is intended, or if it might be related to the fact that the other areas are smaller?
Skjermbilde 2025-10-23 kl  11 52 59
  • Also noticed that the textbox can be dragged outside of the gray area. I don't think it is a big issue as of now, but if you have time it could maybe be fixed.

@franmagn
Copy link
Member

franmagn commented Oct 23, 2025

It looks really good, and the movement between the annotations are really smooth, good job!

Just two things I noticed:

  • When I try to move to either annotation 3 and 4, it zooms out quite far (as shown on the picture below). If I do the same for annotation 1 and 2, the camera moves closer to them, as intended. Not sure if the camera behaviour for 3 and 4 is intended, or if it might be related to the fact that the other areas are smaller?
Skjermbilde 2025-10-23 kl 11 52 59 - Also noticed that the textbox can be dragged outside of the gray area. I don't think it is a big issue as of now, but if you have time it could maybe be fixed.

The zooming out (or in) depends entirely on where the camera was when you clicked the "Add a Location" button, so maybe you where further away when you saved those 2 points. Maybe we should explain this more in detail in the documentation for the usage of the application.

About the text box, nice catch! I will fix it asap!

@mariewah mariewah requested a review from kleinc October 23, 2025 11:30
@coderabbit
Copy link

coderabbit bot commented Oct 23, 2025

Walkthrough

Adds a Potree annotations sidebar (UI + CSS), client-side persistence with autosave and optional server save, a small Express server endpoint and npm script, a Vite dev proxy, and an initial annotations JSON plus README updates.

Changes

Cohort / File(s) Summary
Backend & Dev config
server.js, vite.config.js, package.json
Adds an Express server (server.js) with CORS and JSON body parsing and POST /api/annotations; adds server npm script and express/cors dependencies; adds Vite dev server config (vite.config.js) with proxy /apihttp://localhost:5174.
Annotation UI & wiring
Panel & wiring: src/AnnotationControl/annotationPanel.js, src/potreeViewer.js
Styles / HTML: src/AnnotationControl/annotationPanel.css, index.html
Adds initAnnotationsPanel(viewer) and integrates it into the viewer init; builds the saved-locations sidebar (list, add, jump, delete, inline title/description editing) and hooks Potree annotation events; introduces panel stylesheet and includes its link in index.html.
Persistence & assets
src/AnnotationControl/persistence.js, public/annotations/annotations.json
Adds initAnnotationPersistence(viewer, options) implementing load-from-JSON/localStorage, debounced autosave, optional POST save to server, deep mutation watching/patching, serialization grouped by folders, and a small public API; adds initial public/annotations/annotations.json.
Docs
README.md
Adds server run instructions and a "Structure" section describing the Potree-based build and update procedure.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as "Annotations Panel"
    participant Viewer as "Potree Viewer"
    participant Persist as "Annotation Persistence"
    participant Server

    User->>UI: Click "Add Location"
    UI->>Viewer: Capture camera & marker position
    Viewer-->>UI: Live annotation created
    UI->>Persist: Register new annotation
    Persist->>localStorage: Debounced snapshot
    Persist->>Server: POST /api/annotations (debounced)
    Server-->>Persist: { saved: true, path }
    Persist->>UI: Refresh list / emit update
Loading
sequenceDiagram
    participant UI as "Annotations Panel"
    participant Viewer as "Potree Viewer"
    participant Persist as "Annotation Persistence"

    UI->>UI: Inline edit title/description
    UI->>Viewer: Update live annotation object
    Viewer-->>Persist: annotation_changed event
    Persist->>Persist: Mark dirty & debounce snapshot
    Persist->>localStorage: Save snapshot
    Persist->>UI: Confirm persisted state / refresh
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop and tuck each camera's gleam,

I store the views inside a dream.
A sidebar burrow, names that sing,
JSON crumbs for every spring.
Hooray — saved hops on soft moonbeam!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.03% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "4 save camera positions us5" relates to the main change in the changeset. It references the issue number and the core objective—saving camera positions through a new "Saved Locations" feature. While the phrasing is somewhat terse and branch-name-like rather than a polished summary, it does communicate the essential concept. A developer scanning the history would recognize this relates to camera position management, even if the title could be more descriptive or refined.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly aligned with the objectives of moving annotations to a sidebar section and implementing persistence. The modifications span README documentation, HTML stylesheet integration, package dependencies (express, cors), the new Express server, client-side UI and persistence modules, Vite configuration for dev server proxying, and the data storage file. Each file serves a clear purpose within the scope of the "Saved Locations" feature implementation, and no unrelated or tangential changes are present.
Description Check ✅ Passed The pull request description follows the repository template structure with all required sections completed: Issue number (Closes #4), a detailed Description covering six key changes (moved annotations to sidebar, renamed UI, added travel button, set up Express server, updated README), and clear Testing steps with specific commands. The description is substantive and provides reviewers with sufficient context about what changed and how to verify the implementation, even though the optional Screenshots section was left blank.

[pre_merge_check_pass]


The pull request implements most core coding objectives from issue #4. The annotation tool, list, and properties have been moved to a dedicated "Saved Locations" sidebar section (annotationPanel.js, annotationPanel.css), delete functionality is implemented (visible in CSS and code structure), and the travel button between locations is added. The Express server with API endpoint stores annotation data with folder support, and the persistence module serializes all required fields: Name, Description, Camera Position, Camera Target, and Position. However, the PR description does not explicitly clarify whether folder creation and folder-selection UI during annotation creation are fully implemented, though the code infrastructure (folders property, serializeGrouped logic) suggests these features may be partially present. |

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 4-save-camera-positions-us5

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
src/AnnotationControl/annotationPanel.js (1)

1-1: Docstring consistency.

Docs style varies across helpers (e.g., _getJSTree, _getJSTreeInstance, _findLiveAnnotationByUUID). Align placement/format with the rest.

🧹 Nitpick comments (10)
server.js (3)

36-38: File writes are not atomic and lack backups.

Direct writeFileSync can corrupt the file if the process crashes mid-write. Consider atomic writes (write to temp file, then rename) to ensure data integrity.

Example atomic write pattern:

const tempFile = annotationsFile + '.tmp'
await fs.promises.writeFile(tempFile, data, 'utf8')
await fs.promises.rename(tempFile, annotationsFile)

26-44: No authentication or rate limiting on the save endpoint.

Any client can repeatedly overwrite annotation data. Consider adding rate limiting and authentication mechanisms for production deployments.


29-34: Basic validation may allow malformed data.

The validation only checks for the presence of folders as an object but doesn't validate its structure or content depth. Consider adding schema validation (e.g., with a library like Joi or Zod) to ensure data integrity.

src/AnnotationControl/annotationPanel.js (3)

69-75: Prevent double initialization (buttons/listeners duplication).

Calling initAnnotationsPanel multiple times will add duplicate listeners and “Add a location” buttons.

   if (!targetContainer) {
     console.warn(
       'Annotations list container not found and dynamic injection failed'
     )
     return
   }
+  // Prevent double-initialization
+  if (targetContainer.dataset.annPanelInitialized === '1') {
+    updateAnnotationsList()
+    return
+  }
...
   createAddButton()
   updateAnnotationsList()
   initAnnotationPersistence(viewer)
+  // Mark initialized
+  try { targetContainer.dataset.annPanelInitialized = '1' } catch {}

Also applies to: 991-1009


570-572: Avoid magic placeholder coordinates.

Hard-coding PLACEHOLDER_POS couples UI to dataset-specific values. Prefer a semantic check (e.g., wait until ann.marker exists and differs from default) or hide until a live marker position is available.


919-990: US:5 verification: folder selection on create.

Create button doesn’t prompt/select a folder; new annotations default to ‘General’. If folder selection is required at creation time, point me to where it’s implemented or we should add it.

src/AnnotationControl/persistence.js (4)

2-3: Make endpoints and autosave configurable.

Allow callers to pass saveUrl/jsonUrl/autosave. Keeps dev/prod flexible.

-export function initAnnotationPersistence(viewer, options = {}) {
-  const jsonUrl = options.jsonUrl || '/annotations/annotations.json'
+export function initAnnotationPersistence(viewer, options = {}) {
+  const jsonUrl = options.jsonUrl || '/annotations/annotations.json'
...
-  const saveUrl = defaultSaveUrl
-  const autosave = true
+  const saveUrl = options.saveUrl || defaultSaveUrl
+  const autosave = options.autosave ?? true

Also applies to: 16-20


84-84: Reduce console noise (guard with debug).

Wrap this log with a debug flag or remove before release.


53-62: Note: grouping only by top-level children.

serializeGrouped iterates root children only. If nested annotations matter, consider a recursive walk or confirm folders are always one level.


64-81: Offline fallback: consider versioning.

When schema changes (e.g., new cameraTarget), consider bumping version or handling both formats during load.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f46613 and 30a89bc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • README.md (2 hunks)
  • index.html (1 hunks)
  • package.json (1 hunks)
  • public/annotations/annotations.json (1 hunks)
  • server.js (1 hunks)
  • src/AnnotationControl/annotationPanel.css (1 hunks)
  • src/AnnotationControl/annotationPanel.js (1 hunks)
  • src/AnnotationControl/persistence.js (1 hunks)
  • src/potreeViewer.js (2 hunks)
  • vite.config.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/AnnotationControl/persistence.js (1)
server.js (1)
  • data (37-37)
src/AnnotationControl/annotationPanel.js (1)
src/AnnotationControl/persistence.js (2)
  • root (151-151)
  • initAnnotationPersistence (1-276)
src/potreeViewer.js (1)
src/AnnotationControl/annotationPanel.js (1)
  • initAnnotationsPanel (20-1010)
🪛 GitHub Actions: Prettier
src/AnnotationControl/annotationPanel.js

[warning] 1-1: Code style issues found in the above file. Run Prettier with --write to fix.

🔇 Additional comments (11)
vite.config.js (1)

1-14: LGTM! Development proxy configuration is correct.

The proxy setup correctly routes /api requests to the backend server. The secure: false setting is acceptable for local development but should not be used in production (this config is dev-only).

public/annotations/annotations.json (1)

1-4: LGTM! Clean initial state for annotations storage.

The structure with a version field supports future schema evolution, and the empty folders object is an appropriate starting point.

README.md (2)

25-29: LGTM! Clear server setup instructions.

The server instructions are well-placed and easy to follow.


41-49: LGTM! Helpful structural documentation.

The Structure section provides valuable context about the Potree integration and version management.

index.html (1)

41-41: LGTM! Stylesheet link properly added.

The annotation panel CSS is correctly linked and positioned with other stylesheets.

src/potreeViewer.js (2)

1-1: LGTM! Clean import following existing patterns.

The import is consistent with other control panel imports.


41-41: LGTM! Proper initialization placement.

The annotation panel is initialized alongside other panels in the correct location within the GUI setup callback.

src/AnnotationControl/annotationPanel.css (2)

1-10: Heavy use of !important to override third-party styles.

While necessary to hide Potree's annotation icon, the multiple !important declarations make this rule brittle. Ensure this selector remains stable across Potree versions.

Verify this selector continues to work after Potree updates by checking the button-icon class and annotation.svg path remain unchanged in the Potree build.


12-365: LGTM! Comprehensive and accessible UI styling.

The stylesheet provides:

  • Clean visual hierarchy and spacing
  • Smooth transitions and interactions
  • Proper focus states for accessibility
  • Responsive hover and active states
package.json (1)

14-17: Dependencies are secure and free from known vulnerabilities.

Verification confirms that express ^4.21.1 and cors ^2.8.5 are not affected by any known vulnerabilities in the GitHub advisory database. Both versions exceed the minimum patched versions for all reported issues. No action required.

src/AnnotationControl/annotationPanel.js (1)

995-1009: Prettier formatting issues resolved.

Prettier successfully ran and fixed formatting issues in the file. The changes are ready to commit.

Copy link
Member

@mariewah mariewah left a comment

Choose a reason for hiding this comment

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

Agree with the comments Klein left :D

Copy link
Member

@kleinc kleinc left a comment

Choose a reason for hiding this comment

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

Nice!

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/AnnotationControl/annotationPanel.js (1)

563-570: Consider making the placeholder position configurable.

The hardcoded PLACEHOLDER_POS = [589748.27, 231444.54, 753.675] is specific to your current dataset or Potree configuration. If you use a different point cloud or Potree version, this constant may not match the actual placeholder, causing valid coordinates to be hidden or placeholder coordinates to be displayed.

Consider one of these approaches:

  1. Pass as an option to initAnnotationsPanel(viewer, { placeholderPos: [...] }).
  2. Detect dynamically by checking if a newly created annotation has these exact coordinates before placement.
  3. Use a configurable epsilon and document the expected placeholder for your project.

Example:

+  const PLACEHOLDER_POS = (viewer.config && viewer.config.annotationPlaceholder)
+    || [589748.27, 231444.54, 753.675]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa8725e and 6f45475.

📒 Files selected for processing (1)
  • src/AnnotationControl/annotationPanel.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/AnnotationControl/annotationPanel.js (1)
src/AnnotationControl/persistence.js (2)
  • root (151-151)
  • initAnnotationPersistence (1-276)

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/AnnotationControl/annotationPanel.js (3)

347-371: Enable Jump when either point or camera is available.

setView already guards on camPos. Don’t disable Jump when point is missing but camera exists.

-          // Enable or disable jump button depending on whether we have a saved point position
+          // Enable Jump when either a saved point OR a camera position exists
           try {
-            if (pointPos && Array.isArray(pointPos) && pointPos.length === 3) {
+            const canJumpByPoint =
+              pointPos && Array.isArray(pointPos) && pointPos.length === 3
+            const canJumpByCam =
+              cam && Array.isArray(cam) && cam.length === 3
+            if (canJumpByPoint || canJumpByCam) {
               jumpBtn.disabled = false
               jumpBtn.classList.remove('jump-disabled')
             } else {
               jumpBtn.disabled = true
               jumpBtn.classList.add('jump-disabled')
             }
           } catch (e) {}

Also applies to: 593-601


703-711: Prevent double-commit from blur + keydown in inline editors.

Both editors can fire twice (blur and keydown). Add a small “finished” guard.

-    function finishInlineEditForUUID(commit) {
+    let finished = false
+    function finishInlineEditForUUID(commit) {
+      if (finished) return
+      finished = true
       const newText = commit ? input.value.trim() || oldText : oldText
       // restore label
-    function finishInlineDescriptionEdit(commit) {
+    let finished = false
+    function finishInlineDescriptionEdit(commit) {
+      if (finished) return
+      finished = true
       const newText = commit ? ta.value.trim() || '' : oldText

Also applies to: 804-817


342-351: Make toggle control accessible (button + aria-expanded).

Use a real button for the triangle toggle and update aria-expanded on state changes.

-      const toggle = document.createElement('span')
-      toggle.className = 'toggle-triangle'
-      toggle.title = 'Toggle details'
+      const toggle = document.createElement('button')
+      toggle.type = 'button'
+      toggle.className = 'toggle-triangle'
+      toggle.title = 'Toggle details'
+      toggle.setAttribute('aria-expanded', 'false')
-            // Otherwise toggle the row
-            row.classList.toggle('open')
+            // Otherwise toggle the row
+            row.classList.toggle('open')
+            try {
+              toggle.setAttribute(
+                'aria-expanded',
+                row.classList.contains('open') ? 'true' : 'false'
+              )
+            } catch (e) {}
         toggle.addEventListener('click', (ev) => {
           ev.stopPropagation()
-          row.classList.toggle('open')
+          row.classList.toggle('open')
+          try {
+            toggle.setAttribute(
+              'aria-expanded',
+              row.classList.contains('open') ? 'true' : 'false'
+            )
+          } catch (e) {}
         })

Also applies to: 496-501, 504-510

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f45475 and 4795405.

📒 Files selected for processing (1)
  • src/AnnotationControl/annotationPanel.js (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T17:13:41.544Z
Learnt from: franmagn
PR: TDT4290-group-4/MolloyExplorer#38
File: src/AnnotationControl/annotationPanel.js:346-369
Timestamp: 2025-10-23T17:13:41.544Z
Learning: In the MolloyExplorer project's saved locations/annotations feature (src/AnnotationControl/annotationPanel.js and related files), the team has decided to ignore `cameraTarget` and only use `cameraPosition` and `pointPosition` (also referred to as annotation position) when jumping between saved locations.

Applied to files:

  • src/AnnotationControl/annotationPanel.js
🪛 GitHub Actions: Prettier
src/AnnotationControl/annotationPanel.js

[warning] 1-1: Code style issues found in src/AnnotationControl/annotationPanel.js. Run 'npx prettier --write' to fix.

🔇 Additional comments (3)
src/AnnotationControl/annotationPanel.js (3)

1000-1003: US:5 acceptance: verify export schema includes Camera Target, or document the deviation.

Issue #4 requires “Camera Target” in export. The team decision is to ignore cameraTarget. Please confirm the acceptance criteria were updated or ensure persistence includes a placeholder/derived target. Based on learnings.

Would you like me to scan the persistence layer and server endpoint to validate the JSON shape?


1-1: Prettier warning in CI.

Run: npx prettier --write src/AnnotationControl/annotationPanel.js


879-897: Bug: early return aborts live updates when jQuery is missing.

return inside the try exits _commitEditedName entirely, skipping live annotation updates and UI refresh.

   function _commitEditedName(uuid, name) {
     if (!uuid) return
     // update jsTree node text
     try {
-      if (!window.jQuery) return
-      const tree = window.jQuery('#jstree_scene').jstree && window.jQuery('#jstree_scene').jstree()
-      if (tree) {
+      if (window.jQuery) {
+        const tree =
+          window.jQuery('#jstree_scene').jstree &&
+          window.jQuery('#jstree_scene').jstree()
+        if (tree) {
           const annotationsRoot = tree.get_json('annotations')
           if (annotationsRoot && annotationsRoot.children) {
             const node = annotationsRoot.children.find(
               (c) => (c.data && c.data.uuid) === uuid
             )
             if (node) {
               _renameJSTreeNode(node.id, name)
               node.text = name
             }
           }
-        }
+        }
+      }
     } catch (e) {}

Likely an incorrect or invalid review comment.

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
src/AnnotationControl/annotationPanel.js (5)

57-66: Fix unguarded $ reference before jQuery check.

Line 59 uses $ directly before checking if window.jQuery exists on line 60, which will throw a ReferenceError if jQuery isn't loaded.

Apply this diff to guard the first check:

       header.addEventListener('click', () => {
-        if ($(menu).accordion && $(menu).data('uiAccordion')) return
-        if (window.jQuery) {
+        if (
+          window.jQuery &&
+          window.jQuery(menu).accordion &&
+          window.jQuery(menu).data('uiAccordion')
+        ) {
+          return
+        }
+        if (window.jQuery) {
           const $p = window.jQuery(panel)
           $p.is(':visible') ? $p.slideUp(350) : $p.slideDown(350)
           return
         }
+        // Fallback: toggle via style when jQuery unavailable
+        panel.style.display = panel.style.display === 'none' ? '' : 'none'
       })

930-1024: Prevent duplicate buttons and event listeners on re-initialization.

If initAnnotationsPanel is called multiple times, it will create duplicate "Add" buttons and register multiple event listeners, leading to memory leaks and unexpected behavior.

Apply these diffs:

1. Guard button creation (in createAddButton):

   function createAddButton() {
+    // Prevent duplicate buttons
+    const host = targetContainer.parentElement
+    if (host && host.querySelector('.annotation-add-button')) return
+    
     const btn = document.createElement('button')

2. Guard event listener registration:

   // Listen to Potree's annotation events to auto-refresh the list
-  if (viewer.scene && viewer.scene.annotations) {
+  if (
+    viewer.scene &&
+    viewer.scene.annotations &&
+    !viewer.__annoPanelListeners
+  ) {
+    viewer.__annoPanelListeners = true
     viewer.scene.annotations.addEventListener(
       'annotation_added',
       updateAnnotationsList
     )
     viewer.scene.annotations.addEventListener(
       'annotation_removed',
       updateAnnotationsList
     )
     viewer.scene.annotations.addEventListener(
       'annotation_changed',
       updateAnnotationsList
     )
   }

185-206: Unify on window.jQuery instead of mixing window.$ and $.

The function checks window.$ (line 187) but then uses $.jstree and $('#jstree_scene') which may not be available. Standardize on window.jQuery.

Apply this diff:

   function _renameJSTreeNode(nodeId, text) {
     try {
-      if (window.$ && $.jstree) {
-        if ($.jstree.reference) {
-          const ref = $.jstree.reference(nodeId)
+      if (window.jQuery && (window.jQuery.fn.jstree || window.jQuery.jstree)) {
+        if (window.jQuery.jstree && window.jQuery.jstree.reference) {
+          const ref = window.jQuery.jstree.reference(nodeId)
           if (ref && typeof ref.rename_node === 'function') {
             ref.rename_node(nodeId, text)
             return
           }
         }
         // fallback to global selector
-        if ($('#jstree_scene') && $('#jstree_scene').jstree) {
+        const $el = window.jQuery('#jstree_scene')
+        if ($el && $el.jstree) {
           try {
-            $('#jstree_scene').jstree('rename_node', nodeId, text)
+            $el.jstree('rename_node', nodeId, text)
             return
           } catch (e) {}
         }
       }
     } catch (e) {
       // ignore rename failures
     }
   }

990-1002: Guard $ usage in delayed jsTree access.

Line 992 uses $ without checking if jQuery is available, which will throw a ReferenceError if jQuery isn't loaded.

Apply this diff:

       setTimeout(() => {
-        let tree = $('#jstree_scene').jstree && $('#jstree_scene').jstree()
-        if (!tree || !annotation) return
+        if (!annotation) return
+        if (!window.jQuery) {
+          updateAnnotationsList()
+          return
+        }
+        const $el = window.jQuery('#jstree_scene')
+        const tree = $el && $el.jstree ? $el.jstree() : null
+        if (!tree) {
+          updateAnnotationsList()
+          return
+        }
         // Persist captured camera info on the annotation object so it will be available in the jsTree node data
         try {
           if (camPos) annotation.cameraPosition = camPos
         } catch (e) {
           console.warn('Could not attach camera info to annotation', e)
         }
         updateAnnotationsList()
       }, 200) // A short delay for Potree/jsTree to update

118-141: Replace $ with window.jQuery for consistency after guard checks.

After verifying window.jQuery exists, the code still uses the $ alias which may be undefined in noConflict mode or other edge cases. Use window.jQuery consistently.

Apply this diff:

   function _getJSTree() {
     try {
       if (!window.jQuery) return null
-      return $('#jstree_scene').jstree && $('#jstree_scene').jstree()
+      const $el = window.jQuery('#jstree_scene')
+      return $el && $el.jstree ? $el.jstree() : null
     } catch (e) {
       return null
     }
   }

   function _getJSTreeInstance() {
     try {
       if (!window.jQuery || !window.jQuery.jstree) return null
-      return (
-        ($('#jstree_scene').jstree && $('#jstree_scene').jstree(true)) ||
-        (typeof $.jstree !== 'undefined' &&
-          $.jstree.reference &&
-          $.jstree.reference('#jstree_scene')) ||
-        null
-      )
+      const $el = window.jQuery('#jstree_scene')
+      return (
+        ($el && $el.jstree && $el.jstree(true)) ||
+        (window.jQuery.jstree.reference &&
+          window.jQuery.jstree.reference('#jstree_scene')) ||
+        null
+      )
     } catch (e) {
       return null
     }
   }
🧹 Nitpick comments (2)
src/AnnotationControl/annotationPanel.js (2)

573-580: Document the placeholder position constant.

The hardcoded PLACEHOLDER_POS appears to be Potree's default pre-placement coordinate. Consider extracting it to a named constant at the module level with a comment explaining its source and the Potree version it's valid for. This aids maintainability if Potree is upgraded.

Example:

+// Potree's default placeholder coordinates before an annotation is actually placed
+// (specific to the current Potree version in use)
+const POTREE_PLACEHOLDER_POSITION = [589748.27, 231444.54, 753.675]
+
 export function initAnnotationsPanel(viewer) {
   // ... existing code ...
   
         function approxEqual(a, b, eps = 1e-3) {
           if (!a || !b || a.length !== b.length) return false
           for (let i = 0; i < a.length; i++)
             if (Math.abs(Number(a[i]) - Number(b[i])) > eps) return false
           return true
         }
-        const PLACEHOLDER_POS = [589748.27, 231444.54, 753.675]
-        if (pointPos && approxEqual(pointPos, PLACEHOLDER_POS)) pointPos = null
+        if (pointPos && approxEqual(pointPos, POTREE_PLACEHOLDER_POSITION)) pointPos = null

20-1025: Consider extracting helper utilities for better modularity.

The 1025-line file with deeply nested helpers works correctly but could benefit from extracting stable utility functions (e.g., vecToArray, jQuery helpers, UUID indexing) to a separate module or to module-level scope. This would improve testability and readability, particularly for the 363-line updateAnnotationsList function.

This is a nice-to-have improvement for future refactoring, not a blocker.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4795405 and b084e93.

📒 Files selected for processing (1)
  • src/AnnotationControl/annotationPanel.js (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T17:13:41.544Z
Learnt from: franmagn
PR: TDT4290-group-4/MolloyExplorer#38
File: src/AnnotationControl/annotationPanel.js:346-369
Timestamp: 2025-10-23T17:13:41.544Z
Learning: In the MolloyExplorer project's saved locations/annotations feature (src/AnnotationControl/annotationPanel.js and related files), the team has decided to ignore `cameraTarget` and only use `cameraPosition` and `pointPosition` (also referred to as annotation position) when jumping between saved locations.

Applied to files:

  • src/AnnotationControl/annotationPanel.js
🧬 Code graph analysis (1)
src/AnnotationControl/annotationPanel.js (1)
src/AnnotationControl/persistence.js (2)
  • root (151-151)
  • initAnnotationPersistence (1-276)

Copy link
Member

@kleinc kleinc left a comment

Choose a reason for hiding this comment

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

LGTM!

@mariewah mariewah self-requested a review October 24, 2025 06:06
Copy link
Member

@mariewah mariewah left a comment

Choose a reason for hiding this comment

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

LGTM!

@mariewah mariewah merged commit e537d11 into dev Oct 24, 2025
2 checks passed
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Save camera positions US:5
4 participants