diff --git a/frontend/index.html b/frontend/index.html index 773f38e..75a3a7d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -44,6 +44,12 @@
Загрузка...
+
diff --git a/frontend/script.js b/frontend/script.js index 6d9d063..eec6898 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -72,16 +72,61 @@ async function handleDownloadPage(fileUuid) { } const fileData = data.result.data; + const fileSize = parseInt(fileData.file_size); - // Create download button handler + // Create download button handler with progress tracking const handleDownload = async () => { + const downloadButton = document.getElementById('downloadButton'); + const downloadProgress = document.getElementById('downloadProgress'); + const progressBar = document.getElementById('progressBar'); + const progressText = document.getElementById('progressText'); + + // For small files (< 10MB), use direct download (browser's native download) + // For larger files, show progress bar + if (fileSize < 10 * 1024 * 1024) { + // Small file - use direct download with browser's native progress + const link = document.createElement('a'); + link.href = fileData.url; + link.download = fileData.file_name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + return; + } + + // Large file - download with progress tracking try { - // For presigned URLs, we fetch the file and create a blob URL + downloadButton.disabled = true; + downloadProgress.style.display = 'block'; + progressBar.style.width = '0%'; + progressText.textContent = '0%'; + const response = await fetch(fileData.url); if (!response.ok) { throw new Error('Ошибка при загрузке файла'); } - const blob = await response.blob(); + + const contentLength = response.headers.get('content-length'); + const total = contentLength ? parseInt(contentLength, 10) : fileSize; + let loaded = 0; + + const reader = response.body.getReader(); + const chunks = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + chunks.push(value); + loaded += value.length; + + const percent = Math.round((loaded / total) * 100); + progressBar.style.width = percent + '%'; + progressText.textContent = `${percent}% (${formatBytes(loaded)} / ${formatBytes(total)})`; + } + + // Combine chunks into blob + const blob = new Blob(chunks); const blobUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); @@ -91,9 +136,18 @@ async function handleDownloadPage(fileUuid) { link.click(); document.body.removeChild(link); - // Clean up the blob URL + // Clean up window.URL.revokeObjectURL(blobUrl); + downloadProgress.style.display = 'none'; + downloadButton.disabled = false; + progressText.textContent = 'Загрузка завершена!'; + + setTimeout(() => { + progressText.textContent = ''; + }, 2000); } catch (error) { + downloadProgress.style.display = 'none'; + downloadButton.disabled = false; // Fallback: try direct navigation window.location.href = fileData.url; } diff --git a/frontend/style.css b/frontend/style.css index 5e9a903..ac77033 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -333,3 +333,33 @@ button:disabled { transform: translateY(-1px); box-shadow: 0 8px 16px -4px rgba(99, 102, 241, 0.4); } + +/* Download progress */ +.download-progress { + margin-top: 24px; + text-align: center; +} + +.progress-bar-container { + width: 100%; + height: 8px; + background: #e5e7eb; + border-radius: 4px; + overflow: hidden; + margin-bottom: 12px; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, #6366f1, #8b5cf6); + border-radius: 4px; + transition: width 0.3s ease; + width: 0%; +} + +.progress-text { + font-size: 14px; + color: #6b7280; + margin: 0; + font-weight: 500; +}