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 = `${emptyLabel}`; 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"); } });