actual nice UI

This commit is contained in:
xenofem 2022-04-27 23:52:45 -04:00
parent 55fa969bea
commit d055bb7a7a
4 changed files with 172 additions and 42 deletions

View file

@ -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 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>

View file

@ -1,10 +1,54 @@
body {
text-align: center;
font-family: sans-serif;
max-width: 512px;
margin-left: auto;
margin-right: auto;
}
#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 +67,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;
}

View file

@ -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
View 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`;
}