Compare commits
3 commits
55fa969bea
...
acd58dbaa5
Author | SHA1 | Date | |
---|---|---|---|
xenofem | acd58dbaa5 | ||
xenofem | c81725a8e7 | ||
xenofem | d055bb7a7a |
|
@ -4,21 +4,41 @@
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<link rel="stylesheet" type="text/css" href="transbeam.css"/>
|
<link rel="stylesheet" type="text/css" href="transbeam.css"/>
|
||||||
|
<script src="util.js"></script>
|
||||||
<title>Upload Test</title>
|
<title>Upload Test</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div id="header">
|
||||||
|
<img src="transbeam.svg" height="128">
|
||||||
|
<h1>transbeam</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>This page requires Javascript :(</noscript>
|
||||||
|
|
||||||
|
<button id="upload">Upload</button>
|
||||||
|
<div id="download_link_container" style="display: none;">
|
||||||
|
Download link: <span id="download_link"></span>
|
||||||
|
</div>
|
||||||
|
<div id="progress_container" style="display: none;">
|
||||||
|
<div id="progress"></div>
|
||||||
|
<div id="progress_bar">
|
||||||
|
<div id="progress_bar_filled"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="file_list_container" style="display: none;">
|
||||||
|
<table id="file_list">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="file_input_container">
|
||||||
<label>
|
<label>
|
||||||
<input type="file" multiple id="file_input"/>
|
<input type="file" multiple id="file_input"/>
|
||||||
<span class="fake_button" id="file_input_message">Select files to upload...</span>
|
<span class="fake_button" id="file_input_message">Select files to upload...</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button id="upload" disabled>Upload</button>
|
<div id="footer">
|
||||||
<h2>Files selected:</h2>
|
<h5>(c) 2022 xenofem, MIT licensed</h5>
|
||||||
<ul id="file_list">
|
<h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5>
|
||||||
</ul>
|
</div>
|
||||||
<div id="download_link"></div>
|
|
||||||
<div id="progress"></div>
|
|
||||||
<script src="upload.js"></script>
|
<script src="upload.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,10 +1,58 @@
|
||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
max-width: 512px;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h1 {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress_container {
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress_bar, #progress_bar_filled {
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
#progress_bar {
|
||||||
|
border: 1px solid #48f;
|
||||||
|
}
|
||||||
|
#progress_bar_filled {
|
||||||
|
width: 0;
|
||||||
|
background-color: #27f;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr + tr td {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.file_size {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.file_name {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, .fake_button {
|
button, .fake_button {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-family: sans-serif;
|
|
||||||
color: #000;
|
color: #000;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
border: 1px solid #bbb;
|
border: 1px solid #bbb;
|
||||||
|
@ -23,3 +71,11 @@ button:disabled, input:disabled + .fake_button {
|
||||||
border-color: #ddd;
|
border-color: #ddd;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer h5 {
|
||||||
|
margin: 5px auto;
|
||||||
|
}
|
||||||
|
|
100
static/upload.js
100
static/upload.js
|
@ -1,5 +1,21 @@
|
||||||
const FILE_CHUNK_SIZE = 16384;
|
const FILE_CHUNK_SIZE = 16384;
|
||||||
|
|
||||||
|
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 uploadButton = document.getElementById('upload');
|
||||||
|
|
||||||
|
const downloadLinkContainer = document.getElementById('download_link_container');
|
||||||
|
const downloadLink = document.getElementById('download_link');
|
||||||
|
|
||||||
|
const progressContainer = document.getElementById('progress_container');
|
||||||
|
const progress = document.getElementById('progress');
|
||||||
|
const progressBarFilled = document.getElementById('progress_bar_filled');
|
||||||
|
|
||||||
let files = [];
|
let files = [];
|
||||||
|
|
||||||
let socket = null;
|
let socket = null;
|
||||||
|
@ -23,7 +39,7 @@ function finishSending() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket.close();
|
socket.close();
|
||||||
alert("done");
|
progressContainer.textContent = "Upload complete!";
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData() {
|
function sendData() {
|
||||||
|
@ -38,7 +54,7 @@ function sendData() {
|
||||||
socket.send(data);
|
socket.send(data);
|
||||||
byteIndex = endpoint;
|
byteIndex = endpoint;
|
||||||
bytesSent += data.size;
|
bytesSent += data.size;
|
||||||
progress.textContent = `${Math.floor(bytesSent * 100 / totalBytes)}%`;
|
updateProgress();
|
||||||
} else {
|
} else {
|
||||||
fileIndex += 1;
|
fileIndex += 1;
|
||||||
byteIndex = 0;
|
byteIndex = 0;
|
||||||
|
@ -46,42 +62,69 @@ function sendData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileInput = document.getElementById('file_input');
|
function updateProgress() {
|
||||||
const fileInputMessage = document.getElementById('file_input_message');
|
let percentage;
|
||||||
const fileList = document.getElementById('file_list');
|
if (totalBytes === 0) {
|
||||||
const uploadButton = document.getElementById('upload');
|
percentage = "0%";
|
||||||
const downloadLink = document.getElementById('download_link');
|
} else {
|
||||||
const progress = document.getElementById('progress');
|
percentage = `${(bytesSent*100/totalBytes).toFixed(1)}%`;
|
||||||
|
}
|
||||||
function updateButtons() {
|
progress.textContent = percentage;
|
||||||
if (files.length === 0) {
|
progressBarFilled.style.width = percentage;
|
||||||
uploadButton.disabled = true;
|
}
|
||||||
fileInputMessage.textContent = 'Select files to upload...';
|
|
||||||
|
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';
|
||||||
} else {
|
} else {
|
||||||
uploadButton.disabled = false;
|
|
||||||
fileInputMessage.textContent = 'Select more files to upload...';
|
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 = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateButtons();
|
updateFiles();
|
||||||
|
downloadLinkContainer.style.display = 'none';
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
|
||||||
function addFile(newFile) {
|
function addFile(newFile) {
|
||||||
if (files.some((oldFile) => newFile.name === oldFile.name)) { return; }
|
if (files.some((oldFile) => newFile.name === oldFile.name)) { return; }
|
||||||
|
|
||||||
files.push(newFile);
|
files.push(newFile);
|
||||||
|
|
||||||
const listEntry = document.createElement('li');
|
addListEntry(newFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListEntry(file) {
|
||||||
|
const listEntry = document.createElement('tr');
|
||||||
|
|
||||||
|
const deleteButtonCell = document.createElement('td');
|
||||||
const deleteButton = document.createElement('button');
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButtonCell.appendChild(deleteButton);
|
||||||
|
deleteButton.className = 'file_delete';
|
||||||
deleteButton.textContent = 'x';
|
deleteButton.textContent = 'x';
|
||||||
deleteButton.addEventListener('click', () => {
|
deleteButton.addEventListener('click', () => {
|
||||||
removeFile(newFile.name);
|
removeFile(file.name);
|
||||||
listEntry.remove();
|
listEntry.remove();
|
||||||
updateButtons();
|
updateFiles();
|
||||||
});
|
});
|
||||||
const entryName = document.createElement('span');
|
|
||||||
entryName.textContent = newFile.name;
|
const sizeCell = document.createElement('td');
|
||||||
listEntry.appendChild(deleteButton);
|
sizeCell.className = 'file_size';
|
||||||
listEntry.appendChild(entryName);
|
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);
|
fileList.appendChild(listEntry);
|
||||||
}
|
}
|
||||||
|
@ -92,25 +135,26 @@ function removeFile(name) {
|
||||||
|
|
||||||
fileInput.addEventListener('input', (e) => {
|
fileInput.addEventListener('input', (e) => {
|
||||||
for (const file of e.target.files) { addFile(file); }
|
for (const file of e.target.files) { addFile(file); }
|
||||||
updateButtons();
|
updateFiles();
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadButton.addEventListener('click', (e) => {
|
uploadButton.addEventListener('click', (e) => {
|
||||||
if (files.length === 0) { return; }
|
if (files.length === 0) { return; }
|
||||||
|
|
||||||
fileInput.disabled = true;
|
fileInputContainer.remove();
|
||||||
for (const button of document.getElementsByTagName('button')) {
|
for (const button of Array.from(document.getElementsByTagName('button'))) {
|
||||||
button.disabled = true;
|
button.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
totalBytes = files.reduce((acc, file) => acc + file.size, 0);
|
|
||||||
|
|
||||||
socket = new WebSocket(`${window.location.protocol === 'http:' ? 'ws' : 'wss'}://${window.location.host}/upload`);
|
socket = new WebSocket(`${window.location.protocol === 'http:' ? 'ws' : 'wss'}://${window.location.host}/upload`);
|
||||||
socket.addEventListener('open', sendMetadata);
|
socket.addEventListener('open', sendMetadata);
|
||||||
socket.addEventListener('message', (msg) => {
|
socket.addEventListener('message', (msg) => {
|
||||||
if (bytesSent === 0 && msg.data.match(/^[A-Za-z0-9]+$/)) {
|
if (bytesSent === 0 && msg.data.match(/^[A-Za-z0-9]+$/)) {
|
||||||
downloadLink.textContent = `${window.location.origin}/download/${msg.data}`;
|
downloadLink.textContent = `${window.location.origin}/download/${msg.data}`;
|
||||||
|
downloadLinkContainer.style.display = '';
|
||||||
|
updateProgress();
|
||||||
|
progressContainer.style.display = '';
|
||||||
sendData();
|
sendData();
|
||||||
} else if (msg.data === 'ack') {
|
} else if (msg.data === 'ack') {
|
||||||
sendData();
|
sendData();
|
||||||
|
|
14
static/util.js
Normal file
14
static/util.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const UNITS = [
|
||||||
|
{ name: 'GB', size: Math.pow(2, 30) },
|
||||||
|
{ name: 'MB', size: Math.pow(2, 20) },
|
||||||
|
{ name: 'KB', size: Math.pow(2, 10) },
|
||||||
|
];
|
||||||
|
|
||||||
|
function displaySize(bytes) {
|
||||||
|
for (const unit of UNITS) {
|
||||||
|
if (bytes >= unit.size) {
|
||||||
|
return `${(bytes / unit.size).toFixed(1)}${unit.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${bytes}B`;
|
||||||
|
}
|
Loading…
Reference in a new issue