Files
led-controller/src/static/numpad.js

118 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Bluetooth / USB HID numpad shortcuts (browser focus required).
*
* Numpad19,0 → zone 110 (visible zone list order)
* NumpadEnter → sequence beat sync (step), same as S
* NumpadDecimal → sequence beat sync (pass), same as Shift+S
* NumpadMultiply → reset audio detector
* NumpadAdd → brightness +16
* NumpadSubtract → brightness 16
* NumpadDivide → stop zone sequence playback
*/
(() => {
const BRIGHTNESS_STEP = 16;
function isTypingTarget(target) {
if (!target || typeof target !== "object") return false;
const tag = String(target.tagName || "").toLowerCase();
return tag === "input" || tag === "textarea" || tag === "select" || target.isContentEditable;
}
function zoneIdsInListOrder() {
return [...document.querySelectorAll("#zones-list .zone-button[data-zone-id]")]
.map((el) => el.getAttribute("data-zone-id"))
.filter((id) => id != null && id !== "");
}
async function selectZoneByListIndex(oneBased) {
const order = zoneIdsInListOrder();
if (oneBased < 1 || oneBased > order.length) return;
const zoneId = order[oneBased - 1];
if (window.tabsManager && typeof window.tabsManager.selectZone === "function") {
await window.tabsManager.selectZone(zoneId);
} else if (typeof selectZone === "function") {
await selectZone(zoneId);
}
}
async function syncSequenceBeatPhase(mode) {
const res = await fetch("/sequences/sync-phase", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({ mode: mode || "step" }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || `Sync failed (${res.status})`);
}
}
async function resetAudioTracking() {
const res = await fetch("/api/audio/reset", {
method: "POST",
headers: { Accept: "application/json" },
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || `Reset failed (${res.status})`);
}
}
function adjustZoneBrightness(delta) {
const zoneId =
(window.tabsManager && typeof window.tabsManager.getCurrentTabId === "function"
? window.tabsManager.getCurrentTabId()
: null) ||
(window.tabsManager && typeof window.tabsManager.getCurrentZoneId === "function"
? window.tabsManager.getCurrentZoneId()
: null);
if (!zoneId) return;
const slider =
document.getElementById("header-brightness-slider") ||
document.getElementById("menu-brightness-slider");
if (!slider) return;
const cur = parseInt(slider.value, 10);
const base = Number.isFinite(cur) ? cur : 127;
const next = Math.max(0, Math.min(255, base + delta));
if (String(slider.value) === String(next)) return;
slider.value = String(next);
slider.dispatchEvent(new Event("input", { bubbles: true }));
}
async function stopSequencePlayback() {
if (typeof window.stopZoneSequencePlayback === "function") {
await window.stopZoneSequencePlayback(true);
}
}
/** @type {Record<string, () => void | Promise<void>>} */
const actions = {
NumpadEnter: () => syncSequenceBeatPhase("step"),
NumpadDecimal: () => syncSequenceBeatPhase("pass"),
NumpadMultiply: () => resetAudioTracking(),
NumpadAdd: () => adjustZoneBrightness(BRIGHTNESS_STEP),
NumpadSubtract: () => adjustZoneBrightness(-BRIGHTNESS_STEP),
NumpadDivide: () => stopSequencePlayback(),
Numpad1: () => selectZoneByListIndex(1),
Numpad2: () => selectZoneByListIndex(2),
Numpad3: () => selectZoneByListIndex(3),
Numpad4: () => selectZoneByListIndex(4),
Numpad5: () => selectZoneByListIndex(5),
Numpad6: () => selectZoneByListIndex(6),
Numpad7: () => selectZoneByListIndex(7),
Numpad8: () => selectZoneByListIndex(8),
Numpad9: () => selectZoneByListIndex(9),
Numpad0: () => selectZoneByListIndex(10),
};
document.addEventListener("keydown", (ev) => {
if (ev.defaultPrevented || ev.repeat || isTypingTarget(ev.target)) return;
const code = ev.code;
if (!code || !code.startsWith("Numpad")) return;
const action = actions[code];
if (!action) return;
ev.preventDefault();
Promise.resolve(action()).catch((e) => console.warn("numpad shortcut failed:", e));
});
})();