-
-
Download link:
+
-
Copied!
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/static/states.css b/static/states.css
deleted file mode 100644
index 3281c04..0000000
--- a/static/states.css
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * List of classes the body can have:
- *
- * no_files: no files are selected
- * selecting: upload hasn't started yet
- * uploading: upload is in progress
- * completed: upload is done
- * error: an error has occurred
- */
-
-#message { display: none; }
-body.completed #message {
- display: revert;
- color: #060;
- border-color: #0a0;
-}
-body.error #message {
- display: revert;
- color: #d00;
- border-color: #f24;
-}
-
-#upload_controls { display: none; }
-body.selecting #upload_controls { display: revert; }
-body.no_files #upload_controls { display: none; }
-
-body.selecting #download_link_container { display: none; }
-
-#progress_container { display: none; }
-body.uploading #progress_container { display: revert; }
-
-body.no_files #file_list { display: none; }
-body.completed #file_list { background-color: #7af; }
-
-.delete_button { display: none; }
-body.selecting .delete_button { display: revert; }
-
-#file_input_container { display: none; }
-body.selecting #file_input_container { display: revert; }
diff --git a/static/transbeam.css b/static/transbeam.css
index c14b2ab..19dfa7e 100644
--- a/static/transbeam.css
+++ b/static/transbeam.css
@@ -10,14 +10,6 @@ body {
margin-top: 5px;
}
-#message {
- border: 1px solid;
- border-radius: 4px;
- padding: 10px;
- width: fit-content;
- margin: 10px auto;
-}
-
#progress_container {
margin: 10px auto;
}
@@ -72,15 +64,7 @@ body {
bottom: 0;
margin: auto;
height: fit-content;
- display: none;
}
-#download_link_container.copied #copied_message {
- display: revert;
-}
-#download_link_container.copied #download_link_main {
- visibility: hidden;
-}
-
table {
border-collapse: collapse;
@@ -101,7 +85,7 @@ td {
padding: 10px;
}
-.delete_button {
+td.file_delete {
background-color: #888;
mask-image: url("images/feather-icons/x.svg");
mask-size: contain;
@@ -112,7 +96,7 @@ td {
cursor: pointer;
}
-.delete_button:hover {
+td.file_delete:hover {
background-color: #f00;
}
@@ -143,7 +127,7 @@ button:hover, .fake_button:hover {
}
button:disabled, input:disabled + .fake_button {
- color: #666;
+ color: #aaa;
background-color: #eee;
border-color: #ddd;
cursor: not-allowed;
diff --git a/static/transbeam.js b/static/transbeam.js
index 857ea16..0a6a0e9 100644
--- a/static/transbeam.js
+++ b/static/transbeam.js
@@ -1,135 +1,36 @@
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;
+let socket = null;
let fileIndex = 0;
let byteIndex = 0;
let bytesSent = 0;
let totalBytes = 0;
-let maxSize = null;
-
-let messageBox;
-let fileInput;
-let fileList;
-let uploadButton;
-let lifetimeInput;
-let downloadLink;
-let progress;
-let progressBar;
-
-document.addEventListener('DOMContentLoaded', () => {
- messageBox = document.getElementById('message');
- fileInput = document.getElementById('file_input');
- fileList = document.getElementById('file_list');
- uploadButton = document.getElementById('upload');
- lifetimeInput = document.getElementById('lifetime');
- downloadLink = document.getElementById('download_link');
- progress = document.getElementById('progress');
- progressBar = document.getElementById('progress_bar');
-
- fileInput.addEventListener('input', () => {
- for (const file of fileInput.files) { addFile(file); }
- updateFiles();
- fileInput.value = '';
- });
-
- uploadButton.addEventListener('click', beginUpload);
-
- const downloadLinkContainer = document.getElementById('download_link_container');
- downloadLinkContainer.addEventListener('click', () => {
- navigator.clipboard.writeText(downloadLink.textContent);
- downloadLinkContainer.className = 'copied';
- });
- downloadLinkContainer.addEventListener('mouseleave', () => {
- downloadLinkContainer.className = '';
- });
-
- updateFiles();
-});
-
-function updateFiles() {
- const fileInputMessage = document.getElementById('file_input_message');
-
- totalBytes = files.reduce((acc, file) => acc + file.size, 0);
-
- fileInput.disabled = (files.length >= MAX_FILES || (maxSize !== null && totalBytes >= maxSize));
-
- let extraClasses = '';
- if (maxSize !== null && totalBytes > maxSize) {
- uploadButton.disabled = true;
- displayError(`The maximum size for uploads is ${displaySize(maxSize)}`);
- extraClasses = ' error';
- } else {
- uploadButton.disabled = false;
- }
-
- if (files.length === 0) {
- fileInputMessage.textContent = 'Select files to upload...';
- document.body.className = 'no_files selecting' + extraClasses;
- } else {
- fileInputMessage.textContent = 'Select more files to upload...';
- uploadButton.textContent = `Upload ${files.length} file${files.length > 1 ? 's' : ''} (${displaySize(totalBytes)})`;
- document.body.className = 'selecting' + extraClasses;
- }
-}
-
-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 = 'delete_button';
- 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);
-}
-
-function beginUpload() {
- if (files.length === 0 || files.length > MAX_FILES) { return; }
-
- fileIndex = 0;
- byteIndex = 0;
- bytesSent = 0;
-
- socket = new WebSocket(`${window.location.protocol === 'http:' ? 'ws' : 'wss'}://${window.location.host}/upload`);
- socket.addEventListener('open', sendManifest);
- socket.addEventListener('message', handleMessage);
- socket.addEventListener('close', handleClose);
-}
-
-function sendManifest() {
- const lifetime = parseInt(lifetimeInput.value);
+function sendManifest(lifetime) {
const fileMetadata = files.map((file) => ({
name: file.name,
size: file.size,
@@ -138,55 +39,14 @@ function sendManifest() {
socket.send(JSON.stringify({ lifetime, files: fileMetadata }));
}
-function handleMessage(msg) {
- if (bytesSent === 0) {
- let reply;
- try {
- reply = JSON.parse(msg.data);
- } catch (error) {
- socket.close();
- displayError('Received an invalid response from the server');
- console.error(error);
- return;
- }
- if (reply.type === 'ready') {
- downloadLink.textContent = `${window.location.origin}/download/${reply.code}`;
- updateProgress();
- document.body.className = 'uploading';
- sendData();
- return;
- }
-
- // we're going to display a more useful error message
- socket.removeEventListener('close', handleClose);
- socket.close();
- if (reply.type === 'too_big') {
- maxSize = reply.max_size;
- updateFiles();
- } else if (reply.type === 'too_long') {
- let options = Array.from(lifetimeInput.options);
- options.reverse();
- for (const option of options) {
- if (option.value > reply.max_days) {
- option.disabled = true;
- } else {
- option.selected = true;
- break;
- }
- }
- displayError(`The maximum retention time for uploads is ${reply.max_days} days`);
- } else if (reply.type === 'error') {
- displayError(reply.details);
- }
- } else {
- if (msg.data === 'ack') {
- sendData();
- } else {
- console.error('Received unexpected message from server instead of ack', msg.data);
- displayError();
- socket.close();
- }
+function finishSending() {
+ if (socket.bufferedAmount > 0) {
+ window.setTimeout(finishSending, 1000);
+ return;
}
+ socket.close();
+ progressContainer.textContent = "Upload complete!";
+ fileList.style.backgroundColor = "#7af";
}
function sendData() {
@@ -231,49 +91,108 @@ function updateProgress() {
}
}
-function handleClose(e) {
- console.log('Websocket closed', e);
- if (fileIndex >= files.length) {
- displayCompletion();
+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 {
- let error;
- if (e.code === 1011) {
- if (e.reason) {
- error = `Server error: ${e.reason}`;
- } else {
- error = "A server error has occurred."
+ 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 (e.reason) {
- error = e.reason;
+ } else if (msg.data === 'ack') {
+ sendData();
}
- displayError(error);
- }
-}
+ });
+})
-function finishSending() {
- if (socket.bufferedAmount > 0) {
- window.setTimeout(finishSending, 1000);
- return;
- }
- socket.close();
- displayCompletion();
-}
+downloadLinkContainer.addEventListener('click', (e) => {
+ navigator.clipboard.writeText(downloadLink.textContent);
+ downloadLinkMain.style.visibility = 'hidden';
+ copiedMessage.style.display = '';
+})
-function displayCompletion() {
- messageBox.textContent = 'Upload complete!';
- document.body.className = 'completed';
- removePerFileProgressBars(); // completed file backgrounds are handled in CSS
-}
-
-function displayError(error) {
- messageBox.textContent = error || 'An error has occurred.';
- document.body.className = 'selecting error';
- removePerFileProgressBars();
-}
-
-function removePerFileProgressBars() {
- const fileEntries = Array.from(fileList.children);
- for (entry of fileEntries) {
- entry.style.backgroundSize = "0%";
- }
-}
+downloadLinkContainer.addEventListener('mouseleave', (e) => {
+ copiedMessage.style.display = 'none';
+ downloadLinkMain.style.visibility = 'visible';
+});