Update app/static/script.js
Browse files- app/static/script.js +51 -92
app/static/script.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
| 1 |
// app/static/script.js
|
| 2 |
document.addEventListener('DOMContentLoaded', () => {
|
| 3 |
-
// ---
|
| 4 |
-
const CHUNK_SIZE = 4 * 1024 * 1024; // 4MB chunks
|
| 5 |
let currentFile = null;
|
| 6 |
-
let
|
| 7 |
-
let websocket = null;
|
| 8 |
|
| 9 |
// --- DOM Elements ---
|
| 10 |
-
const uploaderCard = document.getElementById('uploader-card');
|
| 11 |
const states = {
|
| 12 |
initial: document.getElementById('state-initial'),
|
| 13 |
uploading: document.getElementById('state-uploading'),
|
| 14 |
-
assembling: document.getElementById('state-assembling'),
|
| 15 |
complete: document.getElementById('state-complete'),
|
| 16 |
error: document.getElementById('state-error'),
|
| 17 |
};
|
|
@@ -39,7 +35,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 39 |
browseBtn.addEventListener('click', () => fileInput.click());
|
| 40 |
fileInput.addEventListener('change', handleFileSelect);
|
| 41 |
|
| 42 |
-
// Drag and Drop
|
| 43 |
dropZone.addEventListener('dragover', (e) => {
|
| 44 |
e.preventDefault();
|
| 45 |
dropZone.classList.add('drag-over');
|
|
@@ -48,16 +43,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 48 |
dropZone.addEventListener('drop', (e) => {
|
| 49 |
e.preventDefault();
|
| 50 |
dropZone.classList.remove('drag-over');
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
handleFileSelect({ target: { files: files } });
|
| 54 |
}
|
| 55 |
});
|
| 56 |
|
| 57 |
uploadAnotherBtn.addEventListener('click', resetUploader);
|
| 58 |
retryBtn.addEventListener('click', resetUploader);
|
| 59 |
|
| 60 |
-
|
| 61 |
// --- Core Functions ---
|
| 62 |
function handleFileSelect(event) {
|
| 63 |
const file = event.target.files[0];
|
|
@@ -67,104 +60,70 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 67 |
fileNameEl.textContent = file.name;
|
| 68 |
fileSizeEl.textContent = formatBytes(file.size);
|
| 69 |
|
| 70 |
-
|
| 71 |
}
|
| 72 |
|
| 73 |
-
|
| 74 |
switchState('uploading');
|
| 75 |
-
statusText.textContent = '
|
| 76 |
progressBar.style.width = '0%';
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
statusText.textContent = 'Finalizing upload...';
|
| 92 |
-
const completeResponse = await fetch(`/complete/${session.session_id}`, { method: 'POST' });
|
| 93 |
-
if (!completeResponse.ok) throw new Error('Failed to finalize upload.');
|
| 94 |
-
|
| 95 |
-
const result = await completeResponse.json();
|
| 96 |
-
downloadLink.href = result.download_url;
|
| 97 |
-
|
| 98 |
-
} catch (error) {
|
| 99 |
-
console.error('Upload process failed:', error);
|
| 100 |
-
showError(error.message);
|
| 101 |
-
}
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
function setupWebSocket() {
|
| 105 |
-
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 106 |
-
const wsUrl = `${wsProtocol}//${window.location.host}/ws/${session.session_id}`;
|
| 107 |
-
websocket = new WebSocket(wsUrl);
|
| 108 |
-
|
| 109 |
-
websocket.onmessage = (event) => {
|
| 110 |
-
const data = JSON.parse(event.data);
|
| 111 |
-
if (data.type === 'progress') {
|
| 112 |
-
updateProgress(data.uploaded_bytes, data.total_bytes);
|
| 113 |
-
if(data.status === 'assembling') {
|
| 114 |
-
switchState('assembling');
|
| 115 |
-
} else if(data.status === 'completed') {
|
| 116 |
-
switchState('complete');
|
| 117 |
-
}
|
| 118 |
}
|
| 119 |
-
};
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
}
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
const
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
const url = `/upload/${session.session_id}?chunk_index=${i}&total_size=${file.size}`;
|
| 135 |
-
|
| 136 |
-
statusText.textContent = `Uploading chunk ${i + 1} of ${totalChunks}...`;
|
| 137 |
-
|
| 138 |
-
const response = await fetch(url, {
|
| 139 |
-
method: 'PUT',
|
| 140 |
-
body: formData,
|
| 141 |
-
});
|
| 142 |
-
|
| 143 |
-
if (!response.ok) {
|
| 144 |
-
const errorData = await response.json();
|
| 145 |
-
throw new Error(errorData.detail || 'Chunk upload failed.');
|
| 146 |
}
|
| 147 |
}
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
function updateProgress(uploaded, total) {
|
| 151 |
-
if (total > 0) {
|
| 152 |
-
const percentage = (uploaded / total) * 100;
|
| 153 |
-
progressBar.style.width = `${percentage}%`;
|
| 154 |
-
statusText.textContent = `${formatBytes(uploaded)} / ${formatBytes(total)}`;
|
| 155 |
-
}
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
function showError(message) {
|
| 159 |
errorMessage.textContent = message;
|
| 160 |
switchState('error');
|
| 161 |
}
|
| 162 |
|
| 163 |
function resetUploader() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
currentFile = null;
|
| 165 |
-
session = {};
|
| 166 |
fileInput.value = ''; // Reset file input
|
| 167 |
-
if (websocket) websocket.close();
|
| 168 |
switchState('initial');
|
| 169 |
}
|
| 170 |
|
|
|
|
| 1 |
// app/static/script.js
|
| 2 |
document.addEventListener('DOMContentLoaded', () => {
|
| 3 |
+
// --- State ---
|
|
|
|
| 4 |
let currentFile = null;
|
| 5 |
+
let currentXHR = null; // To hold the request object for cancellation
|
|
|
|
| 6 |
|
| 7 |
// --- DOM Elements ---
|
|
|
|
| 8 |
const states = {
|
| 9 |
initial: document.getElementById('state-initial'),
|
| 10 |
uploading: document.getElementById('state-uploading'),
|
|
|
|
| 11 |
complete: document.getElementById('state-complete'),
|
| 12 |
error: document.getElementById('state-error'),
|
| 13 |
};
|
|
|
|
| 35 |
browseBtn.addEventListener('click', () => fileInput.click());
|
| 36 |
fileInput.addEventListener('change', handleFileSelect);
|
| 37 |
|
|
|
|
| 38 |
dropZone.addEventListener('dragover', (e) => {
|
| 39 |
e.preventDefault();
|
| 40 |
dropZone.classList.add('drag-over');
|
|
|
|
| 43 |
dropZone.addEventListener('drop', (e) => {
|
| 44 |
e.preventDefault();
|
| 45 |
dropZone.classList.remove('drag-over');
|
| 46 |
+
if (e.dataTransfer.files.length > 0) {
|
| 47 |
+
handleFileSelect({ target: { files: e.dataTransfer.files } });
|
|
|
|
| 48 |
}
|
| 49 |
});
|
| 50 |
|
| 51 |
uploadAnotherBtn.addEventListener('click', resetUploader);
|
| 52 |
retryBtn.addEventListener('click', resetUploader);
|
| 53 |
|
|
|
|
| 54 |
// --- Core Functions ---
|
| 55 |
function handleFileSelect(event) {
|
| 56 |
const file = event.target.files[0];
|
|
|
|
| 60 |
fileNameEl.textContent = file.name;
|
| 61 |
fileSizeEl.textContent = formatBytes(file.size);
|
| 62 |
|
| 63 |
+
startUpload(file);
|
| 64 |
}
|
| 65 |
|
| 66 |
+
function startUpload(file) {
|
| 67 |
switchState('uploading');
|
| 68 |
+
statusText.textContent = 'Uploading...';
|
| 69 |
progressBar.style.width = '0%';
|
| 70 |
|
| 71 |
+
const formData = new FormData();
|
| 72 |
+
formData.append('file', file);
|
| 73 |
+
|
| 74 |
+
// We use XMLHttpRequest instead of fetch because it provides
|
| 75 |
+
// a native 'onprogress' event for tracking upload progress.
|
| 76 |
+
const xhr = new XMLHttpRequest();
|
| 77 |
+
currentXHR = xhr;
|
| 78 |
+
|
| 79 |
+
xhr.upload.addEventListener('progress', (event) => {
|
| 80 |
+
if (event.lengthComputable) {
|
| 81 |
+
const percentage = (event.loaded / event.total) * 100;
|
| 82 |
+
progressBar.style.width = `${percentage}%`;
|
| 83 |
+
statusText.textContent = `${formatBytes(event.loaded)} / ${formatBytes(event.total)}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
+
});
|
| 86 |
+
|
| 87 |
+
xhr.addEventListener('load', () => {
|
| 88 |
+
if (xhr.status >= 200 && xhr.status < 300) {
|
| 89 |
+
const response = JSON.parse(xhr.responseText);
|
| 90 |
+
downloadLink.href = response.download_url;
|
| 91 |
+
downloadLink.download = response.filename; // Set the default download filename
|
| 92 |
+
switchState('complete');
|
| 93 |
+
} else {
|
| 94 |
+
handleUploadError(xhr);
|
| 95 |
+
}
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
xhr.addEventListener('error', () => handleUploadError(xhr));
|
| 99 |
+
xhr.addEventListener('abort', () => console.log('Upload aborted.'));
|
| 100 |
+
|
| 101 |
+
xhr.open('POST', '/upload', true);
|
| 102 |
+
xhr.send(formData);
|
| 103 |
}
|
| 104 |
|
| 105 |
+
function handleUploadError(xhr) {
|
| 106 |
+
let message = 'An unknown network error occurred.';
|
| 107 |
+
try {
|
| 108 |
+
const errorData = JSON.parse(xhr.responseText);
|
| 109 |
+
message = errorData.detail || 'Upload failed.';
|
| 110 |
+
} catch (e) {
|
| 111 |
+
// Response was not JSON, use status text
|
| 112 |
+
if (xhr.statusText) {
|
| 113 |
+
message = xhr.statusText;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
}
|
| 115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
errorMessage.textContent = message;
|
| 117 |
switchState('error');
|
| 118 |
}
|
| 119 |
|
| 120 |
function resetUploader() {
|
| 121 |
+
if (currentXHR) {
|
| 122 |
+
currentXHR.abort(); // Cancel any ongoing upload
|
| 123 |
+
currentXHR = null;
|
| 124 |
+
}
|
| 125 |
currentFile = null;
|
|
|
|
| 126 |
fileInput.value = ''; // Reset file input
|
|
|
|
| 127 |
switchState('initial');
|
| 128 |
}
|
| 129 |
|