let selectedFiles = []; let completedFiles = []; // Constants const MAX_AGE_DAYS = 2; const MAX_AGE_MS = MAX_AGE_DAYS * 24 * 60 * 60 * 1000; // Initialize the application document.addEventListener('DOMContentLoaded', function () { cleanupOrphanedStorageEntries(); setupEventListeners(); }); /** * Remove localStorage entries older than MAX_AGE_DAYS * This prevents orphaned entries from accumulating */ function cleanupOrphanedStorageEntries() { try { const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}'); const now = Date.now(); let cleaned = false; for (const id in storageData) { if (now - storageData[id].timestamp > MAX_AGE_MS) { delete storageData[id]; cleaned = true; } } if (cleaned) { localStorage.setItem('uploadedFileData', JSON.stringify(storageData)); console.log('Cleaned orphaned localStorage entries'); } // Also clean legacy format if exists localStorage.removeItem('uploadedFileIds'); } catch (e) { console.warn('Failed to cleanup localStorage:', e); } } function setupEventListeners() { const uploadSection = document.getElementById('upload-section'); const fileInput = document.getElementById('file-input'); const processButton = document.getElementById('process-button'); const clearButton = document.getElementById('clear-button'); const downloadButton = document.getElementById('download-button'); // File upload events uploadSection.addEventListener('click', () => fileInput.click()); uploadSection.addEventListener('dragover', handleDragOver); uploadSection.addEventListener('dragleave', handleDragLeave); uploadSection.addEventListener('drop', handleDrop); fileInput.addEventListener('change', handleFileSelect); // Button events processButton.addEventListener('click', processButtonFn); clearButton.addEventListener('click', clearAllFiles); downloadButton.addEventListener('click', downloadAllFiles); } function showMessage(message, type = 'error') { const errorMsg = document.getElementById('error-message'); const successMsg = document.getElementById('success-message'); // Hide both messages first errorMsg.style.display = 'none'; successMsg.style.display = 'none'; if (type === 'error') { errorMsg.textContent = message; errorMsg.style.display = 'block'; } else { successMsg.textContent = message; successMsg.style.display = 'block'; } // Auto-hide after 5 seconds setTimeout(() => { errorMsg.style.display = 'none'; successMsg.style.display = 'none'; }, 5000); } function handleDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('dragover'); } function handleDragLeave(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); } function handleDrop(e) { e.preventDefault(); const uploadSection = e.currentTarget; uploadSection.classList.remove('dragover'); const files = Array.from(e.dataTransfer.files); processFiles(files); } function handleFileSelect(e) { const files = Array.from(e.target.files); processFiles(files); } function generateUniqueId(file, length = 12) { let ext = "." + file.name.split(".")[file.name.split(".").length - 1] const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext; } /** * Save file ID with timestamp to localStorage */ function saveFileIdToStorage(id) { const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}'); if (!storageData[id]) { storageData[id] = { timestamp: Date.now() }; localStorage.setItem('uploadedFileData', JSON.stringify(storageData)); } } /** * Remove file ID from localStorage */ function removeFileIdFromStorage(id) { const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}'); delete storageData[id]; localStorage.setItem('uploadedFileData', JSON.stringify(storageData)); } function processFiles(files) { files.forEach((file, index) => { const id = generateUniqueId(file); // generate unique ID const fileData = { id: id, file: file, name: file.name, size: formatFileSize(file.size), status: 'ready', preview: null }; // Store file ID for potential server deletion later saveFileIdToStorage(id); // Create preview for images const reader = new FileReader(); reader.onload = (e) => { fileData.preview = e.target.result; updateFileCard(fileData.id); }; reader.readAsDataURL(file); selectedFiles.push(fileData); }); updateUI(); } function updateUI() { const filesPreview = document.getElementById('files-preview'); const processButton = document.getElementById('process-button'); if (selectedFiles.length > 0) { filesPreview.style.display = 'block'; processButton.disabled = false; renderFileCards(); } else { filesPreview.style.display = 'none'; processButton.disabled = true; } } function renderFileCards() { const filesGrid = document.getElementById('files-grid'); filesGrid.innerHTML = selectedFiles.map(file => { // Find completed file data for download URL const completedFile = completedFiles.find(cf => cf.id === file.id); // Check if metadata info exists and has content const hasMetadata = file.other_info && Object.keys(file.other_info).length > 0; // Calculate button positions based on page type const isMetadataPage = window.location.pathname == '/image/remove_metadata'; const downloadTop = isMetadataPage ? '70px' : '40px'; const viewTop = isMetadataPage ? '100px' : '70px'; return `
${isMetadataPage && file.status === 'completed' ? `` : ``} ${file.status === 'error' && file.errorMessage ? `` : ``} ${file.status === 'completed' && completedFile ? `` : ``} ${file.status === 'completed' && completedFile ? `` : ``}
${file.preview ? `${file.name}` : '🖼️'}

