Initial commit: scan_web integrated recording UI
FastAPI backend + vanilla JS frontend for LiDAR/camera startup, camera settings, and rosbag recording, unifying the previous scan_gui/scan_gui_dual/ scan_gui_triple desktop tools into one web app.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user