Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Excel to PDF Converter</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .dropzone { | |
| border: 2px dashed #3b82f6; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #10b981; | |
| background-color: #f0f9ff; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| .file-item:hover { | |
| background-color: #f8fafc; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .animate-pulse { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-4xl"> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-blue-600 mb-2">Excel to PDF Converter</h1> | |
| <p class="text-gray-600">Convert your Excel spreadsheets to professional PDF documents in seconds</p> | |
| </header> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> | |
| <div class="p-6"> | |
| <div class="flex flex-col md:flex-row gap-6"> | |
| <div class="flex-1"> | |
| <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer h-full flex flex-col items-center justify-center"> | |
| <div class="text-blue-500 mb-4"> | |
| <i class="fas fa-file-excel text-5xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">Drag & Drop Excel File</h3> | |
| <p class="text-gray-500 mb-4">or click to browse your files</p> | |
| <input type="file" id="fileInput" class="hidden" accept=".xlsx, .xls, .csv"> | |
| <button id="browseBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200"> | |
| Select File | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="border-2 border-gray-200 rounded-lg p-8 text-center h-full flex flex-col items-center justify-center"> | |
| <div class="text-red-500 mb-4"> | |
| <i class="fas fa-file-pdf text-5xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">Your PDF Output</h3> | |
| <p class="text-gray-500 mb-4">Converted file will appear here</p> | |
| <button id="downloadBtn" class="bg-gray-200 text-gray-600 font-medium py-2 px-6 rounded-lg cursor-not-allowed opacity-50" disabled> | |
| Download PDF | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="fileInfo" class="border-t border-gray-200 p-6 hidden"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h3 class="font-medium text-gray-800">Selected File</h3> | |
| <button id="clearBtn" class="text-blue-600 hover:text-blue-800 text-sm font-medium">Clear</button> | |
| </div> | |
| <div id="fileDetails" class="flex items-center gap-4 p-3 bg-gray-50 rounded-lg"> | |
| <!-- File details will be inserted here --> | |
| </div> | |
| </div> | |
| <div id="progressSection" class="border-t border-gray-200 p-6 hidden"> | |
| <div class="flex items-center justify-between mb-3"> | |
| <h3 class="font-medium text-gray-800">Conversion Progress</h3> | |
| <span id="progressPercent" class="text-sm font-medium text-blue-600">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <p id="statusText" class="text-sm text-gray-500 mt-2">Ready to convert</p> | |
| </div> | |
| <div class="border-t border-gray-200 p-6"> | |
| <div class="flex flex-col sm:flex-row gap-3"> | |
| <button id="convertBtn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| <i class="fas fa-sync-alt mr-2"></i> Convert to PDF | |
| </button> | |
| <button id="settingsBtn" class="px-4 py-3 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition duration-200"> | |
| <i class="fas fa-cog"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8 hidden" id="settingsPanel"> | |
| <div class="p-6"> | |
| <h3 class="font-medium text-gray-800 mb-4">Conversion Settings</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Page Orientation</label> | |
| <div class="flex gap-4"> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" name="orientation" value="portrait" class="h-4 w-4 text-blue-600" checked> | |
| <span class="ml-2 text-gray-700">Portrait</span> | |
| </label> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" name="orientation" value="landscape" class="h-4 w-4 text-blue-600"> | |
| <span class="ml-2 text-gray-700">Landscape</span> | |
| </label> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Page Size</label> | |
| <select id="pageSize" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"> | |
| <option value="a4">A4</option> | |
| <option value="letter">Letter</option> | |
| <option value="legal">Legal</option> | |
| <option value="a3">A3</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="includeGrid" class="h-4 w-4 text-blue-600" checked> | |
| <span class="ml-2 text-sm text-gray-700">Include grid lines</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <div class="p-6"> | |
| <h3 class="font-medium text-gray-800 mb-4">Recent Conversions</h3> | |
| <div id="recentFiles" class="space-y-2"> | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-history text-3xl mb-2"></i> | |
| <p>Your recent conversions will appear here</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize jsPDF | |
| const { jsPDF } = window.jspdf; | |
| // DOM elements | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const browseBtn = document.getElementById('browseBtn'); | |
| const convertBtn = document.getElementById('convertBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const fileInfo = document.getElementById('fileInfo'); | |
| const fileDetails = document.getElementById('fileDetails'); | |
| const progressSection = document.getElementById('progressSection'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| const statusText = document.getElementById('statusText'); | |
| const settingsBtn = document.getElementById('settingsBtn'); | |
| const settingsPanel = document.getElementById('settingsPanel'); | |
| const recentFiles = document.getElementById('recentFiles'); | |
| // State variables | |
| let selectedFile = null; | |
| let pdfBlob = null; | |
| let recentConversions = JSON.parse(localStorage.getItem('recentConversions')) || []; | |
| // Initialize UI with recent conversions | |
| updateRecentConversions(); | |
| // Event listeners | |
| browseBtn.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileSelect); | |
| convertBtn.addEventListener('click', convertToPDF); | |
| downloadBtn.addEventListener('click', downloadPDF); | |
| clearBtn.addEventListener('click', clearSelection); | |
| settingsBtn.addEventListener('click', toggleSettings); | |
| // Drag and drop events | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, unhighlight, false); | |
| }); | |
| function highlight() { | |
| dropzone.classList.add('active'); | |
| } | |
| function unhighlight() { | |
| dropzone.classList.remove('active'); | |
| } | |
| dropzone.addEventListener('drop', handleDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const file = dt.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFile(file) { | |
| if (!file) return; | |
| // Check if file is Excel | |
| const validTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |
| 'application/vnd.ms-excel', | |
| 'text/csv']; | |
| if (!validTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls|csv)$/)) { | |
| showError('Please select a valid Excel file (.xlsx, .xls, .csv)'); | |
| return; | |
| } | |
| selectedFile = file; | |
| updateFileInfo(); | |
| convertBtn.disabled = false; | |
| } | |
| function updateFileInfo() { | |
| fileInfo.classList.remove('hidden'); | |
| const fileSize = (selectedFile.size / 1024).toFixed(2); | |
| const fileType = selectedFile.name.split('.').pop().toUpperCase(); | |
| fileDetails.innerHTML = ` | |
| <div class="bg-blue-100 p-3 rounded-lg text-blue-800"> | |
| <i class="fas fa-file-excel"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <p class="text-sm font-medium text-gray-900 truncate">${selectedFile.name}</p> | |
| <p class="text-xs text-gray-500">${fileSize} KB • ${fileType}</p> | |
| </div> | |
| `; | |
| } | |
| function clearSelection() { | |
| selectedFile = null; | |
| fileInfo.classList.add('hidden'); | |
| progressSection.classList.add('hidden'); | |
| convertBtn.disabled = true; | |
| downloadBtn.disabled = true; | |
| downloadBtn.classList.remove('bg-red-600', 'hover:bg-red-700'); | |
| downloadBtn.classList.add('bg-gray-200', 'text-gray-600'); | |
| fileInput.value = ''; | |
| } | |
| function toggleSettings() { | |
| settingsPanel.classList.toggle('hidden'); | |
| } | |
| function showError(message) { | |
| statusText.textContent = message; | |
| statusText.classList.remove('text-gray-500'); | |
| statusText.classList.add('text-red-500'); | |
| progressSection.classList.remove('hidden'); | |
| setTimeout(() => { | |
| statusText.textContent = 'Ready to convert'; | |
| statusText.classList.remove('text-red-500'); | |
| statusText.classList.add('text-gray-500'); | |
| progressSection.classList.add('hidden'); | |
| }, 3000); | |
| } | |
| function updateProgress(percent) { | |
| progressBar.style.width = `${percent}%`; | |
| progressPercent.textContent = `${percent}%`; | |
| if (percent < 30) { | |
| statusText.textContent = 'Processing data...'; | |
| } else if (percent < 70) { | |
| statusText.textContent = 'Formatting PDF...'; | |
| } else { | |
| statusText.textContent = 'Finalizing document...'; | |
| } | |
| } | |
| async function convertToPDF() { | |
| if (!selectedFile) return; | |
| progressSection.classList.remove('hidden'); | |
| convertBtn.disabled = true; | |
| convertBtn.innerHTML = '<i class="fas fa-spinner animate-spin mr-2"></i> Converting...'; | |
| try { | |
| // Simulate progress (in a real app, this would be actual progress) | |
| let progress = 0; | |
| const progressInterval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(progressInterval); | |
| } | |
| updateProgress(Math.min(progress, 100)); | |
| }, 200); | |
| // Read Excel file | |
| const data = await readFile(selectedFile); | |
| const workbook = XLSX.read(data, { type: 'array' }); | |
| // Get first sheet | |
| const firstSheetName = workbook.SheetNames[0]; | |
| const worksheet = workbook.Sheets[firstSheetName]; | |
| // Convert to JSON | |
| const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); | |
| // Get settings | |
| const orientation = document.querySelector('input[name="orientation"]:checked').value; | |
| const pageSize = document.getElementById('pageSize').value; | |
| const includeGrid = document.getElementById('includeGrid').checked; | |
| // Generate PDF | |
| await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate processing | |
| const doc = new jsPDF({ | |
| orientation: orientation, | |
| unit: 'mm', | |
| format: pageSize | |
| }); | |
| // Add title | |
| doc.setFontSize(18); | |
| doc.text(selectedFile.name.replace(/\.[^/.]+$/, ""), 14, 20); | |
| // Add date | |
| doc.setFontSize(10); | |
| doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 14, 27); | |
| // Add table | |
| doc.autoTable({ | |
| startY: 30, | |
| head: [jsonData[0]], | |
| body: jsonData.slice(1), | |
| theme: includeGrid ? 'grid' : 'striped', | |
| styles: { fontSize: 8 }, | |
| headStyles: { fillColor: [59, 130, 246] } | |
| }); | |
| // Generate PDF blob | |
| pdfBlob = doc.output('blob'); | |
| // Update UI | |
| statusText.textContent = 'Conversion complete!'; | |
| statusText.classList.remove('text-gray-500'); | |
| statusText.classList.add('text-green-500'); | |
| downloadBtn.disabled = false; | |
| downloadBtn.classList.remove('bg-gray-200', 'text-gray-600', 'cursor-not-allowed', 'opacity-50'); | |
| downloadBtn.classList.add('bg-red-600', 'hover:bg-red-700', 'text-white'); | |
| convertBtn.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Converted'; | |
| // Add to recent conversions | |
| addRecentConversion(selectedFile.name, new Date()); | |
| } catch (error) { | |
| console.error('Conversion error:', error); | |
| showError('Error during conversion. Please try again.'); | |
| convertBtn.disabled = false; | |
| convertBtn.innerHTML = '<i class="fas fa-sync-alt mr-2"></i> Convert to PDF'; | |
| } | |
| } | |
| function readFile(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => resolve(e.target.result); | |
| reader.onerror = (e) => reject(e); | |
| reader.readAsArrayBuffer(file); | |
| }); | |
| } | |
| function downloadPDF() { | |
| if (!pdfBlob) return; | |
| const url = URL.createObjectURL(pdfBlob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = selectedFile.name.replace(/\.[^/.]+$/, "") + '.pdf'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| function addRecentConversion(filename, date) { | |
| recentConversions.unshift({ | |
| filename: filename, | |
| date: date.toISOString(), | |
| timestamp: Date.now() | |
| }); | |
| // Keep only last 5 conversions | |
| if (recentConversions.length > 5) { | |
| recentConversions = recentConversions.slice(0, 5); | |
| } | |
| localStorage.setItem('recentConversions', JSON.stringify(recentConversions)); | |
| updateRecentConversions(); | |
| } | |
| function updateRecentConversions() { | |
| if (recentConversions.length === 0) { | |
| recentFiles.innerHTML = ` | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-history text-3xl mb-2"></i> | |
| <p>Your recent conversions will appear here</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| recentFiles.innerHTML = recentConversions.map(conv => ` | |
| <div class="file-item flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:shadow-sm transition duration-200"> | |
| <div class="flex items-center gap-3"> | |
| <div class="bg-blue-100 p-2 rounded-lg text-blue-800"> | |
| <i class="fas fa-file-pdf"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${conv.filename.replace(/\.[^/.]+$/, "")}.pdf</p> | |
| <p class="text-xs text-gray-500">${new Date(conv.date).toLocaleString()}</p> | |
| </div> | |
| </div> | |
| <button class="text-blue-600 hover:text-blue-800 p-1 rounded-full" onclick="downloadRecent('${conv.filename.replace(/\.[^/.]+$/, "")}.pdf', ${conv.timestamp})"> | |
| <i class="fas fa-download"></i> | |
| </button> | |
| </div> | |
| `).join(''); | |
| } | |
| // Global function for recent file download | |
| window.downloadRecent = function(filename, timestamp) { | |
| const conversion = recentConversions.find(c => c.timestamp === timestamp); | |
| if (!conversion) return; | |
| // In a real app, you would retrieve the actual PDF blob from storage | |
| // For this demo, we'll just create a dummy PDF | |
| const doc = new jsPDF(); | |
| doc.text(`This would be the PDF for ${filename}`, 10, 10); | |
| const blob = doc.output('blob'); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }; | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=3sangave/rutiksnagave" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |