|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
let currentFile = null; |
|
|
let currentXHR = null; |
|
|
|
|
|
|
|
|
const states = { |
|
|
initial: document.getElementById('state-initial'), |
|
|
uploading: document.getElementById('state-uploading'), |
|
|
complete: document.getElementById('state-complete'), |
|
|
error: document.getElementById('state-error'), |
|
|
}; |
|
|
const dropZone = document.getElementById('drop-zone'); |
|
|
const browseBtn = document.getElementById('browse-btn'); |
|
|
const fileInput = document.getElementById('file-input'); |
|
|
const fileNameEl = document.getElementById('file-name'); |
|
|
const fileSizeEl = document.getElementById('file-size'); |
|
|
const progressBar = document.getElementById('progress-bar'); |
|
|
const statusText = document.getElementById('status-text'); |
|
|
const downloadLink = document.getElementById('download-link'); |
|
|
const uploadAnotherBtn = document.getElementById('upload-another-btn'); |
|
|
const errorMessage = document.getElementById('error-message'); |
|
|
const retryBtn = document.getElementById('retry-btn'); |
|
|
|
|
|
|
|
|
const downloadUrlInput = document.getElementById('download-url-input'); |
|
|
const copyLinkBtn = document.getElementById('copy-link-btn'); |
|
|
|
|
|
|
|
|
function switchState(state) { |
|
|
Object.values(states).forEach(el => el.classList.remove('active')); |
|
|
if (states[state]) { |
|
|
states[state].classList.add('active'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
browseBtn.addEventListener('click', () => fileInput.click()); |
|
|
fileInput.addEventListener('change', handleFileSelect); |
|
|
|
|
|
dropZone.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
dropZone.classList.add('drag-over'); |
|
|
}); |
|
|
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over')); |
|
|
dropZone.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
dropZone.classList.remove('drag-over'); |
|
|
if (e.dataTransfer.files.length > 0) { |
|
|
handleFileSelect({ target: { files: e.dataTransfer.files } }); |
|
|
} |
|
|
}); |
|
|
|
|
|
uploadAnotherBtn.addEventListener('click', resetUploader); |
|
|
retryBtn.addEventListener('click', resetUploader); |
|
|
copyLinkBtn.addEventListener('click', handleCopyLink); |
|
|
|
|
|
|
|
|
function handleFileSelect(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
currentFile = file; |
|
|
fileNameEl.textContent = file.name; |
|
|
fileSizeEl.textContent = formatBytes(file.size); |
|
|
|
|
|
startUpload(file); |
|
|
} |
|
|
|
|
|
function startUpload(file) { |
|
|
switchState('uploading'); |
|
|
statusText.textContent = 'Uploading...'; |
|
|
progressBar.style.width = '0%'; |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('file', file); |
|
|
|
|
|
const xhr = new XMLHttpRequest(); |
|
|
currentXHR = xhr; |
|
|
|
|
|
xhr.upload.addEventListener('progress', (event) => { |
|
|
if (event.lengthComputable) { |
|
|
const percentage = (event.loaded / event.total) * 100; |
|
|
progressBar.style.width = `${percentage}%`; |
|
|
statusText.textContent = `${formatBytes(event.loaded)} / ${formatBytes(event.total)}`; |
|
|
} |
|
|
}); |
|
|
|
|
|
xhr.addEventListener('load', () => { |
|
|
if (xhr.status >= 200 && xhr.status < 300) { |
|
|
const response = JSON.parse(xhr.responseText); |
|
|
|
|
|
|
|
|
const fullUrl = `${window.location.origin}${response.download_url}`; |
|
|
|
|
|
downloadLink.href = response.download_url; |
|
|
downloadLink.download = response.filename; |
|
|
downloadUrlInput.value = fullUrl; |
|
|
|
|
|
switchState('complete'); |
|
|
} else { |
|
|
handleUploadError(xhr); |
|
|
} |
|
|
}); |
|
|
|
|
|
xhr.addEventListener('error', () => handleUploadError(xhr)); |
|
|
xhr.addEventListener('abort', () => console.log('Upload aborted.')); |
|
|
|
|
|
xhr.open('POST', '/upload', true); |
|
|
xhr.send(formData); |
|
|
} |
|
|
|
|
|
|
|
|
function handleCopyLink() { |
|
|
|
|
|
navigator.clipboard.writeText(downloadUrlInput.value).then(() => { |
|
|
|
|
|
copyLinkBtn.textContent = 'Copied!'; |
|
|
copyLinkBtn.classList.add('copied'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
copyLinkBtn.textContent = 'Copy'; |
|
|
copyLinkBtn.classList.remove('copied'); |
|
|
}, 2000); |
|
|
}).catch(err => { |
|
|
console.error('Failed to copy text: ', err); |
|
|
|
|
|
}); |
|
|
} |
|
|
|
|
|
function handleUploadError(xhr) { |
|
|
let message = 'An unknown network error occurred.'; |
|
|
try { |
|
|
const errorData = JSON.parse(xhr.responseText); |
|
|
message = errorData.detail || 'Upload failed.'; |
|
|
} catch (e) { |
|
|
if (xhr.statusText) { |
|
|
message = xhr.statusText; |
|
|
} |
|
|
} |
|
|
errorMessage.textContent = message; |
|
|
switchState('error'); |
|
|
} |
|
|
|
|
|
function resetUploader() { |
|
|
if (currentXHR) { |
|
|
currentXHR.abort(); |
|
|
currentXHR = null; |
|
|
} |
|
|
currentFile = null; |
|
|
fileInput.value = ''; |
|
|
downloadUrlInput.value = ''; |
|
|
switchState('initial'); |
|
|
} |
|
|
|
|
|
function formatBytes(bytes, decimals = 2) { |
|
|
if (bytes === 0) return '0 Bytes'; |
|
|
const k = 1024; |
|
|
const dm = decimals < 0 ? 0 : decimals; |
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; |
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; |
|
|
} |
|
|
|
|
|
switchState('initial'); |
|
|
}); |