import { startStatusSocket } from "/js/status-panel.js"; import { buildCameraGrid, setPreviewActive } from "/js/camera-preview.js"; import { buildCameraTabs } from "/js/camera-settings.js"; import { initRecordingPanel } from "/js/recording-panel.js"; import { initLogPanel } from "/js/log-panel.js"; import { initPointcloudView } from "/js/pointcloud-view.js"; async function getJSON(url, opts) { const res = await fetch(url, opts); if (!res.ok) throw new Error(`${url}: ${res.status} ${await res.text()}`); return res.json(); } const OVERLAY_IDS = ["overlay-settings", "overlay-recording", "overlay-log"]; let pointcloudView = null; function anyOverlayOpen() { return OVERLAY_IDS.some((id) => !document.getElementById(id).classList.contains("hidden")); } // The point-cloud render loop keeps running (cheap-ish) but there's no // reason to keep painting frames the operator can't see behind a full-screen // overlay — pause on open, resume once every overlay is closed. function syncPointcloudPaused() { if (pointcloudView) pointcloudView.setPaused(anyOverlayOpen()); } function openOverlay(id) { document.getElementById(id).classList.remove("hidden"); syncPointcloudPaused(); } function closeOverlay(id) { document.getElementById(id).classList.add("hidden"); syncPointcloudPaused(); } // Camera MJPEG only needs to stream while its tab is actually visible inside // the open Settings overlay — otherwise it's wasted phone battery/bandwidth // behind a closed overlay (same reasoning M3's point-cloud stream will need // for the scan view itself). function syncPreviewActive() { const overlaySettings = document.getElementById("overlay-settings"); const previewPane = document.getElementById("pane-camera-preview"); const active = !overlaySettings.classList.contains("hidden") && previewPane.classList.contains("active"); setPreviewActive(active); } function setupOverlays() { document.querySelectorAll("[data-close]").forEach((el) => { el.onclick = () => { closeOverlay(el.dataset.close); syncPreviewActive(); }; }); document.getElementById("btn-settings").onclick = () => { openOverlay("overlay-settings"); syncPreviewActive(); }; document.getElementById("btn-rec-open").onclick = () => openOverlay("overlay-recording"); document.getElementById("btn-open-log").onclick = () => openOverlay("overlay-log"); } function setupPaneTabs() { const btns = document.querySelectorAll(".pane-tab-btn[data-pane]"); btns.forEach((btn) => { btn.onclick = () => { btns.forEach((b) => b.classList.remove("active")); document.querySelectorAll(".pane-tab").forEach((p) => p.classList.remove("active")); btn.classList.add("active"); document.getElementById(btn.dataset.pane).classList.add("active"); syncPreviewActive(); }; }); } async function main() { setupOverlays(); setupPaneTabs(); pointcloudView = initPointcloudView(); const status = await getJSON("/api/status"); await buildCameraGrid(status.cameras); await buildCameraTabs(status.cameras); initRecordingPanel(status.default_save_dir); initLogPanel(); document.getElementById("btn-lidar-start").onclick = async () => { await getJSON("/api/lidar/start", { method: "POST" }); }; document.getElementById("btn-lidar-stop").onclick = async () => { await getJSON("/api/lidar/stop", { method: "POST" }); }; document.getElementById("btn-estop").onclick = async () => { if (!confirm("모든 프로세스(LiDAR/카메라/녹화)를 즉시 정지합니다. 계속할까요?")) return; await getJSON("/api/system/estop", { method: "POST" }); }; document.getElementById("btn-fastlio-test-start").onclick = async () => { try { await getJSON("/api/fastlio/start", { method: "POST" }); } catch (e) { alert(String(e)); } }; document.getElementById("btn-fastlio-test-stop").onclick = async () => { try { await getJSON("/api/fastlio/stop", { method: "POST" }); } catch (e) { alert(String(e)); } }; const btnViewOrbit = document.getElementById("btn-view-orbit"); const btnViewLidar = document.getElementById("btn-view-lidar"); btnViewOrbit.onclick = () => { pointcloudView.setViewMode("orbit"); btnViewOrbit.className = "btn btn-blue"; btnViewLidar.className = "btn btn-gray"; }; btnViewLidar.onclick = () => { pointcloudView.setViewMode("lidar"); btnViewLidar.className = "btn btn-blue"; btnViewOrbit.className = "btn btn-gray"; }; startStatusSocket(); } main().catch((e) => { console.error(e); alert("초기화 실패: " + e); });