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;
+}