/* * Wires floorplan3d.js to identity's GET /floorplan/presence. * * One request gives the plan AND who is in it, so there is no second call and no window * where the rooms are drawn but the people are missing. */ "use strict"; const params = new URLSearchParams(location.search); const API = (params.get("api") || "").replace(/\/$/, ""); const TOKEN = params.get("token") || ""; const POLL_MS = Number(params.get("poll") || 15000); const $ = (id) => document.getElementById(id); if (!API || !TOKEN) { $("error").hidden = false; $("error").textContent = "Not configured — needs ?api=&token= in the URL."; throw new Error("floorplan: missing ?api=/&token="); } const plan = new Floorplan3D($("plan"), { // identity serves the picture; this builds the URL rather than the SDK knowing about // any particular service. photoUrl: (personId) => `${API}/people/${personId}/photo?token=${encodeURIComponent(TOKEN)}`, }); let levels = []; let activeLevelId = params.get("level") ? Number(params.get("level")) : null; function load() { fetch(`${API}/floorplan/presence`, { headers: { Authorization: `Bearer ${TOKEN}` } }) .then((res) => { if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res.json(); }) .then((data) => { $("error").hidden = true; levels = data.levels || []; if (activeLevelId === null && levels.length) activeLevelId = levels[0].id; renderLevelButtons(); const level = levels.find((l) => l.id === activeLevelId) || levels[0] || null; $("level-name").textContent = level ? level.name : "No levels drawn yet"; plan.setLevel(level, data.unplaced || []); // The honest count, in the same sentence: placed people and the ones the system // cannot locate. A view that silently omits the second number implies it knows // where everybody is. const placed = (level ? level.rooms || [] : []).reduce( (n, room) => n + (room.occupants || []).length, 0 ); const unplaced = (data.unplaced || []).length; $("summary").textContent = unplaced ? `${placed} placed · ${unplaced} home but not locatable` : `${placed} placed`; }) .catch((err) => { $("error").hidden = false; $("error").textContent = `identity: ${err.message}`; }) .finally(() => setTimeout(load, POLL_MS)); } function renderLevelButtons() { if (levels.length < 2) { $("levels").innerHTML = ""; return; } $("levels").innerHTML = levels .map( (l) => `` ) .join(""); $("levels") .querySelectorAll("button") .forEach((btn) => btn.addEventListener("click", () => { activeLevelId = Number(btn.dataset.id); load(); }) ); } function escapeHtml(s) { return String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]) ); } load();