Refactor file upload interface to use jQuery, and to have separate upload progress bars. The progess bars make clear when the data has been sent to the first Tor node, and when its waiting on the rest of the Tor network. It also allows the sender to cancel uploads

This commit is contained in:
Micah Lee 2019-02-15 18:52:28 -08:00
parent 002f91d108
commit 1b68adc6d0
5 changed files with 222 additions and 131 deletions

View file

@ -9,7 +9,7 @@
body { body {
margin: 0; margin: 0;
font-family: Helvetica; font-family: Helvetica, sans-serif;
} }
header { header {
@ -103,58 +103,96 @@ table.file-list td:last-child {
} }
.upload-wrapper { .upload-wrapper {
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 400px; min-height: 400px;
}
.upload {
text-align: center; text-align: center;
} }
.upload img { .upload-wrapper img.logo {
width: 120px; width: 120px;
height: 120px; height: 120px;
} }
.upload .upload-header { .upload-wrapper .upload-header {
font-size: 30px; font-size: 30px;
font-weight: normal; font-weight: normal;
color: #666666; color: #666666;
margin: 0 0 10px 0; margin: 0 0 10px 0;
} }
.upload .upload-description { .upload-wrapper .upload-description {
color: #666666; color: #666666;
margin: 0 0 20px 0; margin: 0 0 20px 0;
} }
div#uploads {
width: 800px;
max-width: 90%;
margin: 0 auto;
}
div#uploads .upload {
border: 1px solid #DDDDDD;
margin: 20px 0;
padding: 10px;
text-align: left;
}
div#uploads .upload .upload-filename {
font-weight: bold;
font-family: monospace;
font-size: 1.1em;
margin-bottom: 5px;
}
div#uploads .upload .upload-status {
color: #999999;
font-size: 0.9em;
margin-bottom: 5px;
}
div#uploads .upload input.cancel {
color: #f24537;
border: 0;
background: none;
box-shadow: none;
border-radius: 0px;
cursor: pointer;
font-family: sans-serif;
font-size: 12px;
text-decoration: none;
display: inline-block;
float:right;
}
div#uploads .upload progress {
width: 100%;
height: 20px;
}
ul.flashes { ul.flashes {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
color: #cc0000; width: 800px;
text-align: left; max-width: 90%;
margin: 0 auto;
} }
ul.flashes li { ul.flashes li {
margin: 0 0 5px 0; margin: 0 0 5px 0;
padding: 10px; padding: 5px;
list-style: none; list-style: none;
border: 0;
border-radius: 3px;
text-align: left; text-align: left;
} }
li.error { li.error {
color: #ffffff; color: #dd4040;
background-color: #c90c0c;
} }
li.info { li.info {
color: #000000; color: #3e933f;
background-color: #a9e26c;
} }
.closed-wrapper { .closed-wrapper {

BIN
share/static/img/ajax.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

View file

@ -0,0 +1,2 @@
// Hide the noscript div, because our javascript is executing
document.getElementById('noscript').style.display = 'none';

View file

@ -1,54 +1,110 @@
// Hide the noscript div, because our javascript is executing $(function(){
document.getElementById('noscript').style.display = 'none'; // Add a flash message
var flash = function(category, message) {
$('#flashes').append($('<li>').addClass(category).text(message));
};
var form = document.getElementById('send'); // Add an upload
var fileSelect = document.getElementById('file-select'); var new_upload_div = function(xhr, filenames) {
var uploadButton = document.getElementById('send-button'); /*
var flashes = document.getElementById('flashes'); The DOM for an upload looks something like this:
// Add a flash message <div class="upload">
function flash(category, message) { <div class="upload-meta">
var el = document.createElement('li'); <input class="cancel" type="button" value="Cancel" />
el.innerText = message; <div class="upload-filename">educational-video.mp4, secret-plans.pdf</div>
el.className = category; <div class="upload-status">Sending to first Tor node ...</div>
flashes.appendChild(el); </div>
} <progress value="25" max="100"></progress>
</div>
*/
var $progress = $('<progress>').attr({ value: '0', max: 100 });
var $cancel_button = $('<input>').addClass('cancel').attr({ type: 'button', value: 'Cancel' });
var $upload_filename = $('<div>').addClass('upload-filename').text(filenames.join(', '));
var $upload_status = $('<div>').addClass('upload-status').text('Sending data to initial Tor node ...');
form.onsubmit = function(event) { var $upload_div = $('<div>').addClass('upload')
.append(
$('<div>').addClass('upload-meta')
.append($cancel_button)
.append($upload_filename)
.append($upload_status)
)
.append($progress);
$cancel_button.click(function(){
// Abort the upload, and remove the upload div
xhr.abort();
$upload_div.remove()
});
return $upload_div;
};
// Intercept submitting the form
$('#send').submit(function(event){
event.preventDefault(); event.preventDefault();
// Disable button, and update text // Create form data, and list of filenames
uploadButton.innerHTML = 'Uploading ...'; var files = $('#file-select').get(0).files;
uploadButton.disabled = true; var filenames = [];
fileSelect.disabled = true;
// Create form data
var files = fileSelect.files;
var formData = new FormData(); var formData = new FormData();
for (var i = 0; i < files.length; i++) { for(var i = 0; i < files.length; i++) {
var file = files[i]; var file = files[i];
filenames.push(file.name);
formData.append('file[]', file, file.name); formData.append('file[]', file, file.name);
} }
// Set up the request // Reset the upload form
var ajax = new XMLHttpRequest(); $('#send').get(0).reset();
ajax.upload.addEventListener('progress', function(event){ // Start upload
console.log('upload progress', 'uploaded '+event.loaded+' bytes / '+event.total+' bytes'); xhr = $.ajax({
var percent = parseInt((event.loaded / event.total) * 100, 10); method: 'POST',
uploadButton.innerHTML = 'Uploading '+percent+'%'; url: window.location.pathname + '/upload-ajax',
data: formData,
// Tell jQuery not to process data or worry about content-type
cache: false,
contentType: false,
processData: false,
// Custom XMLHttpRequest
xhr: function() {
var xhr = $.ajaxSettings.xhr();
if(xhr.upload) {
xhr.upload.addEventListener('progress', function(event) {
// Update progress bar for this specific upload
if(event.lengthComputable) {
console.log('upload progress', ''+event.loaded+' bytes / '+event.total+' bytes');
$('progress', this.$upload_div).attr({
value: event.loaded,
max: event.total,
});
}
// If it's finished sending all data to the first Tor node, remove cancel button
// and update the status
if(event.loaded == event.total) {
console.log('upload progress', 'complete');
$('.cancel', this.$upload_div).remove();
$('.upload-status', this.$upload_div).html('<img src="/static/img/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...');
}
}, false); }, false);
}
return xhr;
},
success: function(data, textStatus, xhr){
console.log('upload finished', data);
// Remove the upload div
xhr.$upload_div.remove();
ajax.addEventListener('load', function(event){
console.log('upload finished', ajax.response);
if(ajax.status == 200) {
// Parse response // Parse response
try { try {
var response = JSON.parse(ajax.response); var response = JSON.parse(data);
// The 'new_body' response replaces the whole HTML document and ends // The 'new_body' response replaces the whole HTML document and ends
if('new_body' in response) { if('new_body' in response) {
document.body.innerHTML = response['new_body']; $('body').html(response['new_body']);
return; return;
} }
@ -66,29 +122,19 @@ form.onsubmit = function(event) {
} }
} }
} catch(e) { } catch(e) {
console.log('invalid response', ajax.response); console.log('invalid response');
flash('error', 'Invalid response from server: '+ajax.response); flash('error', 'Invalid response from server: '+data);
} }
},
// Re-enable button, and update text error: function(xhr, textStatus, errorThrown){
uploadButton.innerHTML = 'Send Files'; console.log('error', errorThrown);
uploadButton.disabled = false; flash('error', 'Error uploading: ' + errorThrown);
fileSelect.disabled = false;
} }
}, false); });
console.log('upload started', filenames);
ajax.addEventListener('error', function(event){ // Make the upload div
console.log('error', event); xhr.$upload_div = new_upload_div(xhr, filenames);
flash('error', 'Error uploading'); $('#uploads').append(xhr.$upload_div);
}, false); });
});
ajax.addEventListener('abort', function(event){
console.log('abort', event);
flash('error', 'Upload aborted');
}, false);
// Send the request
ajax.open('POST', window.location.pathname + '/upload-ajax', true);
ajax.send(formData);
console.log('upload started');
}

