Files
frigate-camera-control-bridge/frigate_camera_control_bridge/static/app.js
T
2026-08-03 01:40:15 +02:00

161 lines
4.6 KiB
JavaScript

const SNAPSHOT_REFRESH_DELAY_MS = 2000;
function formatRemaining(totalSeconds) {
const seconds = Math.max(0, Math.ceil(totalSeconds));
const minutes = Math.floor(seconds / 60);
const rest = seconds % 60;
return `${minutes}:${String(rest).padStart(2, "0")}`;
}
function updateTimers() {
const now = Date.now() / 1000;
document.querySelectorAll("[data-auto-off-at]").forEach((item) => {
const target = Number(item.dataset.autoOffAt || 0);
if (!target) {
return;
}
const remaining = target - now;
if (remaining <= 0) {
item.textContent = item.dataset.expiredLabel || "Automatic turn-off pending";
item.classList.add("timer-expired");
return;
}
item.textContent = `${item.dataset.prefix || "Automatic turn-off in"} ${formatRemaining(remaining)}`;
});
}
function refreshStatusElement() {
return document.querySelector("[data-refresh-status]");
}
function showRefreshMessage(message, type = "success") {
const status = refreshStatusElement();
if (!status) {
return;
}
status.textContent = message || "";
status.classList.toggle("is-visible", Boolean(message));
status.classList.toggle("is-success", type === "success");
status.classList.toggle("is-error", type === "error");
}
function rememberRefreshMessage(message) {
try {
window.sessionStorage.setItem("frigateRefreshMessage", message);
} catch (_error) {
// Some browsers may block sessionStorage; the refresh still succeeds.
}
}
function restoreRefreshMessage() {
try {
const message = window.sessionStorage.getItem("frigateRefreshMessage");
if (!message) {
return;
}
window.sessionStorage.removeItem("frigateRefreshMessage");
showRefreshMessage(message, "success");
} catch (_error) {
// Ignore storage errors and keep the GUI usable.
}
}
async function handleRefreshSubmit(form, event) {
event.preventDefault();
const button = form.querySelector(".refresh-button");
if (button && button.classList.contains("is-refreshing")) {
return;
}
const status = refreshStatusElement();
const workingLabel = status?.dataset.workingLabel || "Refreshing cameras...";
showRefreshMessage(workingLabel, "success");
if (button) {
button.disabled = true;
button.classList.add("is-refreshing");
button.setAttribute("aria-busy", "true");
}
try {
const response = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
});
const payload = await response.json();
if (!response.ok || payload.ok === false) {
throw new Error(payload.message || "Refresh failed");
}
showRefreshMessage(payload.message, "success");
if (payload.changed && payload.redirect_url) {
rememberRefreshMessage(payload.message);
window.setTimeout(() => {
window.location.assign(payload.redirect_url);
}, 900);
return;
}
} catch (error) {
showRefreshMessage(error.message || "Refresh failed", "error");
} finally {
if (button) {
button.disabled = false;
button.classList.remove("is-refreshing");
button.removeAttribute("aria-busy");
}
}
}
function refreshSnapshot(button) {
if (button.classList.contains("is-refreshing")) {
return;
}
const media = button.closest(".media");
const image = media?.querySelector("img.snapshot");
if (!image) {
return;
}
const url = new URL(image.currentSrc || image.src, window.location.href);
url.searchParams.set("refresh", "1");
url.searchParams.set("v", String(Date.now()));
const cleanup = () => {
button.disabled = false;
button.classList.remove("is-refreshing");
button.removeAttribute("aria-busy");
};
button.disabled = true;
button.classList.add("is-refreshing");
button.setAttribute("aria-busy", "true");
image.addEventListener("load", cleanup, { once: true });
image.addEventListener("error", cleanup, { once: true });
window.setTimeout(() => {
image.src = url.toString();
}, SNAPSHOT_REFRESH_DELAY_MS);
}
document.addEventListener("click", (event) => {
const button = event.target.closest(".snapshot-refresh");
if (!(button instanceof HTMLButtonElement)) {
return;
}
refreshSnapshot(button);
});
document.addEventListener("submit", (event) => {
const form = event.target;
if (!(form instanceof HTMLFormElement)) {
return;
}
if (form.classList.contains("refresh-form")) {
handleRefreshSubmit(form, event);
return;
}
if (form.dataset.requiresAck !== "true") {
return;
}
const message = form.dataset.ackMessage || "Confirm this action";
if (!window.confirm(message)) {
event.preventDefault();
}
});
updateTimers();
restoreRefreshMessage();
setInterval(updateTimers, 1000);