/
klischa
/
RuView
Обзор
Документация
Войти
/
klischa
/
RuView
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ui/viz.html
376 строк
12 KB
rUv
fix(ui): unbreak viz.html — OrbitControls importmap, WS URL, toast NPE (#760) (#773)
23 май 2026, 17:48
Не верифицирован
23 май 2026, 17:48
5d54412
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WiFi DensePose - 3D Visualization</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: #050510; font-family: 'Courier New', 'Consolas', monospace; color: #88bbdd; } #viz-container { width: 100%; height: 100%; position: relative; } #loading-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #050510; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.6s ease; } #loading-overlay.hidden { opacity: 0; pointer-events: none; } .loading-title { font-size: 22px; color: #aaddff; margin-bottom: 16px; letter-spacing: 4px; text-transform: uppercase; } .loading-bar-track { width: 280px; height: 3px; background: #112233; border-radius: 2px; overflow: hidden; margin-bottom: 12px; } .loading-bar-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #0066ff, #00ccff); border-radius: 2px; transition: width 0.3s ease; } .loading-status { font-size: 11px; color: #446688; } /* Stats.js panel positioning */ #stats-container { position: absolute; top: 40px; right: 140px; z-index: 200; } </style> </head> <body> <div id="viz-container"> <!-- Loading overlay --> <div id="loading-overlay"> <div class="loading-title">WiFi DensePose</div> <div class="loading-bar-track"> <div class="loading-bar-fill" id="loading-fill"></div> </div> <div class="loading-status" id="loading-status">Initializing...</div> </div> <!-- Stats.js container --> <div id="stats-container"></div> </div> <!-- Three.js r160 dropped examples/js/ UMD builds. Load via importmap and expose THREE + OrbitControls as a mutable global so the existing component modules (scene.js, body-model.js, …) keep working without a wider refactor. Note: `import * as THREE` returns a frozen Module Namespace Object — spread it into a plain object before attaching OrbitControls, otherwise the assignment silently no-ops. --> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } } </script> <!-- Stats.js for performance monitoring --> <script src="https://unpkg.com/stats.js@0.17.0/build/stats.min.js"></script> <!-- All app code lives in one module so global THREE is installed before the component modules run. Two separate module scripts would race since each is independently async-resolved. --> <script type="module"> import * as ThreeNS from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const THREE = { ...ThreeNS, OrbitControls }; window.THREE = THREE; // Component modules use `THREE.*` as a global — must be installed first. const { Scene } = await import('./components/scene.js'); const { BodyModel, BodyModelManager } = await import('./components/body-model.js'); const { SignalVisualization } = await import('./components/signal-viz.js'); const { Environment } = await import('./components/environment.js'); const { DashboardHUD } = await import('./components/dashboard-hud.js'); const { WebSocketClient } = await import('./services/websocket-client.js'); const { DataProcessor } = await import('./services/data-processor.js'); const { buildSensingWsUrl } = await import('./services/sensing.service.js'); // -- Application State -- const state = { scene: null, environment: null, bodyModelManager: null, signalViz: null, hud: null, wsClient: null, dataProcessor: null, stats: null, isDemoMode: true, startTime: Date.now() }; // -- Loading Progress -- function setLoadingProgress(pct, msg) { const fill = document.getElementById('loading-fill'); const status = document.getElementById('loading-status'); if (fill) fill.style.width = pct + '%'; if (status) status.textContent = msg; } function hideLoading() { const overlay = document.getElementById('loading-overlay'); if (overlay) overlay.classList.add('hidden'); setTimeout(() => { if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); }, 700); } // -- Initialize Stats.js -- function initStats() { const stats = new Stats(); stats.showPanel(0); // FPS panel stats.dom.style.position = 'relative'; document.getElementById('stats-container').appendChild(stats.dom); return stats; } // -- Main Initialization -- async function init() { const container = document.getElementById('viz-container'); try { setLoadingProgress(10, 'Creating 3D scene...'); // 1. Scene setup state.scene = new Scene(container); setLoadingProgress(25, 'Building environment...'); // 2. Environment (room, grid, APs, zones) state.environment = new Environment(state.scene.getScene()); setLoadingProgress(40, 'Preparing body models...'); // 3. Body model manager state.bodyModelManager = new BodyModelManager(state.scene.getScene()); setLoadingProgress(55, 'Setting up signal visualization...'); // 4. Signal visualization state.signalViz = new SignalVisualization(state.scene.getScene()); setLoadingProgress(65, 'Creating HUD...'); // 5. Dashboard HUD state.hud = new DashboardHUD(container); setLoadingProgress(75, 'Initializing data processor...'); // 6. Data processor state.dataProcessor = new DataProcessor(); setLoadingProgress(80, 'Setting up Stats.js...'); // 7. Stats.js state.stats = initStats(); setLoadingProgress(85, 'Connecting to server...'); // 8. WebSocket client — derive URL from window.location so the page // works on both default (HTTP 8080 / WS 8765) and Docker (3000/3001) // port pairings. `?ws=…` query overrides for advanced setups. const wsOverride = new URLSearchParams(window.location.search).get('ws'); state.wsClient = new WebSocketClient({ url: wsOverride || buildSensingWsUrl(), onMessage: (msg) => handleWebSocketMessage(msg), onStateChange: (newState, oldState) => handleConnectionStateChange(newState, oldState), onError: (err) => console.error('[VIZ] WebSocket error:', err) }); // Attempt connection (will fall back to demo mode if server unavailable) state.wsClient.connect(); setLoadingProgress(95, 'Starting render loop...'); // 9. Register the main update loop state.scene.onUpdate((delta, elapsed) => { mainUpdate(delta, elapsed); }); // Start rendering state.scene.start(); setLoadingProgress(100, 'Ready'); // Hide loading after a brief moment setTimeout(hideLoading, 400); console.log('[VIZ] Initialization complete'); } catch (err) { console.error('[VIZ] Initialization failed:', err); setLoadingProgress(100, 'Error: ' + err.message); } } // -- Main Update Loop (called every frame) -- function mainUpdate(delta, elapsed) { // Stats.js begin if (state.stats) state.stats.begin(); // Determine data source let vizData = null; if (state.isDemoMode) { // Generate demo data vizData = state.dataProcessor.generateDemoData(delta); // Generate demo signal data const demoSignal = SignalVisualization.generateDemoData(elapsed); state.signalViz.updateSignalData(demoSignal); } // If we have viz data (from demo or last processed real data), update visualizations if (vizData) { // Update body models state.bodyModelManager.update(vizData.persons, delta); // Update zone occupancy state.environment.updateZoneOccupancy(vizData.zoneOccupancy); // Update confidence heatmap const heatmap = state.dataProcessor.generateConfidenceHeatmap( vizData.persons, 20, 15, 8, 6 ); state.environment.updateConfidenceHeatmap(heatmap); } // Update environment animations (AP pulse, signal paths) state.environment.update(delta, elapsed); // Update signal visualization animations state.signalViz.update(delta, elapsed); // Update HUD if (state.hud) { state.hud.tickFPS(); const wsMetrics = state.wsClient.getMetrics(); state.hud.updateState({ connectionStatus: state.wsClient.state, isRealData: state.wsClient.isRealData && !state.isDemoMode, latency: wsMetrics.latency, messageCount: wsMetrics.messageCount, uptime: wsMetrics.uptime, personCount: state.bodyModelManager.getActiveCount(), confidence: state.bodyModelManager.getAverageConfidence(), sensingMode: state.isDemoMode ? 'Mock' : (state.wsClient.isRealData ? 'CSI' : 'Mock') }); } // Stats.js end if (state.stats) state.stats.end(); } // -- Handle incoming WebSocket messages -- function handleWebSocketMessage(message) { const processed = state.dataProcessor.processMessage(message); if (!processed) return; // Switch off demo mode when we get real data if (processed.persons.length > 0) { if (state.isDemoMode) { state.isDemoMode = false; console.log('[VIZ] Switched to live data mode'); } // Update body models state.bodyModelManager.update(processed.persons, 0.016); // Update zone occupancy state.environment.updateZoneOccupancy(processed.zoneOccupancy); // Update signal data if available if (processed.signalData) { state.signalViz.updateSignalData(processed.signalData); } // Update confidence heatmap const heatmap = state.dataProcessor.generateConfidenceHeatmap( processed.persons, 20, 15, 8, 6 ); state.environment.updateConfidenceHeatmap(heatmap); } } // -- Handle WebSocket connection state changes -- function handleConnectionStateChange(newState, oldState) { console.log(`[VIZ] Connection: ${oldState} -> ${newState}`); if (newState === 'connected') { // Will switch from demo to real when data arrives console.log('[VIZ] Connected to server, waiting for data...'); } else if (newState === 'error' || newState === 'disconnected') { // Fall back to demo mode if (!state.isDemoMode) { state.isDemoMode = true; console.log('[VIZ] Switched to demo mode (server unavailable)'); } } } // -- Cleanup on page unload -- window.addEventListener('beforeunload', () => { if (state.wsClient) state.wsClient.dispose(); if (state.bodyModelManager) state.bodyModelManager.dispose(); if (state.signalViz) state.signalViz.dispose(); if (state.environment) state.environment.dispose(); if (state.hud) state.hud.dispose(); if (state.scene) state.scene.dispose(); }); // -- Keyboard shortcuts -- document.addEventListener('keydown', (e) => { switch (e.key.toLowerCase()) { case 'r': // Reset camera if (state.scene) state.scene.resetCamera(); break; case 'd': // Toggle demo mode state.isDemoMode = !state.isDemoMode; console.log(`[VIZ] Demo mode: ${state.isDemoMode ? 'ON' : 'OFF'}`); break; case 'c': // Force reconnect if (state.wsClient) { state.wsClient.disconnect(); state.wsClient.autoReconnect = true; state.wsClient.reconnectAttempts = 0; state.wsClient.connect(); } break; } }); // -- Start -- init(); </script> </body> </html>