// Gestión de tema (light/dark) function initTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; if (savedTheme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } function toggleTheme() { const isDark = document.documentElement.classList.contains('dark'); if (isDark) { document.documentElement.classList.remove('dark'); localStorage.setItem('theme', 'light'); } else { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark'); } } // Gestión de sesión de usuario function getUserSession() { let session = localStorage.getItem('userSession'); if (!session) { session = 'user_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); localStorage.setItem('userSession', session); } return session; } // Notificación tipo toast function showToast(message, type = 'success') { const toast = document.getElementById('toast'); const toastMessage = document.getElementById('toastMessage'); toastMessage.textContent = message; toast.classList.remove('bg-green-500', 'bg-red-500', 'bg-blue-500'); if (type === 'success') { toast.classList.add('bg-green-500'); } else if (type === 'error') { toast.classList.add('bg-red-500'); } else if (type === 'info') { toast.classList.add('bg-blue-500'); } toast.classList.remove('translate-x-full'); setTimeout(() => { toast.classList.add('translate-x-full'); }, 3000); } // Tooltip personalizado let tooltipTimeout; function showCustomTooltip(target, text) { let tooltip = document.getElementById('custom-tooltip'); if (!tooltip) { tooltip = document.createElement('div'); tooltip.id = 'custom-tooltip'; tooltip.className = 'custom-tooltip'; document.body.appendChild(tooltip); } tooltip.textContent = text; const rect = target.getBoundingClientRect(); tooltip.style.top = (window.scrollY + rect.bottom + 4) + 'px'; tooltip.style.left = (window.scrollX + rect.left) + 'px'; tooltip.classList.add('visible'); clearTimeout(tooltipTimeout); } function hideCustomTooltip() { const tooltip = document.getElementById('custom-tooltip'); if (tooltip) { tooltip.classList.remove('visible'); tooltipTimeout = setTimeout(() => { tooltip.remove(); }, 200); } } // Formateo de tamaño de archivo 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]; } // Formateo de fecha function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); } // Tiempo restante function getTimeRemaining(expirationDate) { const now = new Date(); const expiry = new Date(expirationDate); const diff = expiry - now; if (diff <= 0) return 'Expired'; const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (hours > 0) { return `${hours}h ${minutes}m remaining`; } else { return `${minutes}m remaining`; } } // Progreso de subida let uploadStartTime = 0; let uploadedBytes = 0; function updateProgress(loaded, total, isDone = false) { const percent = isDone ? 100 : Math.round((loaded / total) * 100); const progressBar = document.getElementById('progressBar'); const progressPercent = document.getElementById('progressPercent'); const uploadSpeed = document.getElementById('uploadSpeed'); const timeRemaining = document.getElementById('timeRemaining'); progressBar.style.width = percent + '%'; progressPercent.textContent = percent + '%'; if (isDone) { uploadSpeed.textContent = ''; timeRemaining.textContent = ''; return; } const elapsed = (Date.now() - uploadStartTime) / 1000; const speed = loaded / elapsed; const speedMB = (speed / (1024 * 1024)).toFixed(2); uploadSpeed.textContent = `${speedMB} MB/s`; if (speed > 0) { const remaining = (total - loaded) / speed; const minutes = Math.floor(remaining / 60); const seconds = Math.floor(remaining % 60); timeRemaining.textContent = `${minutes}:${seconds.toString().padStart(2, '0')} remaining`; } } // Manejo de selección de archivo function handleFileSelection(file) { const fileInfo = document.getElementById('file-info'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); const maxFileSizeMB = window.maxFileSizeMB || 16; if (file) { if (file.size > maxFileSizeMB * 1024 * 1024) { showToast(`File size exceeds the allowed limit (${maxFileSizeMB} MB)`, 'error'); clearFileSelection(); return; } fileName.textContent = file.name; fileName.classList.add('truncate'); fileName.setAttribute('data-tooltip', file.name); fileSize.textContent = formatFileSize(file.size); fileInfo.classList.remove('hidden'); } else { fileInfo.classList.add('hidden'); } } function clearFileSelection() { const fileInput = document.getElementById('file-upload'); const fileInfo = document.getElementById('file-info'); fileInput.value = ''; fileInfo.classList.add('hidden'); } // Cargar archivos del usuario async function loadUserFiles() { const userSession = getUserSession(); try { const response = await fetch(`/files/${userSession}`); const files = await response.json(); const filesList = document.getElementById('filesList'); const noFiles = document.getElementById('noFiles'); if (files.length === 0) { filesList.innerHTML = ''; noFiles.classList.remove('hidden'); } else { noFiles.classList.add('hidden'); filesList.innerHTML = files.map(file => `
`).join(''); } document.querySelectorAll('.file-tooltip').forEach(el => { el.addEventListener('mouseenter', function(e) { showCustomTooltip(e.target, e.target.getAttribute('data-tooltip')); }); el.addEventListener('mouseleave', hideCustomTooltip); }); updateRemainingTimes(); } catch (error) { showToast('Error loading files', 'error'); } } // Variables para el modal de eliminación let fileIdToDelete = null; function openDeleteModal(fileId) { fileIdToDelete = fileId; document.getElementById('deleteModal').classList.remove('hidden'); } function closeDeleteModal() { fileIdToDelete = null; document.getElementById('deleteModal').classList.add('hidden'); } // Eliminar archivo (ahora solo ejecuta si se confirma en el modal) async function confirmDeleteFile() { if (!fileIdToDelete) return; const userSession = getUserSession(); try { const response = await fetch(`/delete/${fileIdToDelete}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_session: userSession }) }); const result = await response.json(); if (result.success) { showToast('File deleted successfully'); loadUserFiles(); } else { showToast(result.error || 'Error deleting file', 'error'); } } catch (error) { showToast('Error deleting file: ' + error.message, 'error'); } finally { closeDeleteModal(); } } // Reemplazar deleteFile para abrir el modal window.deleteFile = openDeleteModal; // Eventos del modal function setupDeleteModalEvents() { document.getElementById('cancelDeleteBtn').addEventListener('click', closeDeleteModal); document.getElementById('confirmDeleteBtn').addEventListener('click', confirmDeleteFile); // Cerrar modal con Escape document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeDeleteModal(); }); } // Inicialización de eventos function setupEventListeners() { // Tema document.getElementById('themeToggle').addEventListener('click', toggleTheme); // Selección de archivo document.getElementById('file-upload').addEventListener('change', function(e) { const file = e.target.files[0]; handleFileSelection(file); }); // Limpiar selección document.getElementById('clear-file').addEventListener('click', clearFileSelection); // Subida de archivo document.getElementById('uploadForm').addEventListener('submit', async function(e) { e.preventDefault(); const fileInput = document.getElementById('file-upload'); const file = fileInput.files[0]; const duration = document.getElementById('duration').value; const uploadBtn = document.getElementById('uploadBtn'); const uploadBtnText = document.getElementById('uploadBtnText'); const uploadProgress = document.getElementById('uploadProgress'); const maxFileSizeMB = window.maxFileSizeMB || 16; if (!file) { showToast('Please select a file', 'error'); return; } if (file.size > maxFileSizeMB * 1024 * 1024) { showToast(`File size exceeds the allowed limit (${maxFileSizeMB} MB)`, 'error'); clearFileSelection(); return; } uploadBtn.disabled = true; uploadBtnText.textContent = 'Uploading...'; uploadProgress.classList.remove('hidden'); uploadStartTime = Date.now(); uploadedBytes = 0; const formData = new FormData(); formData.append('file', file); formData.append('user_session', getUserSession()); formData.append('duration_hours', duration); let waitingTimeout, errorTimeout; try { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { updateProgress(e.loaded, e.total); } }); xhr.addEventListener('load', function() { clearTimeout(waitingTimeout); clearTimeout(errorTimeout); updateProgress(file.size, file.size, true); document.getElementById('uploadSpeed').textContent = ''; document.getElementById('timeRemaining').textContent = ''; document.getElementById('waitingServer')?.remove(); if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); if (result.success) { showToast('File uploaded successfully!'); clearFileSelection(); loadUserFiles(); } else { showToast(result.error || 'Error uploading file', 'error'); } } else { showToast('Error uploading file', 'error'); } }); xhr.addEventListener('error', function() { clearTimeout(waitingTimeout); clearTimeout(errorTimeout); document.getElementById('waitingServer')?.remove(); showToast('Network error while uploading', 'error'); }); xhr.upload.addEventListener('load', function() { // Barra llegó a 100%, pero aún esperando respuesta updateProgress(file.size, file.size, true); let waiting = document.getElementById('waitingServer'); if (!waiting) { waiting = document.createElement('div'); waiting.id = 'waitingServer'; waiting.className = 'text-xs text-blue-600 dark:text-blue-400 mt-2 flex items-center gap-2'; waiting.innerHTML = 'Waiting for server confirmation...'; uploadProgress.appendChild(waiting); } // Si pasan 60s tras barra 100%, mostrar advertencia waitingTimeout = setTimeout(() => { waiting.textContent = 'The upload is taking longer than expected. Please check your connection.'; }, 60000); // Si pasan 2 minutos, mostrar error y permitir reintentar errorTimeout = setTimeout(() => { waiting.remove(); showToast('Upload failed: server did not respond. Please try again.', 'error'); uploadBtn.disabled = false; uploadBtnText.textContent = 'Upload File'; uploadProgress.classList.add('hidden'); }, 120000); }); xhr.open('POST', '/upload'); xhr.send(formData); } catch (error) { showToast('Error uploading file: ' + error.message, 'error'); } finally { setTimeout(() => { uploadBtn.disabled = false; uploadBtnText.textContent = 'Upload File'; uploadProgress.classList.add('hidden'); }, 1000); } }); // Refrescar lista document.getElementById('refreshBtn').addEventListener('click', loadUserFiles); } document.addEventListener('DOMContentLoaded', function() { initTheme(); setupEventListeners(); setupDeleteModalEvents(); loadUserFiles(); setInterval(updateRemainingTimes, 60000); }); function updateRemainingTimes() { document.querySelectorAll('.file-remaining-time').forEach(el => { const expiration = el.getAttribute('data-expiration'); el.textContent = getTimeRemaining(expiration); }); } window.copyDownloadUrl = function(url) { navigator.clipboard.writeText(url).then(() => { showToast('Download link copied!'); }, () => { showToast('Failed to copy link', 'error'); }); }