const FILE_CHUNK_SIZE = 16384; const MAX_FILES = 256; const fileInputContainer = document.getElementById('file_input_container'); const fileInput = document.getElementById('file_input'); const fileInputMessage = document.getElementById('file_input_message'); const fileListContainer = document.getElementById('file_list_container'); const fileList = document.getElementById('file_list'); const lifetimeContainer = document.getElementById('lifetime_container'); const lifetimeInput = document.getElementById('lifetime'); const uploadButton = document.getElementById('upload'); const downloadLinkContainer = document.getElementById('download_link_container'); const downloadLinkMain = document.getElementById('download_link_main'); const downloadLink = document.getElementById('download_link'); const copiedMessage = document.getElementById('copied_message'); const progressContainer = document.getElementById('progress_container'); const progress = document.getElementById('progress'); const progressBar = document.getElementById('progress_bar'); let files = []; let socket = null; let fileIndex = 0; let byteIndex = 0; let bytesSent = 0; let totalBytes = 0; function sendManifest(lifetime) { const fileMetadata = files.map((file) => ({ name: file.name, size: file.size, modtime: file.lastModified, })); socket.send(JSON.stringify({ lifetime, files: fileMetadata })); } function finishSending() { if (socket.bufferedAmount > 0) { window.setTimeout(finishSending, 1000); return; } socket.close(); progressContainer.textContent = "Upload complete!"; fileList.style.backgroundColor = "#7af"; } function sendData() { if (fileIndex >= files.length) { finishSending(); return; } const currentFile = files[fileIndex]; if (byteIndex < currentFile.size) { const endpoint = Math.min(byteIndex+FILE_CHUNK_SIZE, currentFile.size); const data = currentFile.slice(byteIndex, endpoint); socket.send(data); byteIndex = endpoint; bytesSent += data.size; updateProgress(); } else { fileIndex += 1; byteIndex = 0; sendData(); } } function updateProgress() { let percentage; if (totalBytes === 0) { percentage = "0%"; } else { percentage = `${(bytesSent*100/totalBytes).toFixed(1)}%`; } progress.textContent = `${percentage} (${displaySize(bytesSent)}/${displaySize(totalBytes)})`; progressBar.style.backgroundSize = percentage; const fileEntries = Array.from(fileList.children); for (entry of fileEntries.slice(0, fileIndex)) { entry.style.backgroundSize = "100%"; } if (fileIndex < files.length) { const currentFile = files[fileIndex]; if (currentFile.size > 0) { fileEntries[fileIndex].style.backgroundSize = `${(byteIndex*100/currentFile.size)}%`; } } } function updateFiles() { totalBytes = files.reduce((acc, file) => acc + file.size, 0); if (files.length === 0) { fileInputMessage.textContent = 'Select files to upload...'; fileListContainer.style.display = 'none'; uploadButton.style.display = 'none'; lifetimeContainer.style.display = 'none'; } else { fileInputMessage.textContent = 'Select more files to upload...'; fileListContainer.style.display = ''; uploadButton.textContent = `Upload ${files.length} file${files.length > 1 ? 's' : ''} (${displaySize(totalBytes)})`; uploadButton.style.display = ''; lifetimeContainer.style.display = ''; } fileInput.disabled = (files.length >= MAX_FILES); } updateFiles(); downloadLinkContainer.style.display = 'none'; progressContainer.style.display = 'none'; function addFile(newFile) { if (files.length >= MAX_FILES) { return; } if (files.some((oldFile) => newFile.name === oldFile.name)) { return; } files.push(newFile); addListEntry(newFile); } function addListEntry(file) { const listEntry = document.createElement('tr'); const deleteButtonCell = document.createElement('td'); deleteButtonCell.className = 'file_delete'; deleteButtonCell.addEventListener('click', () => { removeFile(file.name); listEntry.remove(); updateFiles(); }); const sizeCell = document.createElement('td'); sizeCell.className = 'file_size'; sizeCell.textContent = displaySize(file.size); const nameCell = document.createElement('td'); nameCell.className = 'file_name'; nameCell.textContent = file.name; listEntry.appendChild(deleteButtonCell); listEntry.appendChild(sizeCell); listEntry.appendChild(nameCell); fileList.appendChild(listEntry); } function removeFile(name) { files = files.filter((file) => file.name !== name); } fileInput.addEventListener('input', (e) => { for (const file of e.target.files) { addFile(file); } updateFiles(); e.target.value = ''; }); uploadButton.addEventListener('click', (e) => { if (files.length === 0) { return; } const lifetime = parseInt(lifetimeInput.value); lifetimeContainer.remove(); fileInputContainer.remove(); for (const button of Array.from(document.getElementsByTagName('button')).concat(...document.getElementsByClassName('file_delete'))) { button.remove(); } socket = new WebSocket(`${window.location.protocol === 'http:' ? 'ws' : 'wss'}://${window.location.host}/upload`); socket.addEventListener('open', () => sendManifest(lifetime)); socket.addEventListener('message', (msg) => { if (bytesSent === 0) { const reply = JSON.parse(msg.data); if (reply.type === 'ready' && reply.code.match(/^[A-Za-z0-9]+$/)) { downloadLink.textContent = `${window.location.origin}/download/${reply.code}`; downloadLinkContainer.style.display = ''; updateProgress(); progressContainer.style.display = ''; sendData(); } } else if (msg.data === 'ack') { sendData(); } }); }) downloadLinkContainer.addEventListener('click', (e) => { navigator.clipboard.writeText(downloadLink.textContent); downloadLinkMain.style.visibility = 'hidden'; copiedMessage.style.display = ''; }) downloadLinkContainer.addEventListener('mouseleave', (e) => { copiedMessage.style.display = 'none'; downloadLinkMain.style.visibility = 'visible'; });