${item.fileName}
`; card.querySelector("button").addEventListener("click", () => { triggerDownload(item.blob, item.fileName); }); ui.previewGrid.appendChild(card); }; const convertPdf = async () => { clearError(); if (!state.file || !state.pdfDoc) { showError("Please upload a valid PDF first."); return; } clearPreviews(); const quality = Number(ui.qualitySelect.value); const total = state.pdfDoc.numPages; const base = sanitizeFileBase(state.file.name); try { setLoading(true, "Starting conversion..."); for (let i = 1; i <= total; i += 1) { setProgress(i, total); const page = await state.pdfDoc.getPage(i); const blob = await renderPageToBlob(page, quality); const fileName = `${base}_page_${String(i).padStart(2, "0")}.jpg`; const objectUrl = URL.createObjectURL(blob); const item = { pageNumber: i, blob, fileName, objectUrl }; state.pages.push(item); appendPreviewCard(item); } setLoading(false); ui.pageInfo.textContent = `Conversion complete: ${total} JPG file${total > 1 ? "s" : ""}`; updateButtons(); // For single-page PDFs, allow immediate direct download. if (state.pages.length === 1) { triggerDownload(state.pages[0].blob, state.pages[0].fileName); } } catch (error) { setLoading(false); showError(`Conversion failed: ${error.message || "Unknown error"}`); } }; const downloadZip = async () => { clearError(); if (state.pages.length < 2) { showError("ZIP download is available for multi-page PDFs only."); return; } try { setLoading(true, "Creating ZIP archive..."); const zip = new JSZip(); state.pages.forEach((item) => zip.file(item.fileName, item.blob)); const zipBlob = await zip.generateAsync( { type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } }, (metadata) => { const percent = Math.round(metadata.percent); ui.progressWrap.classList.add("show"); ui.progressBar.style.width = `${percent}%`; ui.statusText.textContent = `Building ZIP... ${percent}%`; } ); setLoading(false); const zipName = `${sanitizeFileBase(state.file.name)}_jpg_pages.zip`; triggerDownload(zipBlob, zipName); } catch (error) { setLoading(false); showError(`ZIP creation failed: ${error.message || "Unknown error"}`); } }; const resetAll = () => { clearError(); setLoading(false); clearPreviews(); state.file = null; state.pdfDoc = null; ui.pdfInput.value = ""; setFileInfo(null); updateButtons(); }; // Event bindings ui.themeToggle.addEventListener("click", () => { const current = ui.body.getAttribute("data-theme") === "dark" ? "dark" : "light"; setTheme(current === "dark" ? "light" : "dark"); }); ui.selectFileBtn.addEventListener("click", () => ui.pdfInput.click()); ui.pdfInput.addEventListener("change", (event) => { const file = event.target.files?.[0]; handleSelectedFile(file); }); ui.convertBtn.addEventListener("click", convertPdf); ui.downloadZipBtn.addEventListener("click", downloadZip); ui.resetBtn.addEventListener("click", resetAll); // Drag and drop support + visual feedback. ["dragenter", "dragover"].forEach((eventName) => { ui.dropZone.addEventListener(eventName, (event) => { event.preventDefault(); event.stopPropagation(); ui.dropZone.classList.add("drag-over"); }); }); ["dragleave", "drop"].forEach((eventName) => { ui.dropZone.addEventListener(eventName, (event) => { event.preventDefault(); event.stopPropagation(); ui.dropZone.classList.remove("drag-over"); }); }); ui.dropZone.addEventListener("drop", (event) => { const file = event.dataTransfer?.files?.[0]; handleSelectedFile(file); }); // Prevent accidental browser navigation when dropping files outside the drop-zone. document.addEventListener("dragover", (event) => event.preventDefault()); document.addEventListener("drop", (event) => event.preventDefault()); initTheme(); updateButtons();