144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
// LightSync frontend — Phase 1 shell
|
|
// WebSocket stub + device list loader
|
|
// Phase 2 expands audio, transport, and WS protocol
|
|
|
|
const WS_URL = `ws://${location.host}/ws`;
|
|
|
|
async function apiPost(path, data) {
|
|
const res = await fetch(path, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (!res.ok) throw new Error(`POST ${path}: HTTP ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
async function apiDelete(path) {
|
|
const res = await fetch(path, { method: "DELETE" });
|
|
if (!res.ok && res.status !== 204) throw new Error(`DELETE ${path}: HTTP ${res.status}`);
|
|
}
|
|
|
|
class LightSyncClient {
|
|
constructor() {
|
|
this.ws = null;
|
|
this.reconnectDelay = 2000;
|
|
this._reconnectTimer = null;
|
|
}
|
|
|
|
connect() {
|
|
if (this._reconnectTimer) {
|
|
clearTimeout(this._reconnectTimer);
|
|
this._reconnectTimer = null;
|
|
}
|
|
|
|
this.ws = new WebSocket(WS_URL);
|
|
|
|
this.ws.onopen = () => {
|
|
document.getElementById("status-dot").className = "status-dot connected";
|
|
document.getElementById("status-text").textContent = "CONNECTED";
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
document.getElementById("status-dot").className = "status-dot";
|
|
document.getElementById("status-text").textContent = "OFFLINE";
|
|
this._reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay);
|
|
};
|
|
|
|
this.ws.onerror = () => {
|
|
document.getElementById("status-dot").className = "status-dot error";
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
const msg = JSON.parse(event.data);
|
|
this.handleMessage(msg);
|
|
};
|
|
}
|
|
|
|
handleMessage(msg) {
|
|
// Phase 2 will dispatch on msg.type
|
|
console.debug("[ws]", msg);
|
|
}
|
|
|
|
send(msg) {
|
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
this.ws.send(JSON.stringify(msg));
|
|
}
|
|
}
|
|
}
|
|
|
|
const client = new LightSyncClient();
|
|
client.connect();
|
|
|
|
// Load device list on startup
|
|
async function loadDevices() {
|
|
try {
|
|
const res = await fetch("/api/devices");
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const devices = await res.json();
|
|
const list = document.getElementById("device-list");
|
|
if (devices.length === 0) {
|
|
list.innerHTML = '<span class="text-dim">no devices registered</span>';
|
|
return;
|
|
}
|
|
list.innerHTML = devices.map(d =>
|
|
`<div class="device-row">
|
|
<div class="device-row-info">
|
|
<span class="text-accent">${escapeHtml(d.name)}</span>
|
|
<span class="text-dim">${escapeHtml(d.strip_type)} / ${d.led_count} LEDs / ${escapeHtml(d.ip)}:${d.port}</span>
|
|
</div>
|
|
<button class="device-remove" onclick="removeDevice('${d.id}')" title="Remove device">×</button>
|
|
</div>`
|
|
).join("");
|
|
} catch (err) {
|
|
const list = document.getElementById("device-list");
|
|
list.innerHTML = `<span class="text-dim">error loading devices</span>`;
|
|
console.error("[devices]", err);
|
|
}
|
|
}
|
|
|
|
async function removeDevice(id) {
|
|
try {
|
|
await apiDelete(`/api/devices/${id}`);
|
|
await loadDevices();
|
|
} catch (err) {
|
|
console.error("[devices] remove failed:", err);
|
|
}
|
|
}
|
|
window.removeDevice = removeDevice;
|
|
|
|
function escapeHtml(str) {
|
|
const div = document.createElement("div");
|
|
div.appendChild(document.createTextNode(String(str)));
|
|
return div.innerHTML;
|
|
}
|
|
|
|
loadDevices();
|
|
|
|
document.getElementById("devices-manage-toggle").addEventListener("click", () => {
|
|
const panel = document.getElementById("devices-panel");
|
|
const btn = document.getElementById("devices-manage-toggle");
|
|
const active = panel.classList.toggle("manage-active");
|
|
btn.classList.toggle("active", active);
|
|
});
|
|
|
|
document.getElementById("add-device-form").addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
const form = e.target;
|
|
const data = {
|
|
name: form.elements["name"].value.trim(),
|
|
strip_type: form.strip_type.value,
|
|
led_count: parseInt(form.led_count.value, 10),
|
|
ip: form.ip.value.trim(),
|
|
port: parseInt(form.port.value, 10),
|
|
};
|
|
try {
|
|
await apiPost("/api/devices", data);
|
|
form.reset();
|
|
form.port.value = "21324";
|
|
await loadDevices();
|
|
} catch (err) {
|
|
console.error("[devices] add failed:", err);
|
|
}
|
|
});
|