app.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // HokoriTemp - JavaScript principal
  2. let selectedFile = null;
  3. // Usar configuración del servidor si está disponible, sino usar valor por defecto
  4. const MAX_FILE_SIZE = (typeof SERVER_CONFIG !== 'undefined' && SERVER_CONFIG.maxFileSize)
  5. ? SERVER_CONFIG.maxFileSize
  6. : 16 * 1024 * 1024; // 16MB por defecto
  7. // DOM Elements
  8. const dropZone = document.getElementById('dropZone');
  9. const fileInput = document.getElementById('fileInput');
  10. const fileInfo = document.getElementById('fileInfo');
  11. const fileName = document.getElementById('fileName');
  12. const fileSize = document.getElementById('fileSize');
  13. const removeFile = document.getElementById('removeFile');
  14. const uploadForm = document.getElementById('uploadForm');
  15. const uploadBtn = document.getElementById('uploadBtn');
  16. const uploadBtnText = document.getElementById('uploadBtnText');
  17. const uploadBtnLoading = document.getElementById('uploadBtnLoading');
  18. const resultSection = document.getElementById('resultSection');
  19. const emptyResultSection = document.getElementById('emptyResultSection');
  20. const errorSection = document.getElementById('errorSection');
  21. const errorMessage = document.getElementById('errorMessage');
  22. const downloadLink = document.getElementById('downloadLink');
  23. // Progress bar elements
  24. const progressContainer = document.getElementById('progressContainer');
  25. const progressBar = document.getElementById('progressBar');
  26. const progressText = document.getElementById('progressText');
  27. const uploadedSize = document.getElementById('uploadedSize');
  28. const totalSize = document.getElementById('totalSize');
  29. // Event Listeners
  30. dropZone.addEventListener('click', () => fileInput.click());
  31. dropZone.addEventListener('dragover', handleDragOver);
  32. dropZone.addEventListener('dragleave', handleDragLeave);
  33. dropZone.addEventListener('drop', handleDrop);
  34. fileInput.addEventListener('change', handleFileSelect);
  35. removeFile.addEventListener('click', removeSelectedFile);
  36. uploadForm.addEventListener('submit', handleUpload);
  37. // Inicializar estado
  38. document.addEventListener('DOMContentLoaded', function() {
  39. // Mostrar el estado vacío del resultado al cargar
  40. emptyResultSection.classList.remove('hidden');
  41. emptyResultSection.classList.add('fade-in');
  42. });
  43. function handleDragOver(e) {
  44. e.preventDefault();
  45. dropZone.classList.add('dragover');
  46. }
  47. function handleDragLeave(e) {
  48. e.preventDefault();
  49. dropZone.classList.remove('dragover');
  50. }
  51. function handleDrop(e) {
  52. e.preventDefault();
  53. dropZone.classList.remove('dragover');
  54. const files = e.dataTransfer.files;
  55. if (files.length > 0) {
  56. handleFile(files[0]);
  57. }
  58. }
  59. function handleFileSelect(e) {
  60. const file = e.target.files[0];
  61. if (file) {
  62. handleFile(file);
  63. }
  64. }
  65. function handleFile(file) {
  66. // Validar tamaño del archivo
  67. if (file.size > MAX_FILE_SIZE) {
  68. showError(`El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`);
  69. return;
  70. }
  71. selectedFile = file;
  72. fileName.textContent = file.name;
  73. fileName.title = file.name; // Agregar título completo para tooltip
  74. fileSize.textContent = formatFileSize(file.size);
  75. // Aplicar clase de advertencia si el archivo es grande (>10MB)
  76. if (file.size > 10 * 1024 * 1024) {
  77. fileInfo.classList.add('file-size-warning');
  78. } else {
  79. fileInfo.classList.remove('file-size-warning');
  80. }
  81. fileInfo.classList.remove('hidden');
  82. uploadBtn.disabled = false;
  83. hideError();
  84. }
  85. function removeSelectedFile() {
  86. selectedFile = null;
  87. fileInput.value = '';
  88. fileInfo.classList.add('hidden');
  89. fileInfo.classList.remove('file-size-warning', 'file-size-error');
  90. uploadBtn.disabled = true;
  91. // Ocultar resultado si hay uno mostrado
  92. hideResult();
  93. // Ocultar barra de progreso
  94. hideProgress();
  95. }
  96. function formatFileSize(bytes) {
  97. if (bytes === 0) return '0 Bytes';
  98. const k = 1024;
  99. const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  100. const i = Math.floor(Math.log(bytes) / Math.log(k));
  101. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  102. }
  103. async function handleUpload(e) {
  104. e.preventDefault();
  105. if (!selectedFile) {
  106. showError('Por favor selecciona un archivo');
  107. return;
  108. }
  109. // Validar tamaño del archivo antes de subir
  110. if (selectedFile.size > MAX_FILE_SIZE) {
  111. showError(`El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`);
  112. return;
  113. }
  114. // Show loading state
  115. uploadBtn.disabled = true;
  116. uploadBtnText.classList.add('hidden');
  117. uploadBtnLoading.classList.remove('hidden');
  118. hideError();
  119. hideResult();
  120. showProgress();
  121. totalSize.textContent = formatFileSize(selectedFile.size);
  122. const formData = new FormData();
  123. formData.append('file', selectedFile);
  124. // Agregar tiempo de vida del archivo
  125. const expiresHours = document.getElementById('expiresHours').value;
  126. formData.append('expires_hours', expiresHours);
  127. return new Promise((resolve, reject) => {
  128. const xhr = new XMLHttpRequest();
  129. // Configurar eventos de progreso
  130. xhr.upload.addEventListener('progress', function(e) {
  131. if (e.lengthComputable) {
  132. updateProgress(e.loaded, e.total);
  133. }
  134. });
  135. // Configurar eventos de respuesta
  136. xhr.addEventListener('load', function() {
  137. hideProgress();
  138. try {
  139. // Verificar si la respuesta es JSON válido
  140. const contentType = xhr.getResponseHeader('content-type');
  141. const isJson = contentType && contentType.includes('application/json');
  142. // Manejar diferentes códigos de respuesta
  143. if (xhr.status === 413) {
  144. showError(`El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`);
  145. resolve();
  146. return;
  147. }
  148. if (xhr.status !== 200) {
  149. let errorMessage = `Error del servidor (${xhr.status})`;
  150. if (isJson) {
  151. try {
  152. const errorData = JSON.parse(xhr.responseText);
  153. errorMessage = errorData.error || errorMessage;
  154. } catch (e) {
  155. console.error('Error parsing JSON response:', e);
  156. }
  157. } else {
  158. // Si no es JSON, intentar leer como texto
  159. const errorText = xhr.responseText;
  160. console.error('Error response:', errorText);
  161. // Detectar errores específicos basados en el contenido
  162. if (errorText.includes('Request Entity Too Large') || errorText.includes('413')) {
  163. errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
  164. } else if (errorText.includes('413')) {
  165. errorMessage = `El archivo es demasiado grande. Tamaño máximo: ${getMaxFileSizeMB()}MB`;
  166. } else {
  167. errorMessage = `Error del servidor: ${xhr.statusText}`;
  168. }
  169. }
  170. showError(errorMessage);
  171. resolve();
  172. return;
  173. }
  174. if (isJson) {
  175. const result = JSON.parse(xhr.responseText);
  176. if (result.success) {
  177. showResult(result.download_url);
  178. showNotification('Archivo subido exitosamente', 'success');
  179. } else {
  180. showError(result.error || 'Error al subir el archivo');
  181. }
  182. } else {
  183. showError('Respuesta inesperada del servidor');
  184. }
  185. } catch (error) {
  186. console.error('Upload error:', error);
  187. showError('Error al procesar la respuesta del servidor.');
  188. }
  189. resolve();
  190. });
  191. // Configurar eventos de error
  192. xhr.addEventListener('error', function() {
  193. hideProgress();
  194. console.error('Upload error: Network error');
  195. showError('Error de conexión. Verifica tu conexión a internet e intenta de nuevo.');
  196. resolve();
  197. });
  198. xhr.addEventListener('abort', function() {
  199. hideProgress();
  200. console.error('Upload error: Request aborted');
  201. showError('Subida cancelada.');
  202. resolve();
  203. });
  204. // Configurar timeout
  205. xhr.timeout = 300000; // 5 minutos
  206. xhr.addEventListener('timeout', function() {
  207. hideProgress();
  208. console.error('Upload error: Request timeout');
  209. showError('Tiempo de espera agotado. Intenta de nuevo.');
  210. resolve();
  211. });
  212. // Enviar la petición
  213. xhr.open('POST', '/upload');
  214. xhr.send(formData);
  215. }).finally(() => {
  216. // Reset loading state
  217. uploadBtn.disabled = false;
  218. uploadBtnText.classList.remove('hidden');
  219. uploadBtnLoading.classList.add('hidden');
  220. });
  221. }
  222. // Función eliminada - ya no se usa localStorage, los enlaces se guardan en la base de datos
  223. function showResult(url) {
  224. downloadLink.value = url;
  225. // Actualizar el tiempo de vida mostrado
  226. const expiresHours = document.getElementById('expiresHours').value;
  227. const expiresTimeText = getExpiresTimeText(expiresHours);
  228. document.getElementById('selectedExpiresTime').textContent = expiresTimeText;
  229. resultSection.classList.remove('hidden');
  230. resultSection.classList.add('fade-in');
  231. emptyResultSection.classList.add('hidden');
  232. // Ocultar el formulario de subida
  233. const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
  234. if (uploadFormContainer) {
  235. uploadFormContainer.classList.add('hidden');
  236. }
  237. }
  238. function getExpiresTimeText(hours) {
  239. const hoursInt = parseInt(hours);
  240. if (hoursInt === 1) return '1 hora';
  241. if (hoursInt === 6) return '6 horas';
  242. if (hoursInt === 12) return '12 horas';
  243. if (hoursInt === 24) return '24 horas';
  244. if (hoursInt === 48) return '2 días';
  245. if (hoursInt === 72) return '3 días';
  246. if (hoursInt === 168) return '7 días';
  247. return `${hoursInt} horas`;
  248. }
  249. function showError(message) {
  250. errorMessage.textContent = message;
  251. errorSection.classList.remove('hidden');
  252. showNotification(message, 'error');
  253. }
  254. function hideError() {
  255. errorSection.classList.add('hidden');
  256. }
  257. function hideResult() {
  258. resultSection.classList.add('hidden');
  259. resultSection.classList.remove('fade-in');
  260. emptyResultSection.classList.remove('hidden');
  261. emptyResultSection.classList.add('fade-in');
  262. // Mostrar el formulario de subida nuevamente
  263. const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
  264. if (uploadFormContainer) {
  265. uploadFormContainer.classList.remove('hidden');
  266. }
  267. }
  268. function copyLink() {
  269. downloadLink.select();
  270. document.execCommand('copy');
  271. // Show feedback
  272. const copyBtn = event.target;
  273. const originalText = copyBtn.textContent;
  274. copyBtn.textContent = '¡Copiado!';
  275. setTimeout(() => {
  276. copyBtn.textContent = originalText;
  277. }, 2000);
  278. showNotification('Enlace copiado al portapapeles', 'success');
  279. }
  280. function openLink() {
  281. window.open(downloadLink.value, '_blank');
  282. }
  283. function resetForm() {
  284. removeSelectedFile();
  285. hideResult();
  286. hideError();
  287. hideProgress();
  288. // Mostrar el formulario de subida nuevamente
  289. const uploadFormContainer = document.querySelector('.bg-white.rounded-lg.shadow-lg.p-6');
  290. if (uploadFormContainer) {
  291. uploadFormContainer.classList.remove('hidden');
  292. }
  293. // Mostrar el estado vacío del resultado
  294. emptyResultSection.classList.remove('hidden');
  295. }
  296. // Función para mostrar notificaciones
  297. function showNotification(message, type = 'info') {
  298. // Crear elemento de notificación
  299. const notification = document.createElement('div');
  300. notification.className = `notification ${type}`;
  301. notification.textContent = message;
  302. // Agregar al DOM
  303. document.body.appendChild(notification);
  304. // Mostrar con animación
  305. setTimeout(() => {
  306. notification.classList.add('show');
  307. }, 100);
  308. // Ocultar después de 3 segundos
  309. setTimeout(() => {
  310. notification.classList.remove('show');
  311. setTimeout(() => {
  312. document.body.removeChild(notification);
  313. }, 300);
  314. }, 3000);
  315. }
  316. // Función helper para obtener el tamaño máximo en MB
  317. function getMaxFileSizeMB() {
  318. return (typeof SERVER_CONFIG !== 'undefined' && SERVER_CONFIG.maxFileSizeMB)
  319. ? SERVER_CONFIG.maxFileSizeMB
  320. : MAX_FILE_SIZE / (1024 * 1024);
  321. }
  322. // Función para validar archivo antes de subir
  323. function validateFile(file) {
  324. const maxSize = MAX_FILE_SIZE;
  325. const maxSizeMB = getMaxFileSizeMB();
  326. if (file.size > maxSize) {
  327. return {
  328. valid: false,
  329. message: `El archivo es demasiado grande. Tamaño máximo: ${maxSizeMB}MB`
  330. };
  331. }
  332. return { valid: true };
  333. }
  334. // Progress bar functions
  335. function showProgress() {
  336. progressContainer.classList.remove('hidden');
  337. progressBar.style.width = '0%';
  338. progressText.textContent = '0%';
  339. uploadedSize.textContent = '0 KB';
  340. totalSize.textContent = '0 KB';
  341. }
  342. function hideProgress() {
  343. progressContainer.classList.add('hidden');
  344. }
  345. function updateProgress(uploaded, total) {
  346. const percentage = Math.round((uploaded / total) * 100);
  347. progressBar.style.width = percentage + '%';
  348. progressText.textContent = percentage + '%';
  349. uploadedSize.textContent = formatFileSize(uploaded);
  350. totalSize.textContent = formatFileSize(total);
  351. }
  352. function resetProgress() {
  353. progressBar.style.width = '0%';
  354. progressText.textContent = '0%';
  355. uploadedSize.textContent = '0 KB';
  356. totalSize.textContent = '0 KB';
  357. }