// client.js
// UI: Buttons, Anzeige von Result als JSON + Baum, Fallback für Commands
function appendLog(line) {
const el = document.getElementById("log");
if (!el) return;
const now = new Date().toISOString();
el.value += `[${now}] ${line}\n`;
el.scrollTop = el.scrollHeight;
}
function clearTextarea(id) {
const el = document.getElementById(id);
if (el) el.value = "";
}
function clearElement(id) {
const el = document.getElementById(id);
if (el) el.innerHTML = "";
}
function formatScalar(value) {
if (value === null) return "null";
if (value === undefined) return "undefined";
if (typeof value === "string") return JSON.stringify(value);
if (typeof value === "number" && Number.isFinite(value)) return String(value);
if (typeof value === "boolean") return String(value);
return String(value);
}
function renderTree(container, value, key = "result", open = true) {
if (!container) return;
container.innerHTML = "";
container.appendChild(renderNode(key, value, open));
}
function renderNode(key, value, open = false) {
const isObject = value !== null && typeof value === "object";
if (!isObject) {
const leaf = document.createElement("div");
leaf.className = "tree-leaf";
leaf.textContent = `${key}: ${formatScalar(value)}`;
return leaf;
}
const details = document.createElement("details");
details.open = open;
const summary = document.createElement("summary");
summary.textContent = Array.isArray(value)
? `${key} [${value.length}]`
: key;
details.appendChild(summary);
const body = document.createElement("div");
body.style.marginLeft = "16px";
if (Array.isArray(value)) {
value.forEach((item, idx) => {
body.appendChild(renderNode(String(idx), item, false));
});
} else {
Object.entries(value).forEach(([childKey, childVal]) => {
body.appendChild(renderNode(childKey, childVal, false));
});
}
details.appendChild(body);
return details;
}
function renderResult(result) {
const jsonEl = document.getElementById("result-json");
const treeEl = document.getElementById("result-tree");
if (jsonEl) {
jsonEl.value = JSON.stringify(result, null, 2);
}
renderTree(treeEl, result, "result", true);
}
dataCache = null
headersCache = null
rowsCache = null
timeCache = null
jsonCache = null
async function fetchCSV(){
if(dataCache == null || headersCache == null || rowsCache == null || timeCache == null){
({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer());
timeCache = Date.now();
}
if (Date.now() - timeCache > 1000){
({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer());
timeCache = Date.now();
}
return {data: dataCache, headers: headersCache, rows: rowsCache}
}
async function fetchCSV_fromServer() {
const res = await fetch("/api/latest-snapshot");
if (!res.ok) throw new Error("Fehler beim Laden des Snapshots");
let data;
if (res.headers.get("content-type")?.includes("application/json")) {
data = await res.json();
} else {
const csvData = await res.text();
data = {
filename: "latest.csv",
mtime: new Date().toISOString(),
content: csvData
};
}
if (!jsonCache && data.jsonFile) {
jsonCache = JSON.parse(data.jsonFile.content);
}
const lines = data.content.trim().split(/\r?\n/).filter(Boolean);
if (lines.length < 2) {
throw new Error("Keine oder unvollständige Daten");
}
const headers = lines[0].split(",").map(h => h.trim());
const rows = lines.slice(1).map(line => {
const cells = line.split(",");
const obj = {};
headers.forEach((h, i) => {
const raw = (cells[i] ?? "").trim();
const numeric = Number(raw);
obj[h] = raw !== "" && Number.isFinite(numeric) ? numeric : raw;
});
return obj;
});
return { data, headers, rows };
}
async function fetchWebcamSnapshotData() {
const { data, headers, rows } = await fetchCSV();
return {
filename: data.filename,
mtime: data.mtime,
headers,
rows,
robotIntrinsics: jsonCache,
imageFile: data.imageFile,
image2: data.image2
};
}
async function sendToBodyTracker({imageFile, image2, robotIntrinsics}) {
const response = await fetch('/api/estimate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imageFile, image2, robotIntrinsics })
});
if (!response.ok) {
const message = await response.text();
throw new Error(`BodyTracker fehlgeschlagen (${response.status}): ${message}`);
}
return await response.json();
}
async function renderSnapshot() {
const table = document.getElementById("snapshot-table");
const pictureEl = document.getElementById("snapshot-info-picture");
if (!table) return;
try {
const { data, headers, rows } = await fetchCSV();
// Info anzeigen
const infoEl = document.getElementById("snapshot-info");
if (infoEl) {
infoEl.textContent = `Datei: ${data.filename}, Geändert: ${new Date(data.mtime).toLocaleString()}, Zeilen: ${rows.length}`;
}
// Bild anzeigen, falls vorhanden
if (pictureEl) {
let imagesHtml = '';
if (data.imageFile) {
imagesHtml += ``;
}
if (data.image2) {
imagesHtml += `
`;
}
if (imagesHtml) {
pictureEl.innerHTML = `