View file

@ -13,13 +13,18 @@
</header> </header>
<div class="upload-wrapper"> <div class="upload-wrapper">
<div class="upload">
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p> <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
<p class="upload-header">Send Files</p> <p class="upload-header">Send Files</p>
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p> <p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
<form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}"> <form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}">
<p><input type="file" id="file-select" name="file[]" multiple /></p> <p><input type="file" id="file-select" name="file[]" multiple /></p>
<p><button type="submit" id="send-button" class="button">Send Files</button></p> <p><button type="submit" id="send-button" class="button">Send Files</button></p>
</form>
<div id="uploads"></div>
<div> <div>
<ul id="flashes" class="flashes"> <ul id="flashes" class="flashes">
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
@ -31,7 +36,6 @@
{% endwith %} {% endwith %}
</ul> </ul>
</div> </div>
</form>
<!-- <!--
We are not using a <noscript> tag because it only works when the security slider is set to We are not using a <noscript> tag because it only works when the security slider is set to
@ -45,9 +49,10 @@
to Standard or to Standard or
<a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p> <a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
</div> </div>
<script src="/static/js/receive-noscript.js"></script>
</div> </div>
</div> <script src="/static/js/jquery-3.3.1.min.js"></script>
<script src="/static/js/receive.js"></script> <script src="/static/js/receive.js"></script>
</body> </body>
</html> </html>