Setup-Button pro Kamera: Relay ruft SETUP_<name>-URL auf

This commit is contained in:
Joachim Hummel committed 2026-10-17 21:11:24 +02:00
1 parent 146ba6c399
commit 70b63cb215
4 files changed
+50 -7

No files matched your search

+25 -6
View File
@@ -44,6 +44,7 @@ main{padding:22px;max-width:1800px;margin:0 auto}
.btn svg{width:15px;height:15px}
.btn:hover{filter:brightness(1.15);box-shadow:0 4px 16px rgba(139,92,246,.4)}
.btn:active{transform:scale(.95)}
.btn:disabled{opacity:.7;cursor:default}
.btn.alt{background:#222a3a;box-shadow:none;color:var(--fg);border:1px solid var(--line)}
.btn.alt:hover{border-color:var(--accent);color:var(--accent)}
.focus .tile.big .view{aspect-ratio:auto;height:min(75vh,56.25vw)}
@@ -81,6 +82,7 @@ const el = (tag, cls, text) => {
};
const ICONS = {
cam: '<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>',
cog: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
full: '<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>',
off: '<path d="M1 1l22 22M21 21H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3m3-3h6l2 3h4a2 2 0 0 1 2 2v9.34m-7.72-2.06a4 4 0 1 1-5.56-5.56"/>'
};
@@ -88,7 +90,7 @@ const icon = k => { const s = document.createElementNS("http://www.w3.org/2000/s
const btn = (label, ic, fn, cls) => { const b = el("button", "btn" + (cls ? " " + cls : "")); b.title = label; b.append(icon(ic), el("span", "", label)); b.onclick = fn; return b; };
// mode: "live" = MJPEG-Stream, "thumb" = Einzelbild im Intervall
function makeTile(name, mode) {
function makeTile(name, mode, hasSetup) {
const t = el("figure", "tile");
const view = el("div", "view");
const img = el("img");
@@ -109,6 +111,21 @@ function makeTile(name, mode) {
a.click();
});
const full = btn("Vollbild", "full", () => document.fullscreenElement ? document.exitFullscreen() : t.requestFullscreen(), "alt");
if (hasSetup) {
const setup = btn("Setup", "cog", async () => {
setup.disabled = true;
const label = setup.querySelector("span");
try {
const r = await fetch(ROOT + name + "/setup", { method: "POST" });
if (!r.ok) throw new Error(r.status);
label.textContent = "Gesendet ✓";
} catch (e) {
label.textContent = "Fehler ✗";
}
setTimeout(() => { label.textContent = "Setup"; setup.disabled = false; }, 2500);
}, "alt");
actions.append(setup);
}
actions.append(snap, full);
cap.append(el("span", "dot"), link, state, el("span", "sp"), actions);
t.append(view, cap);
@@ -145,20 +162,22 @@ function setOnline(o, on) {
o.online = on;
}
function build(names) {
function build(cams) {
const names = cams.map(c => c.name);
const setupOf = n => cams.find(c => c.name === n).setup;
order = names;
app.replaceChildren();
if (CURRENT && names.includes(CURRENT)) {
app.className = "focus";
const big = makeTile(CURRENT, "live");
const big = makeTile(CURRENT, "live", setupOf(CURRENT));
big.classList.add("big");
app.append(big);
const strip = el("div", "strip");
names.filter(n => n !== CURRENT).forEach(n => strip.append(makeTile(n, "thumb")));
names.filter(n => n !== CURRENT).forEach(n => strip.append(makeTile(n, "thumb", false)));
if (strip.children.length) app.append(strip);
} else {
const grid = el("div", "grid");
names.forEach(n => grid.append(makeTile(n, "live")));
names.forEach(n => grid.append(makeTile(n, "live", setupOf(n))));
app.append(grid);
}
}
@@ -170,7 +189,7 @@ async function poll() {
const data = await r.json();
banner.style.display = "none";
const names = data.cameras.map(c => c.name);
if (names.join() !== order.join()) build(names);
if (names.join() !== order.join()) build(data.cameras);
data.cameras.forEach(c => setOnline(tiles[c.name], c.online));
const n = data.cameras.filter(c => c.online).length;
document.getElementById("count").innerHTML = "<b>" + n + "</b> / " + data.cameras.length + " online";