- Improve link highlighting with Fresnel health color mapping - Route re-provision through live page via query param for reliability - Remove simple-mode gating from command palette (now available everywhere except ambient) - Update Fresnel tests, fleet page unpaired node UX, and help articles Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
217 lines
5.4 KiB
JavaScript
217 lines
5.4 KiB
JavaScript
/**
|
|
* Spaxel Dashboard - Router
|
|
*
|
|
* Hash-based routing: #live (default), #timeline, #automations, #settings, #ambient, #replay
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// ============================================
|
|
// Configuration
|
|
// ============================================
|
|
const ROUTES = {
|
|
live: {
|
|
title: 'Live',
|
|
icon: '■',
|
|
description: 'Real-time 3D detection view'
|
|
},
|
|
timeline: {
|
|
title: 'Timeline',
|
|
icon: '⌚',
|
|
description: 'Activity history and events'
|
|
},
|
|
automations: {
|
|
title: 'Automations',
|
|
icon: '⚙',
|
|
description: 'Spatial triggers and automation rules'
|
|
},
|
|
settings: {
|
|
title: 'Settings',
|
|
icon: '⚙',
|
|
description: 'System configuration and preferences'
|
|
},
|
|
ambient: {
|
|
title: 'Ambient',
|
|
icon: '◉',
|
|
description: 'Always-on display mode'
|
|
},
|
|
replay: {
|
|
title: 'Replay',
|
|
icon: '⏵',
|
|
description: 'Time-travel debugging mode'
|
|
},
|
|
simulate: {
|
|
title: 'Simulate',
|
|
icon: '⚛',
|
|
description: 'Pre-deployment simulator'
|
|
}
|
|
};
|
|
|
|
// ============================================
|
|
// State
|
|
// ============================================
|
|
let currentMode = 'live';
|
|
let previousMode = null;
|
|
let modeChangeHandlers = [];
|
|
|
|
// ============================================
|
|
// Router Core
|
|
// ============================================
|
|
|
|
/**
|
|
* Initialize the router
|
|
*/
|
|
function init() {
|
|
const hash = window.location.hash.slice(1) || 'live';
|
|
|
|
// Validate hash against available routes
|
|
if (ROUTES[hash]) {
|
|
currentMode = hash;
|
|
} else {
|
|
currentMode = 'live';
|
|
}
|
|
|
|
// Set initial mode
|
|
setMode(currentMode, false);
|
|
|
|
// Listen for hash changes
|
|
window.addEventListener('hashchange', onHashChange);
|
|
|
|
// Listen for popstate (browser back/forward)
|
|
window.addEventListener('popstate', onPopState);
|
|
|
|
console.log('[Router] Initialized with mode:', currentMode);
|
|
}
|
|
|
|
/**
|
|
* Handle hash change from browser navigation
|
|
*/
|
|
function onHashChange() {
|
|
const hash = window.location.hash.slice(1) || 'live';
|
|
if (hash !== currentMode && ROUTES[hash]) {
|
|
setMode(hash);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle popstate (browser back/forward)
|
|
*/
|
|
function onPopState() {
|
|
const hash = window.location.hash.slice(1) || 'live';
|
|
if (hash !== currentMode && ROUTES[hash]) {
|
|
setMode(hash);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the current mode
|
|
* @param {string} mode - Mode identifier
|
|
* @param {boolean} updateHash - Whether to update the URL hash (default: true)
|
|
*/
|
|
function setMode(mode, updateHash = true) {
|
|
if (!ROUTES[mode]) {
|
|
console.error('[Router] Unknown mode:', mode);
|
|
return;
|
|
}
|
|
|
|
previousMode = currentMode;
|
|
currentMode = mode;
|
|
|
|
// Update hash without triggering hashchange event
|
|
if (updateHash) {
|
|
history.replaceState({ mode: mode }, '', '#' + mode);
|
|
}
|
|
|
|
// Trigger mode change handlers
|
|
notifyModeChange(mode, previousMode);
|
|
|
|
console.log('[Router] Mode changed:', previousMode, '->', mode);
|
|
}
|
|
|
|
/**
|
|
* Notify all registered mode change handlers
|
|
*/
|
|
function notifyModeChange(newMode, oldMode) {
|
|
modeChangeHandlers.forEach(handler => {
|
|
try {
|
|
handler(newMode, oldMode);
|
|
} catch (e) {
|
|
console.error('[Router] Mode change handler error:', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============================================
|
|
// Public API
|
|
// ============================================
|
|
|
|
/**
|
|
* Register a handler for mode changes
|
|
* @param {Function} handler - Callback(newMode, oldMode)
|
|
*/
|
|
function onModeChange(handler) {
|
|
if (typeof handler === 'function') {
|
|
modeChangeHandlers.push(handler);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current mode
|
|
*/
|
|
function getMode() {
|
|
return currentMode;
|
|
}
|
|
|
|
/**
|
|
* Get the previous mode
|
|
*/
|
|
function getPreviousMode() {
|
|
return previousMode;
|
|
}
|
|
|
|
/**
|
|
* Navigate to a specific mode
|
|
*/
|
|
function navigate(mode) {
|
|
if (ROUTES[mode]) {
|
|
setMode(mode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a specific mode is active
|
|
*/
|
|
function isMode(mode) {
|
|
return currentMode === mode;
|
|
}
|
|
|
|
/**
|
|
* Get all available routes
|
|
*/
|
|
function getRoutes() {
|
|
return { ...ROUTES };
|
|
}
|
|
|
|
// ============================================
|
|
// Export
|
|
// ============================================
|
|
window.SpaxelRouter = {
|
|
init: init,
|
|
onModeChange: onModeChange,
|
|
getMode: getMode,
|
|
getPreviousMode: getPreviousMode,
|
|
navigate: navigate,
|
|
isMode: isMode,
|
|
getRoutes: getRoutes
|
|
};
|
|
|
|
// Auto-initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
console.log('[Router] Router module loaded');
|
|
})();
|