From 2d954a4c4c83341aa3755237d11ccd324910934c Mon Sep 17 00:00:00 2001 From: Kosztyk <36381705+Kosztyk@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:27:34 +0200 Subject: [PATCH] Add files via upload --- public/results.js | 244 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 214 insertions(+), 30 deletions(-) diff --git a/public/results.js b/public/results.js index 60c07eb..23493c2 100644 --- a/public/results.js +++ b/public/results.js @@ -1,38 +1,222 @@ -const webroot = document.querySelector("meta[name='webroot']").content; -const jobId = window.location.pathname.split("/").pop(); -const main = document.querySelector("main"); -let progressElem = document.querySelector("progress"); +// public/results.js +// +// Handles live progress updates for /results/:jobId and Share via Erugo modal actions. +// IMPORTANT: /progress/:jobId re-renders the
content, so we must not keep stale +// element references. Use event delegation + (re)query elements when needed. -const refreshData = () => { - // console.log("Refreshing data...", progressElem.value, progressElem.max); - if (progressElem.value !== progressElem.max) { - fetch(`${webroot}/progress/${jobId}`, { - method: "POST", - }) - .then((res) => res.text()) - .then((html) => { - main.innerHTML = html; - }) - .catch((err) => console.log(err)); +(function () { + const webrootMeta = document.querySelector("meta[name='webroot']"); + const webroot = webrootMeta ? webrootMeta.content : ""; + const jobId = window.location.pathname.split("/").pop(); + const main = document.querySelector("main"); - setTimeout(refreshData, 1000); + // ----------------------------- + // Progress refresh + // ----------------------------- + async function refreshData() { + if (!main || !jobId) return; + + const progressElem = main.querySelector("progress"); + if (!progressElem) return; + + const max = Number(progressElem.getAttribute("max") || "0"); + const val = Number(progressElem.getAttribute("value") || "0"); + + // Only refresh while still processing + if (max > 0 && val >= max) return; + + try { + const res = await fetch(`${webroot}/progress/${jobId}`, { method: "POST", cache: "no-store" }); + const html = await res.text(); + main.innerHTML = html; + } catch (err) { + console.error("[ConvertX] progress refresh failed", err); + } } - progressElem = document.querySelector("progress"); -}; + // Poll every second while job is running + setInterval(refreshData, 1000); + // Run once immediately so the page updates without waiting 1s. + refreshData(); -refreshData(); + // ----------------------------- + // Share modal helpers + // ----------------------------- + function getEls() { + return { + modal: document.getElementById("cxShareModal"), + closeBtn: document.getElementById("cxShareClose"), + cancelBtn: document.getElementById("cxShareCancel"), + submitBtn: document.getElementById("cxShareSubmit"), + statusEl: document.getElementById("cxShareStatus"), -window.downloadAll = function () { - // Get all download links - const downloadLinks = document.querySelectorAll("tbody a[download]"); + emailEl: document.getElementById("cxShareEmail"), + nameEl: document.getElementById("cxShareName"), + descEl: document.getElementById("cxShareDescription"), - // Trigger download for each link - downloadLinks.forEach((link, index) => { - // We add a delay for each download to prevent them from starting at the same time - setTimeout(() => { - const event = new MouseEvent("click"); - link.dispatchEvent(event); - }, index * 300); + linkBlock: document.getElementById("cxShareLinkBlock"), + linkEl: document.getElementById("cxShareLink"), + copyBtn: document.getElementById("cxShareCopy"), + }; + } + + let currentJobId = null; + let currentFileName = null; + + function openModal(jobIdValue, fileNameValue) { + const els = getEls(); + if (!els.modal) return; + + currentJobId = jobIdValue; + currentFileName = fileNameValue; + + if (els.nameEl) els.nameEl.value = fileNameValue || ""; + if (els.emailEl) els.emailEl.value = ""; + if (els.descEl) els.descEl.value = ""; + + if (els.linkBlock) els.linkBlock.classList.add("hidden"); + if (els.linkEl) els.linkEl.value = ""; + if (els.statusEl) els.statusEl.textContent = ""; + + els.modal.classList.remove("hidden"); + els.modal.classList.add("flex"); + + if (els.emailEl) els.emailEl.focus(); + } + + function closeModal() { + const els = getEls(); + if (!els.modal) return; + + els.modal.classList.add("hidden"); + els.modal.classList.remove("flex"); + + currentJobId = null; + currentFileName = null; + } + + async function shareFile(jobIdValue, fileNameValue) { + const els = getEls(); + if (!els.submitBtn || !els.statusEl) return; + + const recipientEmail = (els.emailEl && els.emailEl.value ? els.emailEl.value.trim() : "") || undefined; + const shareName = (els.nameEl && els.nameEl.value ? els.nameEl.value.trim() : "") || undefined; + const description = (els.descEl && els.descEl.value ? els.descEl.value.trim() : "") || undefined; + + els.submitBtn.disabled = true; + els.submitBtn.setAttribute("aria-busy", "true"); + els.statusEl.textContent = "Sending..."; + + try { + const payload = { fileName: fileNameValue, recipientEmail, shareName, description }; + + const res = await fetch(`${webroot}/share-to-erugo/${jobIdValue}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + const text = await res.text(); + let json = null; + try { + json = text ? JSON.parse(text) : null; + } catch { + json = { raw: text }; + } + + if (!res.ok) { + console.error("[ConvertX] share-to-erugo failed", res.status, json); + els.statusEl.textContent = "Failed. See logs."; + return; + } + + const url = + (json && (json.share_url || json.share_link)) || + (json && json.data && json.data.url) || + (json && json.data && json.data.share && json.data.share.url) || + null; + + if (url && els.linkEl && els.linkBlock) { + els.linkEl.value = url; + els.linkBlock.classList.remove("hidden"); + } + + if (recipientEmail) { + els.statusEl.textContent = url ? "Sent. Link also shown below." : "Sent."; + } else { + els.statusEl.textContent = url ? "Created. Copy the link below." : "Created."; + } + } catch (err) { + console.error(err); + els.statusEl.textContent = "Failed. See logs."; + } finally { + els.submitBtn.disabled = false; + els.submitBtn.removeAttribute("aria-busy"); + } + } + + // ----------------------------- + // Global event handlers (delegated) + // ----------------------------- + document.addEventListener("click", async (event) => { + const target = event.target; + + // Share icon buttons inside the table + const shareBtn = target && target.closest ? target.closest("button[data-share='true']") : null; + if (shareBtn) { + const jobIdAttr = shareBtn.getAttribute("data-job-id"); + const fileNameAttr = shareBtn.getAttribute("data-file-name"); + if (!jobIdAttr || !fileNameAttr) return; + openModal(jobIdAttr, fileNameAttr); + return; + } + + // Modal close/cancel + if (target && target.id === "cxShareClose") { + closeModal(); + return; + } + if (target && target.id === "cxShareCancel") { + closeModal(); + return; + } + + // Click outside modal (on overlay) + const els = getEls(); + if (els.modal && target === els.modal) { + closeModal(); + return; + } + + // Submit + if (target && target.id === "cxShareSubmit") { + if (!currentJobId || !currentFileName) return; + await shareFile(currentJobId, currentFileName); + return; + } + + // Copy + if (target && target.id === "cxShareCopy") { + const e = getEls(); + if (!e.linkEl || !e.statusEl) return; + try { + await navigator.clipboard.writeText(e.linkEl.value); + e.statusEl.textContent = "Copied."; + } catch { + e.linkEl.focus(); + e.linkEl.select(); + e.statusEl.textContent = "Select + copy (Ctrl/Cmd+C)."; + } + return; + } }); -}; + + document.addEventListener("keydown", (event) => { + if (event.key !== "Escape") return; + const els = getEls(); + if (els.modal && !els.modal.classList.contains("hidden")) { + closeModal(); + } + }); +})(); +