128 lines
5.2 KiB
JavaScript
128 lines
5.2 KiB
JavaScript
(function () {
|
|
function restartPreview() {
|
|
const img = document.getElementById("preview_img");
|
|
if (!img) {
|
|
return;
|
|
}
|
|
img.src = `/preview.mjpg?t=${Date.now()}`;
|
|
}
|
|
|
|
function setResolutionInputs(width, height, fps) {
|
|
document.getElementById("width_input").value = String(width || "");
|
|
document.getElementById("height_input").value = String(height || "");
|
|
document.getElementById("fps_input").value = String(fps || "");
|
|
const preset = `${width}x${height}@${fps}`;
|
|
const sel = document.getElementById("resolution_select");
|
|
const hasPreset = Array.from(sel.options).some((opt) => opt.value === preset);
|
|
sel.value = hasPreset ? preset : "";
|
|
}
|
|
|
|
async function api(path) {
|
|
const response = await fetch(path);
|
|
if (!response.ok) {
|
|
throw new Error(await response.text());
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
function fmtBytes(n) {
|
|
if (n < 1024) {
|
|
return `${n} B`;
|
|
}
|
|
if (n < 1024 * 1024) {
|
|
return `${(n / 1024).toFixed(1)} KB`;
|
|
}
|
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
async function refreshAll() {
|
|
const [health, photos] = await Promise.all([api("/api/health"), api("/api/photos")]);
|
|
const camera = health.camera || {};
|
|
const widthInput = document.getElementById("width_input");
|
|
if (widthInput.dataset.initialized !== "1") {
|
|
setResolutionInputs(camera.requested_width, camera.requested_height, camera.requested_fps);
|
|
widthInput.dataset.initialized = "1";
|
|
}
|
|
|
|
document.getElementById("status").textContent = camera.ok
|
|
? `Camera OK\nSource: ${camera.source}\nBackend: ${camera.backend}\nActive: ${camera.profile || "-"}\nRequested: ${camera.requested_profile}\nPreferred RS Serial: ${camera.preferred_realsense_serial || "-"}\nConfig RS Serial: ${camera.config_realsense_serial || "-"}\nFallback RS Serial: ${camera.configured_realsense_serial || "-"}\nActive RS Serial: ${camera.realsense_serial || "-"}\nSamples: ${photos.items.length}`
|
|
: `Camera not ready\nSource: ${camera.source}\nRequested: ${camera.requested_profile}\nPreferred RS Serial: ${camera.preferred_realsense_serial || "-"}\nConfig RS Serial: ${camera.config_realsense_serial || "-"}\nFallback RS Serial: ${camera.configured_realsense_serial || "-"}\nActive RS Serial: ${camera.realsense_serial || "-"}\nError: ${camera.last_error || "-"}`;
|
|
|
|
document.getElementById("camera_status").textContent = JSON.stringify(health, null, 2);
|
|
|
|
const gallery = document.getElementById("gallery");
|
|
gallery.innerHTML = "";
|
|
for (const item of photos.items) {
|
|
const card = document.createElement("article");
|
|
card.className = "card";
|
|
card.innerHTML = `
|
|
<img class="thumb" src="/samples/${encodeURIComponent(item.name)}?t=${Date.now()}" alt="">
|
|
<div class="name">${item.name}</div>
|
|
<div class="meta">${fmtBytes(item.size)}</div>
|
|
<div class="row">
|
|
<a class="btn secondary" href="/api/download?name=${encodeURIComponent(item.name)}">Download</a>
|
|
<button class="danger" data-name="${item.name}">Delete</button>
|
|
</div>
|
|
`;
|
|
gallery.appendChild(card);
|
|
}
|
|
|
|
gallery.querySelectorAll("button.danger").forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const { name } = btn.dataset;
|
|
await api(`/api/delete?name=${encodeURIComponent(name)}`);
|
|
await refreshAll();
|
|
});
|
|
});
|
|
}
|
|
|
|
document.getElementById("capture_btn").addEventListener("click", async () => {
|
|
const status = document.getElementById("status");
|
|
status.textContent = "Capturing...";
|
|
try {
|
|
const resp = await api("/api/capture");
|
|
status.textContent = `Saved: ${resp.name}`;
|
|
await refreshAll();
|
|
} catch (e) {
|
|
status.textContent = `Capture failed\n${e.message}`;
|
|
}
|
|
});
|
|
|
|
document.getElementById("resolution_select").addEventListener("change", (e) => {
|
|
const value = e.target.value;
|
|
const match = value.match(/^(\d+)x(\d+)@(\d+)$/);
|
|
if (!match) {
|
|
return;
|
|
}
|
|
setResolutionInputs(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
});
|
|
|
|
document.getElementById("apply_resolution_btn").addEventListener("click", async () => {
|
|
const width = Number(document.getElementById("width_input").value);
|
|
const height = Number(document.getElementById("height_input").value);
|
|
const fps = Number(document.getElementById("fps_input").value);
|
|
const status = document.getElementById("status");
|
|
status.textContent = `Applying resolution ${width}x${height}@${fps}...`;
|
|
try {
|
|
const resp = await api(
|
|
`/api/set_resolution?width=${encodeURIComponent(width)}&height=${encodeURIComponent(height)}&fps=${encodeURIComponent(fps)}`
|
|
);
|
|
const camera = resp.camera || {};
|
|
setResolutionInputs(camera.requested_width, camera.requested_height, camera.requested_fps);
|
|
restartPreview();
|
|
await refreshAll();
|
|
} catch (e) {
|
|
status.textContent = `Resolution change failed\n${e.message}`;
|
|
}
|
|
});
|
|
|
|
document.getElementById("refresh_btn").addEventListener("click", refreshAll);
|
|
|
|
refreshAll().catch((e) => {
|
|
document.getElementById("status").textContent = String(e);
|
|
});
|
|
setInterval(() => {
|
|
refreshAll().catch(() => {});
|
|
}, 10000);
|
|
})();
|