feat(dashboard): add layer management module for toggle controls
Wire Fresnel zone toggle through Layers module for consistent state management across toolbar, debug panel, and layer controls. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
294e55cb53
commit
71377b9efc
3 changed files with 203 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
166
dashboard/js/layers.js
Normal file
166
dashboard/js/layers.js
Normal file
|
|
@ -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');
|
||||
})();
|
||||
|
|
@ -3437,6 +3437,8 @@
|
|||
<script src="js/zone-editor.js"></script>
|
||||
<!-- Fresnel zone helper (shared with explainability) -->
|
||||
<script src="js/fresnel.js"></script>
|
||||
<!-- Layer management -->
|
||||
<script src="js/layers.js"></script>
|
||||
<!-- Node placement, GDOP coverage, room editor -->
|
||||
<script src="js/placement.js"></script>
|
||||
<!-- Panel Framework (must load before modules that use it) -->
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue