transbeam/static/transbeam.js

199 lines
6.4 KiB
JavaScript

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';
});