diff --git a/index.html b/index.html index 2f98510..2c4e868 100644 --- a/index.html +++ b/index.html @@ -39,7 +39,7 @@ href="/src/MeasurementControl/measurementsPanel.css" /> - + diff --git a/src/AcceptedFiltering/threePanels.css b/src/Filter/filter.css similarity index 75% rename from src/AcceptedFiltering/threePanels.css rename to src/Filter/filter.css index e83b9e6..be09a06 100644 --- a/src/AcceptedFiltering/threePanels.css +++ b/src/Filter/filter.css @@ -1,10 +1,7 @@ /* ---------- Buttons (shared look) ---------- */ /* Reuse your accepted button style for all four */ #btnDoElevationControl, -#doAcceptedFiltering, -#btnTHU, -#btnTVU, -#btnTHUFilter { +#doAcceptedFiltering { display: flex; width: 100%; margin: 6px 0 10px; @@ -22,55 +19,34 @@ } #btnDoElevationControl:hover, -#doAcceptedFiltering:hover, -#btnTHU:hover, -#btnTVU:hover, -#btnTHUFilter:hover { +#doAcceptedFiltering:hover { background-color: #8f8f8f; } #btnDoElevationControl:active, -#doAcceptedFiltering:active, -#btnTHU:active, -#btnTVU:active, -#btnTHUFilter:active { +#doAcceptedFiltering:active { transform: scale(0.97); background-color: #a8a6a6; } /* Optional: “active mode” outline if you toggle a class via JS */ #btnDoElevationControl.active, -#doAcceptedFiltering.active, -#btnTHU.active, -#btnTVU.active, -#btnTHUFilter:active { +#doAcceptedFiltering.active { outline: 2px solid #7ba8ff; outline-offset: 1px; } -/* THU/TVU side-by-side */ -#thu_tvu_list .thu-tvu-row { - display: flex; - gap: 6px; -} -#thu_tvu_list .thu-tvu-row > button { - flex: 1 1 50%; - margin: 0; -} - /* ---------- Panels / moved containers ---------- */ /* Keep Potree’s moved subtrees neat and full-width inside our panels */ -#elevation2_list [id='materials.elevation_container'], -#thu_tvu_list [id='materials.extra_container'] { +#elevation_list [id='materials.elevation_container'] { width: 100%; box-sizing: border-box; padding: 6px 8px; /* small breathing room since we moved it out of Appearance */ } /* Slight spacing inside our panel lists (under the button) */ -#elevation2_list, -#accepted_list_host, -#thu_tvu_list { +#elevation_list, +#accepted_list_host { display: block; padding: 4px 0; } @@ -101,16 +77,14 @@ /* ---------- Accordions / headers (light touch) ---------- */ /* Don’t fight jQuery-UI’s theme. Just small spacing adjustments. */ -#menu_elevation2 + .pv-menu-list, -#menu_accepted + .pv-menu-list, -#menu_thu_tvu + .pv-menu-list { +#menu_elevation + .pv-menu-list, +#menu_accepted + .pv-menu-list { padding-top: 6px; } /* Optional: header label color alignment with dark UI */ -#menu_elevation2 span, -#menu_accepted span, -#menu_thu_tvu span { +#menu_elevation span, +#menu_accepted span { color: #e6e6e6; font-weight: 600; letter-spacing: 0.2px; diff --git a/src/AcceptedFiltering/threePanels.js b/src/Filter/filter.js similarity index 93% rename from src/AcceptedFiltering/threePanels.js rename to src/Filter/filter.js index 249df0f..40c5d56 100644 --- a/src/AcceptedFiltering/threePanels.js +++ b/src/Filter/filter.js @@ -1,7 +1,10 @@ -// Three Potree sidebar sections with buttons + panel bodies: -// • Elevation → moves #materials.elevation_container into our Elevation body -// • Accepted → custom UI fully defined here (no external module) - +/** Two Potree sidebar sections with buttons + panel bodies: + * + * • Elevation → moves #materials.elevation_container into our Elevation body + * Used for controlling the elevation gradient with a slider and altering between different gradient schemes + * • Accepted → custom UI fully defined here (no external module) + * Used for indicating which points/surveys are accepted as the seabed and which are not + */ const byId = (id) => document.getElementById(id) /** @@ -88,7 +91,7 @@ function ensurePanelScaffold(listId) { * @param {'elevation'|'accepted'} key */ function showOnly(key) { - const elevBody = byId('elevation2_list')?.querySelector('.panel-body') + const elevBody = byId('elevation_list')?.querySelector('.panel-body') const accBody = byId('accepted_list_host')?.querySelector('.panel-body') if (elevBody) elevBody.style.display = key === 'elevation' ? '' : 'none' @@ -150,7 +153,7 @@ function selectCloudNode(hooks) { * @param {number} pollEvery * @returns {Promise} */ -async function waitForOrPoll(id, softMs = 1400, pollEvery = 120) { +async function waitForOrPoll(id, softMs = 1400, pollEvery = 10) { const start = performance.now() while (performance.now() - start < softMs) { const el = byId(id) @@ -168,9 +171,9 @@ function createElevationPanel() { insertSection({ headerId: 'menu_elevation', headerText: 'Elevation Control', - listId: 'elevation2_list' + listId: 'elevation_list' }) - ensurePanelScaffold('elevation2_list') + ensurePanelScaffold('elevation_list') } /** @@ -178,7 +181,7 @@ function createElevationPanel() { * @param {{onActivateElevation?:Function}} hooks */ function ensureElevationButton(hooks) { - const { btns } = ensurePanelScaffold('elevation2_list') + const { btns } = ensurePanelScaffold('elevation_list') if (!btns || byId('btnDoElevationControl')) return const btn = document.createElement('button') @@ -219,7 +222,7 @@ function setUpElevationSlider(hooks) { * @returns {boolean} true if moved or already in place */ function moveElevationContainer(hooks) { - const { body } = ensurePanelScaffold('elevation2_list') + const { body } = ensurePanelScaffold('elevation_list') const src = byId('materials.elevation_container') if (!body || !src) return false @@ -228,6 +231,7 @@ function moveElevationContainer(hooks) { setUpElevationSlider(hooks) accordionRefresh() } + src.style.removeProperty('display') return true } @@ -412,7 +416,7 @@ function attachSelfHealing(activeGetter, hooks) { const mode = activeGetter() if (mode === 'elevation') { const src = byId('materials.elevation_container') - const { body } = ensurePanelScaffold('elevation2_list') + const { body } = ensurePanelScaffold('elevation_list') if (src && body && src.parentNode !== body) moveElevationContainer(hooks) } }) @@ -426,7 +430,7 @@ function attachSelfHealing(activeGetter, hooks) { * @param {object} viewer Potree viewer (not used directly here but available to hooks) * @param {{onActivateElevation?:Function, onActivateAccepted?:Function, selectCloudOnce?:Function, onElevationRangeChange?:Function}} hooks */ -export function initThreePanels(viewer, hooks = {}) { +export function initFilterPanels(viewer, hooks = {}) { // Build sections initElevationControls(hooks) initAcceptedControlsInline(hooks) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index e710bd3..ab6c461 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -1,10 +1,7 @@ import { initAnnotationsPanel } from './AnnotationControl/annotationPanel.js' import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js' import { initMiniMap } from './MiniMap/miniMap.js' -import { - initThreePanels, - toggleAcceptedLegend -} from './AcceptedFiltering/threePanels.js' +import { initFilterPanels, toggleAcceptedLegend } from './Filter/filter.js' import { ecef } from './config.js' /** @@ -90,7 +87,7 @@ export async function createPotreeViewer( }) } - initThreePanels(viewer, { + initFilterPanels(viewer, { onActivateElevation: () => { const $ = window.jQuery || window.$ const slider = $ ? $('#sldHeightRange') : null @@ -208,6 +205,9 @@ function overrideShaderForGradient(pc) { } } +// Prevent overlapping scroll freezing when activating filters quickly +let suppressionActive = false + /** * Freeze all scrollable ancestors of a given root during an action (e.g., jsTree select) * Need this so that when Elevation control or Accepted filter is activated the sidebar doesn't scroll down to the Scene panel @@ -215,6 +215,13 @@ function overrideShaderForGradient(pc) { * @param {*} action */ function suppressSidebarAutoScroll(action, holdMs = 350) { + // --- Re-entrancy guard --- + if (suppressionActive) { + action() + return + } + suppressionActive = true + // anchor on the tree root; fall back to the menu if not found const treeRoot = document.querySelector('#scene_objects') || @@ -231,8 +238,10 @@ function suppressSidebarAutoScroll(action, holdMs = 350) { if (canScroll) scrollers.push(el) el = el.parentElement } + if (!scrollers.length) { action() + suppressionActive = false return } @@ -264,20 +273,19 @@ function suppressSidebarAutoScroll(action, holdMs = 350) { const origFocus = HTMLElement.prototype.focus HTMLElement.prototype.focus = function (opts) { - // force preventScroll behavior even if caller didn't ask try { origFocus.call(this, { ...(opts || {}), preventScroll: true }) } catch { origFocus.call(this) } } + try { action() } finally { const until = performance.now() + holdMs const restoreLoop = () => { - // keep snapping until the selection animations/handlers settle states.forEach(({ el, top, left }) => { el.scrollTop = top el.scrollLeft = left @@ -300,8 +308,11 @@ function suppressSidebarAutoScroll(action, holdMs = 350) { if (h) el.removeEventListener('scroll', h) el.style.overflow = overflow }) + // --- Release the guard --- + suppressionActive = false } } + requestAnimationFrame(restoreLoop) } }