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 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>
<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>
<script src="upload.js"></script>
<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>
<div id="footer">
<h5>(c) 2022 xenofem, MIT licensed</h5>
<h5><a href="https://git.xeno.science/xenofem/transbeam">source</a></h5>
</div>
<script src="upload.js"></script>
</body>
</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"] {
display: none;
}
button, .fake_button {
font-size: 18px;
font-family: sans-serif;
color: #000;
background-color: #ccc;
border: 1px solid #bbb;
@ -23,3 +67,11 @@ button:disabled, input:disabled + .fake_button {
border-color: #ddd;
cursor: not-allowed;
}
#footer {
margin-top: 30px;
}
#footer h5 {
margin: 5px auto;
}

View file

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