Files
hudra/controls.js
T
2026-07-21 16:10:41 +10:00

136 lines
6.3 KiB
JavaScript

const params = new URLSearchParams(location.search);
const sessionId = params.get("session");
const pageTitle = document.getElementById("page-title");
const slideCounter = document.getElementById("slide-counter");
const previousButton = document.getElementById("previous");
const nextButton = document.getElementById("next");
const firstButton = document.getElementById("first");
const lastButton = document.getElementById("last");
const videoButton = document.getElementById("video");
const audienceButton = document.getElementById("audience");
const exitButton = document.getElementById("exit");
const currentPreview = document.getElementById("current-preview");
const nextPreview = document.getElementById("next-preview");
const currentTime = document.getElementById("current-time");
const elapsedTime = document.getElementById("elapsed-time");
const screenStatus = document.getElementById("screen-status");
const zoomOutButton = document.getElementById("zoom-out");
const zoomInButton = document.getElementById("zoom-in");
const zoomValue = document.getElementById("zoom-value");
const scrollUpButton = document.getElementById("scroll-up");
const scrollDownButton = document.getElementById("scroll-down");
const resetViewButton = document.getElementById("reset-view");
const viewStatus = document.getElementById("view-status");
let current = 0;
let count = 0;
let audienceHidden = false;
let audienceZoom = 100;
let audienceScrollY = 0;
const startedAt = Date.now();
function send(command, value) {
if (!sessionId) return;
chrome.runtime.sendMessage({
type: "QLEARN_PRESENTER_CONTROL_COMMAND",
sessionId, command, value
}).catch(() => {});
}
function setPreview(frame, srcdoc, emptyLabel) {
if (srcdoc) {
frame.srcdoc = srcdoc;
frame.style.opacity = "1";
} else {
frame.srcdoc = `<!doctype html><html><body style="margin:0;display:grid;place-items:center;height:100vh;background:#111722;color:#9ca8ba;font:600 28px system-ui">${emptyLabel}</body></html>`;
frame.style.opacity = ".72";
}
}
function render(state) {
if (!state) return;
current = Number(state.current) || 0;
count = Number(state.count) || 0;
pageTitle.textContent = state.pageTitle || "Hudra";
audienceHidden = Boolean(state.audienceHidden);
audienceZoom = Number(state.audienceZoom) || 100;
audienceScrollY = Number(state.audienceScrollY) || 0;
zoomValue.textContent = `${audienceZoom}%`;
const scrollLabel = Math.abs(audienceScrollY) < 4 ? "top" : `${Math.round(audienceScrollY)} px`;
viewStatus.textContent = `${audienceZoom === 100 ? "Fit" : `Scaled ${audienceZoom}%`} · ${scrollLabel}`;
audienceButton.textContent = audienceHidden ? "Restore audience" : "Hide audience";
screenStatus.textContent = audienceHidden ? "Audience screen hidden" : "Audience screen active";
slideCounter.textContent = count > 0 ? `Slide ${current + 1} of ${count}` : "Preparing slides…";
previousButton.disabled = current <= 0 || count <= 0;
firstButton.disabled = current <= 0 || count <= 0;
nextButton.disabled = count <= 0 || current >= count - 1;
lastButton.disabled = count <= 0 || current >= count - 1;
setPreview(currentPreview, state.currentPreview, "Current slide");
setPreview(nextPreview, state.nextPreview, current >= count - 1 ? "End of presentation" : "Next slide");
}
function updateTimes() {
const now = new Date();
currentTime.textContent = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
const elapsed = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
const minutes = String(Math.floor(elapsed / 60)).padStart(2, "0");
const seconds = String(elapsed % 60).padStart(2, "0");
elapsedTime.textContent = `${minutes}:${seconds}`;
}
setInterval(updateTimes, 1000);
updateTimes();
previousButton.addEventListener("click", () => send("previous"));
nextButton.addEventListener("click", () => send("next"));
firstButton.addEventListener("click", () => send("first"));
lastButton.addEventListener("click", () => send("last"));
videoButton.addEventListener("click", () => send("toggleVideo"));
audienceButton.addEventListener("click", () => send("toggleAudienceHidden"));
exitButton.addEventListener("click", () => send("exit"));
zoomOutButton.addEventListener("click", () => send("zoomOut"));
zoomInButton.addEventListener("click", () => send("zoomIn"));
resetViewButton.addEventListener("click", () => send("resetView"));
function addHoldScroll(button, command) {
let timer = null;
const stop = () => { if (timer) clearInterval(timer); timer = null; };
button.addEventListener("pointerdown", (event) => { event.preventDefault(); send(command); timer = setInterval(() => send(command), 140); });
["pointerup", "pointercancel", "pointerleave"].forEach((name) => button.addEventListener(name, stop));
}
addHoldScroll(scrollUpButton, "scrollUp");
addHoldScroll(scrollDownButton, "scrollDown");
currentPreview.addEventListener("wheel", (event) => {
event.preventDefault();
if (event.shiftKey) send(event.deltaY < 0 ? "zoomIn" : "zoomOut");
else send("scrollBy", event.deltaY);
}, { passive: false });
currentPreview.addEventListener("dblclick", () => send("resetView"));
chrome.runtime.onMessage.addListener((message) => {
if (message?.type === "QLEARN_PRESENTER_STATE_UPDATE" && message.sessionId === sessionId) render(message);
});
function requestSession(attempt = 0) {
chrome.runtime.sendMessage({ type: "QLEARN_PRESENTER_GET_SESSION", sessionId })
.then((state) => {
if (state) render(state);
else if (attempt < 20) setTimeout(() => requestSession(attempt + 1), 250);
})
.catch(() => {
if (attempt < 20) setTimeout(() => requestSession(attempt + 1), 250);
});
}
requestSession();
window.addEventListener("keydown", (event) => {
if (event.key.toLowerCase() === "b") { event.preventDefault(); if (!event.repeat) send("toggleAudienceHidden"); return; }
if (event.key === "Tab") { event.preventDefault(); if (!event.repeat) send("toggleVideo"); return; }
if (["ArrowRight", "ArrowDown", "PageDown", " ", "Enter"].includes(event.key)) { event.preventDefault(); send("next"); return; }
if (["ArrowLeft", "ArrowUp", "PageUp", "Backspace"].includes(event.key)) { event.preventDefault(); send("previous"); return; }
if (event.key === "Home") { event.preventDefault(); send("first"); return; }
if (event.key === "End") { event.preventDefault(); send("last"); return; }
if (event.key === "Escape") { event.preventDefault(); send("exit"); }
});