diff --git a/dashboard/js/app.js b/dashboard/js/app.js index bb81f17..69beed8 100644 --- a/dashboard/js/app.js +++ b/dashboard/js/app.js @@ -2280,17 +2280,19 @@ }; window.toggleFresnelZones = function() { - var btn = document.getElementById('fresnel-toggle-btn'); - var isActive = btn && btn.classList.contains('active'); - - if (isActive) { - // Turn off - Viz3D.toggleFresnelZones(false); - if (btn) btn.classList.remove('active'); + if (window.Layers) { + Layers.toggleLayer(Layers.LAYERS.FRESNEL); } else { - // Turn on - Viz3D.toggleFresnelZones(true); - if (btn) btn.classList.add('active'); + // Fallback without Layers module + var btn = document.getElementById('fresnel-toggle-btn'); + var isActive = btn && btn.classList.contains('active'); + if (isActive) { + Viz3D.toggleFresnelZones(false); + if (btn) btn.classList.remove('active'); + } else { + Viz3D.toggleFresnelZones(true); + if (btn) btn.classList.add('active'); + } } }; @@ -2305,6 +2307,18 @@ window.toggleFresnelDebugOverlay = function(visible) { state.fresnelDebugVisible = visible; + // Sync Viz3D Fresnel zones with debug overlay state + if (window.Viz3D && Viz3D.toggleFresnelZones) { + Viz3D.toggleFresnelZones(visible); + } + + // Sync toolbar button + var btn = document.getElementById('fresnel-toggle-btn'); + if (btn) { + if (visible) btn.classList.add('active'); + else btn.classList.remove('active'); + } + if (visible) { rebuildFresnelDebugEllipsoids(); } else { @@ -2590,6 +2604,17 @@ if (debugSection) { debugSection.style.display = 'block'; } + + // Bind layer controls to Layers module + if (window.Layers) { + var fresnelCheckbox = document.getElementById('fresnel-zones-toggle'); + if (fresnelCheckbox) { + Layers.bindCheckbox(Layers.LAYERS.FRESNEL, fresnelCheckbox); + } + Layers.onLayerChange(Layers.LAYERS.FRESNEL, function(visible) { + toggleFresnelDebugOverlay(visible); + }); + } }; // Update Fresnel ellipsoids when links change diff --git a/dashboard/js/layers.js b/dashboard/js/layers.js new file mode 100644 index 0000000..bac1f0a --- /dev/null +++ b/dashboard/js/layers.js @@ -0,0 +1,166 @@ +/** + * Spaxel Dashboard - Layer Management Module + * + * Centralized management for 3D scene overlay layers. + * Provides toggle state tracking and event dispatch for layer changes. + * + * Layers: + * - Links: TX→RX link lines + * - Fresnel Zones: Wireframe ellipsoids for active links (debug) + * - Trails: Blob footprint trails + * - Zones: Zone box meshes and labels + * - Portals: Portal connection meshes + * - Coverage: GDOP quality overlay + * - Crowd Flow: Trajectory flow arrows + */ + +(function() { + 'use strict'; + + var LAYERS = { + LINKS: 'links', + FRESNEL: 'fresnel', + TRAILS: 'trails', + ZONES: 'zones', + PORTALS: 'portals', + COVERAGE: 'coverage', + CROWD_FLOW: 'crowd_flow' + }; + + // Default visibility state + var _state = {}; + _state[LAYERS.LINKS] = true; + _state[LAYERS.FRESNEL] = false; // off by default + _state[LAYERS.TRAILS] = true; + _state[LAYERS.ZONES] = true; + _state[LAYERS.PORTALS] = true; + _state[LAYERS.COVERAGE] = false; + _state[LAYERS.CROWD_FLOW] = false; + + // Listener registry: layerName -> [callback, ...] + var _listeners = {}; + + /** + * Register a callback for layer visibility changes. + * @param {string} layer - Layer name (use LAYERS constants) + * @param {function(visible: boolean)} callback + */ + function onLayerChange(layer, callback) { + if (!_listeners[layer]) _listeners[layer] = []; + _listeners[layer].push(callback); + } + + /** + * Remove a previously registered callback. + * @param {string} layer + * @param {function} callback + */ + function offLayerChange(layer, callback) { + if (!_listeners[layer]) return; + _listeners[layer] = _listeners[layer].filter(function(fn) { + return fn !== callback; + }); + } + + /** + * Set layer visibility and notify listeners. + * @param {string} layer - Layer name + * @param {boolean} visible + */ + function setLayerVisible(layer, visible) { + var prev = _state[layer]; + _state[layer] = visible; + + if (prev !== visible && _listeners[layer]) { + _listeners[layer].forEach(function(cb) { + try { cb(visible); } catch (e) { + console.error('[Layers] Listener error for ' + layer + ':', e); + } + }); + } + } + + /** + * Toggle layer visibility. + * @param {string} layer + */ + function toggleLayer(layer) { + setLayerVisible(layer, !_state[layer]); + } + + /** + * Get current visibility of a layer. + * @param {string} layer + * @returns {boolean} + */ + function isVisible(layer) { + return !!_state[layer]; + } + + /** + * Sync a checkbox DOM element with layer state. + * Sets up bidirectional binding: checkbox changes -> layer state -> checkbox updates. + * @param {string} layer + * @param {HTMLInputElement} checkbox + */ + function bindCheckbox(layer, checkbox) { + if (!checkbox) return; + + // Sync initial state + checkbox.checked = _state[layer]; + + // Checkbox -> layer + checkbox.addEventListener('change', function() { + setLayerVisible(layer, checkbox.checked); + }); + + // Layer -> checkbox + onLayerChange(layer, function(visible) { + checkbox.checked = visible; + }); + } + + /** + * Sync a button DOM element with layer state (active class toggling). + * @param {string} layer + * @param {HTMLElement} button + */ + function bindButton(layer, button) { + if (!button) return; + + // Sync initial state + if (_state[layer]) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + + // Button -> layer + button.addEventListener('click', function() { + toggleLayer(layer); + }); + + // Layer -> button + onLayerChange(layer, function(visible) { + if (visible) { + button.classList.add('active'); + } else { + button.classList.remove('active'); + } + }); + } + + // Public API + window.Layers = { + LAYERS: LAYERS, + onLayerChange: onLayerChange, + offLayerChange: offLayerChange, + setLayerVisible: setLayerVisible, + toggleLayer: toggleLayer, + isVisible: isVisible, + bindCheckbox: bindCheckbox, + bindButton: bindButton + }; + + console.log('[Layers] Module loaded'); +})(); diff --git a/dashboard/live.html b/dashboard/live.html index 915caba..43be4cf 100644 --- a/dashboard/live.html +++ b/dashboard/live.html @@ -3437,6 +3437,8 @@ + +