Compare commits
3 commits
55fa969bea
...
acd58dbaa5
Author | SHA1 | Date | |
---|---|---|---|
xenofem | acd58dbaa5 | ||
xenofem | c81725a8e7 | ||
xenofem | d055bb7a7a |
|
@ -4,21 +4,41 @@
|
|||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="stylesheet" type="text/css" href="transbeam.css"/>
|
||||
<script src="util.js"></script>
|
||||
<title>Upload Test</title>
|
||||
</head>
|
||||
<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>
|
||||
<input type="file" multiple id="file_input"/>
|
||||
<span class="fake_button" id="file_input_message">Select files to upload...</span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="upload" disabled>Upload</button>
|
||||
<h2>Files selected:</h2>
|
||||
<ul id="file_list">
|
||||
</ul>
|
||||
<div id="download_link"></div>
|
||||
<div id="progress"></div>
|
||||
<div id="footer">
|
||||
<h5>(c) 2022 xenofem, MIT licensed</h5>
|
||||
<h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5>
|
||||
</div>
|
||||
<script src="upload.js"></script>
|
||||
</body>
|
||||
</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"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button, .fake_button {
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
color: #000;
|
||||
background-color: #ccc;
|
||||
border: 1px solid #bbb;
|
||||
|
@ -23,3 +71,11 @@ button:disabled, input:disabled + .fake_button {
|
|||
border-color: #ddd;
|
||||
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 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 socket = null;
|
||||
|
@ -23,7 +39,7 @@ function finishSending() {
|
|||
return;
|
||||
}
|
||||
socket.close();
|
||||
alert("done");
|
||||
progressContainer.textContent = "Upload complete!";
|
||||
}
|
||||
|
||||
function sendData() {
|
||||
|
@ -38,7 +54,7 @@ function sendData() {
|
|||
socket.send(data);
|
||||
byteIndex = endpoint;
|
||||
bytesSent += data.size;
|
||||
progress.textContent = `${Math.floor(bytesSent * 100 / totalBytes)}%`;
|
||||
updateProgress();
|
||||
} else {
|
||||
fileIndex += 1;
|
||||
byteIndex = 0;
|
||||
|
@ -46,42 +62,69 @@ function sendData() {
|
|||
}
|
||||
}
|
||||
|
||||
const fileInput = document.getElementById('file_input');
|
||||
const fileInputMessage = document.getElementById('file_input_message');
|
||||
const fileList = document.getElementById('file_list');
|
||||
const uploadButton = document.getElementById('upload');
|
||||
const downloadLink = document.getElementById('download_link');
|
||||
const progress = document.getElementById('progress');
|
||||
|
||||
function updateButtons() {
|
||||
if (files.length === 0) {
|
||||
uploadButton.disabled = true;
|
||||
fileInputMessage.textContent = 'Select files to upload...';
|
||||
function updateProgress() {
|
||||
let percentage;
|
||||
if (totalBytes === 0) {
|
||||
percentage = "0%";
|
||||
} else {
|
||||
percentage = `${(bytesSent*100/totalBytes).toFixed(1)}%`;
|
||||
}
|
||||
progress.textContent = percentage;
|
||||
progressBarFilled.style.width = percentage;
|
||||
}
|
||||
|
||||
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 {
|
||||
uploadButton.disabled = false;
|
||||
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) {
|
||||
if (files.some((oldFile) => newFile.name === oldFile.name)) { return; }
|
||||
|
||||
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');
|
||||
deleteButtonCell.appendChild(deleteButton);
|
||||
deleteButton.className = 'file_delete';
|
||||
deleteButton.textContent = 'x';
|
||||
deleteButton.addEventListener('click', () => {
|
||||
removeFile(newFile.name);
|
||||
removeFile(file.name);
|
||||
listEntry.remove();
|
||||
updateButtons();
|
||||
updateFiles();
|
||||
});
|
||||
const entryName = document.createElement('span');
|
||||
entryName.textContent = newFile.name;
|
||||
listEntry.appendChild(deleteButton);
|
||||
listEntry.appendChild(entryName);
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -92,25 +135,26 @@ function removeFile(name) {
|
|||
|
||||
fileInput.addEventListener('input', (e) => {
|
||||
for (const file of e.target.files) { addFile(file); }
|
||||
updateButtons();
|
||||
updateFiles();
|
||||
e.target.value = '';
|
||||
});
|
||||
|
||||
uploadButton.addEventListener('click', (e) => {
|
||||
if (files.length === 0) { return; }
|
||||
|
||||
fileInput.disabled = true;
|
||||
for (const button of document.getElementsByTagName('button')) {
|
||||
button.disabled = true;
|
||||
fileInputContainer.remove();
|
||||
for (const button of Array.from(document.getElementsByTagName('button'))) {
|
||||
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.addEventListener('open', sendMetadata);
|
||||
socket.addEventListener('message', (msg) => {
|
||||
if (bytesSent === 0 && msg.data.match(/^[A-Za-z0-9]+$/)) {
|
||||
downloadLink.textContent = `${window.location.origin}/download/${msg.data}`;
|
||||
downloadLinkContainer.style.display = '';
|
||||
updateProgress();
|
||||
progressContainer.style.display = '';
|
||||
sendData();
|
||||
} else if (msg.data === 'ack') {
|
||||
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