initial commit
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
const state = {
|
||||
activeColor: null,
|
||||
alertActive: false,
|
||||
demoActive: false,
|
||||
busy: false,
|
||||
};
|
||||
|
||||
const lamps = [...document.querySelectorAll(".lamp")];
|
||||
const alertToggle = document.getElementById("alertToggle");
|
||||
const demoToggle = document.getElementById("demoToggle");
|
||||
const allOffButton = document.getElementById("allOffButton");
|
||||
const activeColorText = document.getElementById("activeColorText");
|
||||
const alertToggleLabel = document.getElementById("alertToggleLabel");
|
||||
const demoToggleLabel = document.getElementById("demoToggleLabel");
|
||||
const connectionBadge = document.getElementById("connectionBadge");
|
||||
const message = document.getElementById("message");
|
||||
|
||||
function setMessage(text, isError = false) {
|
||||
message.textContent = text;
|
||||
message.classList.toggle("is-error", isError);
|
||||
}
|
||||
|
||||
function setConnection(online) {
|
||||
connectionBadge.textContent = online ? "API online" : "API offline";
|
||||
connectionBadge.className = `badge ${online ? "badge--online" : "badge--offline"}`;
|
||||
}
|
||||
|
||||
function render() {
|
||||
lamps.forEach((lamp) => {
|
||||
const active = !state.demoActive && lamp.dataset.color === state.activeColor;
|
||||
lamp.classList.toggle("is-active", active);
|
||||
lamp.setAttribute("aria-pressed", String(active));
|
||||
lamp.disabled = state.busy;
|
||||
});
|
||||
|
||||
activeColorText.textContent = state.demoActive
|
||||
? "Demo running"
|
||||
: (state.activeColor || "Off");
|
||||
|
||||
alertToggle.setAttribute("aria-checked", String(state.alertActive));
|
||||
alertToggleLabel.textContent = state.alertActive ? "On" : "Off";
|
||||
alertToggle.disabled = state.busy;
|
||||
|
||||
demoToggle.setAttribute("aria-checked", String(state.demoActive));
|
||||
demoToggleLabel.textContent = state.demoActive ? "On" : "Off";
|
||||
demoToggle.disabled = state.busy;
|
||||
|
||||
allOffButton.disabled = state.busy;
|
||||
}
|
||||
|
||||
function applyApiState(payload) {
|
||||
if (Object.hasOwn(payload, "active_color")) state.activeColor = payload.active_color;
|
||||
if (Object.hasOwn(payload, "alert_active")) state.alertActive = Boolean(payload.alert_active);
|
||||
if (Object.hasOwn(payload, "demo_active")) state.demoActive = Boolean(payload.demo_active);
|
||||
}
|
||||
|
||||
async function apiCall(path, options = {}) {
|
||||
const result = await fetch(path, {
|
||||
method: options.method || "GET",
|
||||
headers: { "Accept": "application/json" },
|
||||
});
|
||||
const payload = await result.json().catch(() => ({}));
|
||||
if (!result.ok) {
|
||||
throw new Error(payload.error || payload.detail || `Request failed (${result.status})`);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function runAction(action, successMessage) {
|
||||
if (state.busy) return;
|
||||
state.busy = true;
|
||||
render();
|
||||
setMessage("Sending command…");
|
||||
|
||||
try {
|
||||
const payload = await action();
|
||||
applyApiState(payload);
|
||||
setConnection(true);
|
||||
setMessage(successMessage);
|
||||
} catch (error) {
|
||||
setConnection(false);
|
||||
setMessage(error.message, true);
|
||||
} finally {
|
||||
state.busy = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
lamps.forEach((lamp) => {
|
||||
lamp.addEventListener("click", () => {
|
||||
const color = lamp.dataset.color;
|
||||
runAction(
|
||||
() => apiCall(`/ui-api/color/${color}`, { method: "POST" }),
|
||||
`${color[0].toUpperCase()}${color.slice(1)} light activated.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
alertToggle.addEventListener("click", () => {
|
||||
const nextState = state.alertActive ? "off" : "on";
|
||||
runAction(
|
||||
() => apiCall(`/ui-api/alert/${nextState}`, { method: "POST" }),
|
||||
`Alert turned ${nextState}.`,
|
||||
);
|
||||
});
|
||||
|
||||
demoToggle.addEventListener("click", () => {
|
||||
const nextState = state.demoActive ? "off" : "on";
|
||||
runAction(
|
||||
() => apiCall(`/ui-api/demo/${nextState}`, { method: "POST" }),
|
||||
`Demo mode turned ${nextState}.`,
|
||||
);
|
||||
});
|
||||
|
||||
allOffButton.addEventListener("click", () => {
|
||||
runAction(
|
||||
() => apiCall("/ui-api/off", { method: "POST" }),
|
||||
"All outputs turned off.",
|
||||
);
|
||||
});
|
||||
|
||||
async function refreshStatus({ quiet = false } = {}) {
|
||||
if (state.busy) return;
|
||||
try {
|
||||
const payload = await apiCall("/ui-api/status");
|
||||
applyApiState(payload);
|
||||
setConnection(true);
|
||||
if (!quiet) setMessage("Status synchronized.");
|
||||
render();
|
||||
} catch (error) {
|
||||
setConnection(false);
|
||||
if (!quiet) setMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
refreshStatus();
|
||||
setInterval(() => refreshStatus({ quiet: true }), 1500);
|
||||
Reference in New Issue
Block a user