|
@@ -23,6 +23,13 @@ const errorSection = document.getElementById('errorSection');
|
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
|
const downloadLink = document.getElementById('downloadLink');
|
|
const downloadLink = document.getElementById('downloadLink');
|
|
|
|
|
|
|
|
|
|
+// Progress bar elements
|
|
|
|
|
+const progressContainer = document.getElementById('progressContainer');
|
|
|
|
|
+const progressBar = document.getElementById('progressBar');
|
|
|
|
|
+const progressText = document.getElementById('progressText');
|
|
|
|
|
+const uploadedSize = document.getElementById('uploadedSize');
|
|
|
|
|
+const totalSize = document.getElementById('totalSize');
|
|
|
|
|
+
|
|
|
// Event Listeners
|
|
// Event Listeners
|
|
|
dropZone.addEventListener('click', () => fileInput.click());
|
|
dropZone.addEventListener('click', () => fileInput.click());
|
|
|
dropZone.addEventListener('dragover', handleDragOver);
|
|
dropZone.addEventListener('dragover', handleDragOver);
|
|
@@ -97,6 +104,8 @@ function removeSelectedFile() {
|
|
|
uploadBtn.disabled = true;
|
|
uploadBtn.disabled = true;
|
|
|
// Ocultar resultado si hay uno mostrado
|
|
// Ocultar resultado si hay uno mostrado
|
|
|
hideResult();
|
|
hideResult();
|
|
|
|
|
+ // Ocultar barra de progreso
|
|
|
|
|
+ hideProgress();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function formatFileSize(bytes) {
|
|
function formatFileSize(bytes) {
|
|
@@ -127,110 +136,158 @@ async function handleUpload(e) {
|
|
|
uploadBtnLoading.classList.remove('hidden');
|
|
uploadBtnLoading.classList.remove('hidden');
|
|
|
hideError();
|
|
hideError();
|
|
|
hideResult();
|
|
hideResult();
|
|
|
|
|
+ showProgress();
|
|
|
|
|
+ totalSize.textContent = formatFileSize(selectedFile.size);
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
const formData = new FormData();
|
|
|
formData.append('file', selectedFile);
|
|
formData.append('file', selectedFile);
|
|
|
|
|
+
|
|
|
|
|
+ // Agregar tiempo de vida del archivo
|
|
|
|
|
+ const expiresHours = document.getElementById('expiresHours').value;
|
|
|
|
|
+ formData.append('expires_hours', expiresHours);
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- const response = await fetch('/upload', {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- body: formData
|
|
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ const xhr = new XMLHttpRequest();
|
|
|
|
|
+
|
|
|
|
|
+ // Configurar eventos de progreso
|
|
|
|
|
+ xhr.upload.addEventListener('progress', function(e) {
|
|
|
|
|
+ if (e.lengthComputable) {
|
|
|
|
|
+ updateProgress(e.loaded, e.total);
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- // Verificar si la respuesta es JSON válido
|
|
|
|
|
- const contentType = response.headers.get('content-type');
|
|
|
|
|
- const isJson = contentType && contentType.includes('application/json');
|
|
|
|
|
-
|
|
|
|
|
- // Manejar diferentes códigos de respuesta
|
|
|
|
|
- if (response.status === 413) {
|
|
|
|
|
- showError(`El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!response.ok) {
|
|
|
|
|
- let errorMessage = `Error del servidor (${response.status})`;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Configurar eventos de respuesta
|
|
|
|
|
+ xhr.addEventListener('load', function() {
|
|
|
|
|
+ hideProgress();
|
|
|
|
|
|
|
|
- if (isJson) {
|
|
|
|
|
- try {
|
|
|
|
|
- const errorData = await response.json();
|
|
|
|
|
- errorMessage = errorData.error || errorMessage;
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('Error parsing JSON response:', e);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Verificar si la respuesta es JSON válido
|
|
|
|
|
+ const contentType = xhr.getResponseHeader('content-type');
|
|
|
|
|
+ const isJson = contentType && contentType.includes('application/json');
|
|
|
|
|
+
|
|
|
|
|
+ // Manejar diferentes códigos de respuesta
|
|
|
|
|
+ if (xhr.status === 413) {
|
|
|
|
|
+ showError(`El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`);
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- // Si no es JSON, intentar leer como texto
|
|
|
|
|
- try {
|
|
|
|
|
- const errorText = await response.text();
|
|
|
|
|
- console.error('Error response:', errorText);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (xhr.status !== 200) {
|
|
|
|
|
+ let errorMessage = `Error del servidor (${xhr.status})`;
|
|
|
|
|
|
|
|
- // Detectar errores específicos basados en el contenido
|
|
|
|
|
- if (errorText.includes('Request Entity Too Large') || errorText.includes('413')) {
|
|
|
|
|
- errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
|
|
|
|
|
- } else if (errorText.includes('413')) {
|
|
|
|
|
- errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
|
|
|
|
|
|
|
+ if (isJson) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const errorData = JSON.parse(xhr.responseText);
|
|
|
|
|
+ errorMessage = errorData.error || errorMessage;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('Error parsing JSON response:', e);
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
- errorMessage = `Error del servidor: ${response.statusText}`;
|
|
|
|
|
|
|
+ // Si no es JSON, intentar leer como texto
|
|
|
|
|
+ const errorText = xhr.responseText;
|
|
|
|
|
+ console.error('Error response:', errorText);
|
|
|
|
|
+
|
|
|
|
|
+ // Detectar errores específicos basados en el contenido
|
|
|
|
|
+ if (errorText.includes('Request Entity Too Large') || errorText.includes('413')) {
|
|
|
|
|
+ errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
|
|
|
|
|
+ } else if (errorText.includes('413')) {
|
|
|
|
|
+ errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ errorMessage = `Error del servidor: ${xhr.statusText}`;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('Error reading response text:', e);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ showError(errorMessage);
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (isJson) {
|
|
|
|
|
+ const result = JSON.parse(xhr.responseText);
|
|
|
|
|
+
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ showResult(result.download_url);
|
|
|
|
|
+ showNotification('Archivo subido exitosamente', 'success');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showError(result.error || 'Error al subir el archivo');
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showError('Respuesta inesperada del servidor');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Upload error:', error);
|
|
|
|
|
+ showError('Error al procesar la respuesta del servidor.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- showError(errorMessage);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (isJson) {
|
|
|
|
|
- const result = await response.json();
|
|
|
|
|
-
|
|
|
|
|
- if (result.success) {
|
|
|
|
|
- // Save to localStorage
|
|
|
|
|
- saveLinkToStorage(result);
|
|
|
|
|
- showResult(result.download_url);
|
|
|
|
|
- showNotification('Archivo subido exitosamente', 'success');
|
|
|
|
|
- } else {
|
|
|
|
|
- showError(result.error || 'Error al subir el archivo');
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- showError('Respuesta inesperada del servidor');
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Upload error:', error);
|
|
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // Determinar el tipo de error
|
|
|
|
|
- if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
|
|
|
|
|
|
|
+ // Configurar eventos de error
|
|
|
|
|
+ xhr.addEventListener('error', function() {
|
|
|
|
|
+ hideProgress();
|
|
|
|
|
+ console.error('Upload error: Network error');
|
|
|
showError('Error de conexión. Verifica tu conexión a internet e intenta de nuevo.');
|
|
showError('Error de conexión. Verifica tu conexión a internet e intenta de nuevo.');
|
|
|
- } else if (error.name === 'TypeError' && error.message.includes('JSON')) {
|
|
|
|
|
- showError('Error al procesar la respuesta del servidor.');
|
|
|
|
|
- } else {
|
|
|
|
|
- showError('Error inesperado. Intenta de nuevo.');
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ xhr.addEventListener('abort', function() {
|
|
|
|
|
+ hideProgress();
|
|
|
|
|
+ console.error('Upload error: Request aborted');
|
|
|
|
|
+ showError('Subida cancelada.');
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Configurar timeout
|
|
|
|
|
+ xhr.timeout = 300000; // 5 minutos
|
|
|
|
|
+ xhr.addEventListener('timeout', function() {
|
|
|
|
|
+ hideProgress();
|
|
|
|
|
+ console.error('Upload error: Request timeout');
|
|
|
|
|
+ showError('Tiempo de espera agotado. Intenta de nuevo.');
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Enviar la petición
|
|
|
|
|
+ xhr.open('POST', '/upload');
|
|
|
|
|
+ xhr.send(formData);
|
|
|
|
|
+ }).finally(() => {
|
|
|
// Reset loading state
|
|
// Reset loading state
|
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.disabled = false;
|
|
|
uploadBtnText.classList.remove('hidden');
|
|
uploadBtnText.classList.remove('hidden');
|
|
|
uploadBtnLoading.classList.add('hidden');
|
|
uploadBtnLoading.classList.add('hidden');
|
|
|
- }
|
|
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function saveLinkToStorage(result) {
|
|
|
|
|
- const links = JSON.parse(localStorage.getItem('tempFileLinks') || '[]');
|
|
|
|
|
- const newLink = {
|
|
|
|
|
- id: result.file_id,
|
|
|
|
|
- url: result.download_url,
|
|
|
|
|
- filename: result.original_filename,
|
|
|
|
|
- expiresAt: result.expires_at,
|
|
|
|
|
- createdAt: new Date().toISOString()
|
|
|
|
|
- };
|
|
|
|
|
- links.push(newLink);
|
|
|
|
|
- localStorage.setItem('tempFileLinks', JSON.stringify(links));
|
|
|
|
|
-}
|
|
|
|
|
|
|
+// Función eliminada - ya no se usa localStorage, los enlaces se guardan en la base de datos
|
|
|
|
|
|
|
|
function showResult(url) {
|
|
function showResult(url) {
|
|
|
downloadLink.value = url;
|
|
downloadLink.value = url;
|
|
|
|
|
+
|
|
|
|
|
+ // Actualizar el tiempo de vida mostrado
|
|
|
|
|
+ const expiresHours = document.getElementById('expiresHours').value;
|
|
|
|
|
+ const expiresTimeText = getExpiresTimeText(expiresHours);
|
|
|
|
|
+ document.getElementById('selectedExpiresTime').textContent = expiresTimeText;
|
|
|
|
|
+
|
|
|
resultSection.classList.remove('hidden');
|
|
resultSection.classList.remove('hidden');
|
|
|
resultSection.classList.add('fade-in');
|
|
resultSection.classList.add('fade-in');
|
|
|
emptyResultSection.classList.add('hidden');
|
|
emptyResultSection.classList.add('hidden');
|
|
|
|
|
+
|
|
|
|
|
+ // Ocultar el formulario de subida
|
|
|
|
|
+ const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
|
|
|
|
|
+ if (uploadFormContainer) {
|
|
|
|
|
+ uploadFormContainer.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getExpiresTimeText(hours) {
|
|
|
|
|
+ const hoursInt = parseInt(hours);
|
|
|
|
|
+ if (hoursInt === 1) return '1 hora';
|
|
|
|
|
+ if (hoursInt === 6) return '6 horas';
|
|
|
|
|
+ if (hoursInt === 12) return '12 horas';
|
|
|
|
|
+ if (hoursInt === 24) return '24 horas';
|
|
|
|
|
+ if (hoursInt === 48) return '2 días';
|
|
|
|
|
+ if (hoursInt === 72) return '3 días';
|
|
|
|
|
+ if (hoursInt === 168) return '7 días';
|
|
|
|
|
+ return `${hoursInt} horas`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function showError(message) {
|
|
function showError(message) {
|
|
@@ -248,6 +305,12 @@ function hideResult() {
|
|
|
resultSection.classList.remove('fade-in');
|
|
resultSection.classList.remove('fade-in');
|
|
|
emptyResultSection.classList.remove('hidden');
|
|
emptyResultSection.classList.remove('hidden');
|
|
|
emptyResultSection.classList.add('fade-in');
|
|
emptyResultSection.classList.add('fade-in');
|
|
|
|
|
+
|
|
|
|
|
+ // Mostrar el formulario de subida nuevamente
|
|
|
|
|
+ const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
|
|
|
|
|
+ if (uploadFormContainer) {
|
|
|
|
|
+ uploadFormContainer.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function copyLink() {
|
|
function copyLink() {
|
|
@@ -271,6 +334,14 @@ function resetForm() {
|
|
|
removeSelectedFile();
|
|
removeSelectedFile();
|
|
|
hideResult();
|
|
hideResult();
|
|
|
hideError();
|
|
hideError();
|
|
|
|
|
+ hideProgress();
|
|
|
|
|
+
|
|
|
|
|
+ // Mostrar el formulario de subida nuevamente
|
|
|
|
|
+ const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
|
|
|
|
|
+ if (uploadFormContainer) {
|
|
|
|
|
+ uploadFormContainer.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Mostrar el estado vacío del resultado
|
|
// Mostrar el estado vacío del resultado
|
|
|
emptyResultSection.classList.remove('hidden');
|
|
emptyResultSection.classList.remove('hidden');
|
|
|
}
|
|
}
|
|
@@ -319,4 +390,32 @@ function validateFile(file) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return { valid: true };
|
|
return { valid: true };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Progress bar functions
|
|
|
|
|
+function showProgress() {
|
|
|
|
|
+ progressContainer.classList.remove('hidden');
|
|
|
|
|
+ progressBar.style.width = '0%';
|
|
|
|
|
+ progressText.textContent = '0%';
|
|
|
|
|
+ uploadedSize.textContent = '0 KB';
|
|
|
|
|
+ totalSize.textContent = '0 KB';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function hideProgress() {
|
|
|
|
|
+ progressContainer.classList.add('hidden');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateProgress(uploaded, total) {
|
|
|
|
|
+ const percentage = Math.round((uploaded / total) * 100);
|
|
|
|
|
+ progressBar.style.width = percentage + '%';
|
|
|
|
|
+ progressText.textContent = percentage + '%';
|
|
|
|
|
+ uploadedSize.textContent = formatFileSize(uploaded);
|
|
|
|
|
+ totalSize.textContent = formatFileSize(total);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetProgress() {
|
|
|
|
|
+ progressBar.style.width = '0%';
|
|
|
|
|
+ progressText.textContent = '0%';
|
|
|
|
|
+ uploadedSize.textContent = '0 KB';
|
|
|
|
|
+ totalSize.textContent = '0 KB';
|
|
|
}
|
|
}
|