Initial release - Hudra 0.9.1
This commit is contained in:
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
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"); }
|
||||
});
|
||||
Reference in New Issue
Block a user