${file.name}

${file.size}

${getStatusText(file.status)}
`; }).join(''); } function updateFileCard(fileId) { const file = selectedFiles.find(f => f.id === fileId); if (!file) return; const card = document.getElementById(`file-${fileId}`); if (!card) return; const preview = card.querySelector('.file-preview'); if (file.preview && preview) { preview.innerHTML = `${file.name}`; } } async function processButtonFn() { if (selectedFiles.length === 0) return; const progressSection = document.getElementById('progress-section'); const progressBar = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); const processButton = document.getElementById('process-button'); // Show progress section progressSection.style.display = 'block'; processButton.disabled = true; const old_message = processButton.innerHTML; processButton.innerHTML = '🔄 Processing...'; const settings = getSettingsData(); completedFiles = []; for (let i = 0; i < selectedFiles.length; i++) { const file = selectedFiles[i]; // Update file status file.status = 'uploading'; renderFileCards(); try { // Upload and process the file await upload(file); file.status = 'processing'; renderFileCards(); const processedFile = await secret_sauce(file, settings); file.status = 'completed'; file.other_info = processedFile.other_info; renderFileCards(); completedFiles.push(processedFile); } catch (error) { console.error('Processing error:', error); file.status = 'error'; file.errorMessage = error.message; showMessage(`Error processing ${file.name}: ${error.message}`, 'error'); } // Update progress const progress = Math.round(((i + 1) / selectedFiles.length) * 100); progressBar.style.width = progress + '%'; progressText.textContent = `${progress}% complete (${i + 1}/${selectedFiles.length})`; renderFileCards(); } // Completion processButton.disabled = false; processButton.innerHTML = old_message; const successfulProcessing = completedFiles.length; if (successfulProcessing > 0) { document.getElementById('download-button').style.display = 'inline-flex'; showMessage(`Successfully processed ${successfulProcessing} image(s)!`, 'success'); } setTimeout(() => { progressSection.style.display = 'none'; }, 1000); } async function secret_sauce(file, settings) { const formData = new FormData(); formData.append('id', file.id); for (let key of Object.keys(settings)) { if (settings[key]) { formData.append('to_format', settings[key]); } } try { const response = await fetch(secret_sauce_url(), { method: 'POST', body: formData }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `HTTP error! status: ${response.status}`); } const result = await response.json(); return { id: file.id, originalName: file.name, processedName: result.new_filename, url: "/image/download?id=" + result.new_filename, other_info: result.other_info }; } catch (error) { throw new Error(`Failed to process ${file.name}: ${error.message}`); } } function downloadAllFiles() { if (completedFiles.length === 0) return; // Download each completedFiles file completedFiles.forEach((file, index) => { setTimeout(() => { const link = document.createElement('a'); link.href = file.url; link.download = file.processedName; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, index * 100); }); } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function removeFile(fileId) { selectedFiles = selectedFiles.filter(file => file.id !== fileId); completedFiles = completedFiles.filter(file => file.id !== fileId); removeFileIdFromStorage(fileId); updateUI(); if (selectedFiles.length === 0) { document.getElementById('download-button').style.display = 'none'; } } async function clearAllFiles() { const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}'); const fileIds = Object.keys(storageData); if (fileIds.length > 0) { try { await fetch('/image/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: fileIds }) }); } catch (err) { console.error('Error sending delete request:', err); } } // Clear everything locally selectedFiles = []; completedFiles = []; updateUI(); document.getElementById('download-button').style.display = 'none'; document.getElementById('progress-section').style.display = 'none'; localStorage.removeItem('uploadedFileData'); } async function upload(file) { const formData = new FormData(); formData.append('image', file.file); formData.append('id', file.id); try { const response = await fetch('/image/upload', { method: 'POST', body: formData }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `HTTP error! status: ${response.status}`); } return true; } catch (error) { throw new Error(`Failed to upload ${file.name}: ${error.message}`); } } function getSettingsData() { return { to_format: document.getElementById('output-format')?.value }; } function getStatusText(status) { switch (status) { case 'ready': return 'Ready'; case 'uploading': return 'Uploading...'; case 'processing': return window.location.pathname == '/image/remove_metadata' ? 'Removing metadata...' : 'Converting...'; case 'completed': return 'Processed'; case 'error': return 'Error'; default: return 'Unknown'; } } function showMetadata(fileId) { const file = selectedFiles.find(f => f.id === fileId); const contentEl = document.getElementById('metadata-content'); if (file && file.other_info) { contentEl.textContent = JSON.stringify(file.other_info, null, 2); document.getElementById('metadata-modal').style.display = 'block'; document.getElementById('modal-backdrop').style.display = 'block'; } else { contentEl.textContent = 'No metadata removed or unavailable.'; document.getElementById('metadata-modal').style.display = 'block'; document.getElementById('modal-backdrop').style.display = 'block'; } } function closeMetadataModal() { document.getElementById('metadata-modal').style.display = 'none'; document.getElementById('modal-backdrop').style.display = 'none'; } /** * Show error message for a file in a modal/alert */ function showError(fileId) { const file = selectedFiles.find(f => f.id === fileId); if (file && file.errorMessage) { // Use showMessage which displays at the top of the page showMessage(`Error: ${file.errorMessage}`, 'error'); } else { showMessage('Unknown error occurred', 'error'); } } /** * Download a single processed file */ function downloadFile(fileId) { const completedFile = completedFiles.find(f => f.id === fileId); if (completedFile) { const link = document.createElement('a'); link.href = completedFile.url; link.download = completedFile.processedName; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } /** * View the processed image in a modal popup */ function viewImage(fileId) { const completedFile = completedFiles.find(f => f.id === fileId); if (completedFile) { // Create modal if it doesn't exist let modal = document.getElementById('image-view-modal'); let backdrop = document.getElementById('image-view-backdrop'); if (!modal) { // Create backdrop backdrop = document.createElement('div'); backdrop.id = 'image-view-backdrop'; backdrop.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 9998; display: none;'; backdrop.onclick = closeImageModal; document.body.appendChild(backdrop); // Create modal modal = document.createElement('div'); modal.id = 'image-view-modal'; modal.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; display: none; max-width: 90%; max-height: 90%; background: white; border-radius: 10px; padding: 10px; box-shadow: 0 0 30px rgba(0,0,0,0.5);'; modal.innerHTML = ` `; document.body.appendChild(modal); } // Show modal with image document.getElementById('image-view-content').src = completedFile.url; modal.style.display = 'block'; backdrop.style.display = 'block'; } } /** * Close image view modal */ function closeImageModal() { const modal = document.getElementById('image-view-modal'); const backdrop = document.getElementById('image-view-backdrop'); if (modal) modal.style.display = 'none'; if (backdrop) backdrop.style.display = 'none'; }