c03f17488c
CI / lint-and-test (push) Failing after 1m18s
Replace Syncthing as the product upload path with a share-target app and POST /api/jobs. Web UI live-polls job progress. Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
4.2 KiB
JavaScript
161 lines
4.2 KiB
JavaScript
/* live.f12.rocks — live poll + lightbox gallery. No framework. */
|
|
(function () {
|
|
"use strict";
|
|
|
|
var POLL_MS = 4000;
|
|
|
|
function $(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/* --- live reload while jobs run --- */
|
|
|
|
function fingerprint(payload) {
|
|
if (!payload) return "";
|
|
if (payload.job_id) {
|
|
return [
|
|
payload.status,
|
|
payload.stage,
|
|
payload.updated_at,
|
|
payload.variant_count,
|
|
(payload.intermediates || []).length,
|
|
payload.error || "",
|
|
].join("|");
|
|
}
|
|
var jobs = payload.jobs || [];
|
|
return jobs
|
|
.map(function (j) {
|
|
return [j.job_id, j.status, j.updated_at, j.variant_count, j.stage].join(":");
|
|
})
|
|
.join(",");
|
|
}
|
|
|
|
function startLivePoll() {
|
|
if (!document.body.hasAttribute("data-live")) return;
|
|
var jobId = document.body.getAttribute("data-job-id");
|
|
var url = jobId ? "/api/jobs/" + encodeURIComponent(jobId) : "/api/jobs";
|
|
var last = "";
|
|
var first = true;
|
|
|
|
function tick() {
|
|
fetch(url, { headers: { Accept: "application/json" } })
|
|
.then(function (r) {
|
|
if (!r.ok) throw new Error("poll " + r.status);
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
var next = fingerprint(data);
|
|
if (first) {
|
|
last = next;
|
|
first = false;
|
|
return;
|
|
}
|
|
if (next !== last) {
|
|
window.location.reload();
|
|
}
|
|
})
|
|
.catch(function () {
|
|
/* venue wifi: try again next tick */
|
|
});
|
|
}
|
|
|
|
tick();
|
|
setInterval(tick, POLL_MS);
|
|
}
|
|
|
|
/* --- lightbox with prev/next --- */
|
|
|
|
function startLightbox() {
|
|
var box = $("lightbox");
|
|
var img = $("lightbox-img");
|
|
var caption = $("lightbox-caption");
|
|
if (!box || !img) return;
|
|
|
|
var items = [];
|
|
var index = 0;
|
|
|
|
function collect() {
|
|
items = Array.prototype.slice
|
|
.call(document.querySelectorAll("img.lightboxable"))
|
|
.map(function (el) {
|
|
return {
|
|
src: el.getAttribute("data-full-src") || el.currentSrc || el.src,
|
|
alt: el.alt || "",
|
|
};
|
|
});
|
|
}
|
|
|
|
function show(i) {
|
|
if (!items.length) return;
|
|
index = (i + items.length) % items.length;
|
|
img.src = items[index].src;
|
|
img.alt = items[index].alt;
|
|
if (caption) caption.textContent = items[index].alt;
|
|
box.hidden = false;
|
|
document.body.classList.add("lightbox-open");
|
|
}
|
|
|
|
function close() {
|
|
box.hidden = true;
|
|
img.removeAttribute("src");
|
|
document.body.classList.remove("lightbox-open");
|
|
}
|
|
|
|
document.addEventListener("click", function (e) {
|
|
var target = e.target;
|
|
if (!(target instanceof HTMLImageElement)) return;
|
|
if (!target.classList.contains("lightboxable")) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
collect();
|
|
var full = target.getAttribute("data-full-src") || target.currentSrc || target.src;
|
|
var found = items.findIndex(function (it) {
|
|
return it.src === full;
|
|
});
|
|
show(found >= 0 ? found : 0);
|
|
});
|
|
|
|
box.addEventListener("click", function (e) {
|
|
if (e.target === box || (e.target && e.target.classList.contains("lightbox-close"))) {
|
|
close();
|
|
return;
|
|
}
|
|
if (e.target && e.target.classList.contains("lightbox-next")) {
|
|
show(index + 1);
|
|
}
|
|
if (e.target && e.target.classList.contains("lightbox-prev")) {
|
|
show(index - 1);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
if (box.hidden) return;
|
|
if (e.key === "Escape") close();
|
|
if (e.key === "ArrowRight") show(index + 1);
|
|
if (e.key === "ArrowLeft") show(index - 1);
|
|
});
|
|
|
|
var startX = 0;
|
|
box.addEventListener(
|
|
"touchstart",
|
|
function (e) {
|
|
if (e.changedTouches && e.changedTouches[0]) startX = e.changedTouches[0].screenX;
|
|
},
|
|
{ passive: true }
|
|
);
|
|
box.addEventListener(
|
|
"touchend",
|
|
function (e) {
|
|
if (!e.changedTouches || !e.changedTouches[0]) return;
|
|
var dx = e.changedTouches[0].screenX - startX;
|
|
if (Math.abs(dx) < 40) return;
|
|
show(index + (dx < 0 ? 1 : -1));
|
|
},
|
|
{ passive: true }
|
|
);
|
|
}
|
|
|
|
startLivePoll();
|
|
startLightbox();
|
|
})();
|