118 lines
4.3 KiB
JavaScript
118 lines
4.3 KiB
JavaScript
/**
|
||
* Bluetooth / USB HID numpad shortcuts (browser focus required).
|
||
*
|
||
* Numpad1–9,0 → zone 1–10 (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));
|
||
});
|
||
})();
|