mirror of
https://github.com/onionshare/onionshare.git
synced 2025-06-19 20:14:13 -04:00
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:
parent
002f91d108
commit
1b68adc6d0
5 changed files with 222 additions and 131 deletions
|
@ -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
BIN
share/static/img/ajax.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 847 B |
2
share/static/js/receive-noscript.js
Normal file
2
share/static/js/receive-noscript.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Hide the noscript div, because our javascript is executing
|
||||||
|
document.getElementById('noscript').style.display = 'none';
|
|
@ -1,94 +1,140 @@
|
||||||
// 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')
|
||||||
event.preventDefault();
|
.append(
|
||||||
|
$('<div>').addClass('upload-meta')
|
||||||
|
.append($cancel_button)
|
||||||
|
.append($upload_filename)
|
||||||
|
.append($upload_status)
|
||||||
|
)
|
||||||
|
.append($progress);
|
||||||
|
|
||||||
// Disable button, and update text
|
$cancel_button.click(function(){
|
||||||
uploadButton.innerHTML = 'Uploading ...';
|
// Abort the upload, and remove the upload div
|
||||||
uploadButton.disabled = true;
|
xhr.abort();
|
||||||
fileSelect.disabled = true;
|
$upload_div.remove()
|
||||||
|
});
|
||||||
|
|
||||||
// Create form data
|
return $upload_div;
|
||||||
var files = fileSelect.files;
|
};
|
||||||
var formData = new FormData();
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var file = files[i];
|
|
||||||
formData.append('file[]', file, file.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the request
|
// Intercept submitting the form
|
||||||
var ajax = new XMLHttpRequest();
|
$('#send').submit(function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
ajax.upload.addEventListener('progress', function(event){
|
// Create form data, and list of filenames
|
||||||
console.log('upload progress', 'uploaded '+event.loaded+' bytes / '+event.total+' bytes');
|
var files = $('#file-select').get(0).files;
|
||||||
var percent = parseInt((event.loaded / event.total) * 100, 10);
|
var filenames = [];
|
||||||
uploadButton.innerHTML = 'Uploading '+percent+'%';
|
var formData = new FormData();
|
||||||
}, false);
|
for(var i = 0; i < files.length; i++) {
|
||||||
|
var file = files[i];
|
||||||
ajax.addEventListener('load', function(event){
|
filenames.push(file.name);
|
||||||
console.log('upload finished', ajax.response);
|
formData.append('file[]', file, file.name);
|
||||||
if(ajax.status == 200) {
|
|
||||||
// Parse response
|
|
||||||
try {
|
|
||||||
var response = JSON.parse(ajax.response);
|
|
||||||
|
|
||||||
// The 'new_body' response replaces the whole HTML document and ends
|
|
||||||
if('new_body' in response) {
|
|
||||||
document.body.innerHTML = response['new_body'];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show error flashes
|
|
||||||
if('error_flashes' in response) {
|
|
||||||
for(var i=0; i<response['error_flashes'].length; i++) {
|
|
||||||
flash('error', response['error_flashes'][i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show info flashes
|
|
||||||
if('info_flashes' in response) {
|
|
||||||
for(var i=0; i<response['info_flashes'].length; i++) {
|
|
||||||
flash('info', response['info_flashes'][i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
console.log('invalid response', ajax.response);
|
|
||||||
flash('error', 'Invalid response from server: '+ajax.response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-enable button, and update text
|
|
||||||
uploadButton.innerHTML = 'Send Files';
|
|
||||||
uploadButton.disabled = false;
|
|
||||||
fileSelect.disabled = false;
|
|
||||||
}
|
}
|
||||||
}, false);
|
|
||||||
|
|
||||||
ajax.addEventListener('error', function(event){
|
// Reset the upload form
|
||||||
console.log('error', event);
|
$('#send').get(0).reset();
|
||||||
flash('error', 'Error uploading');
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
ajax.addEventListener('abort', function(event){
|
// Start upload
|
||||||
console.log('abort', event);
|
xhr = $.ajax({
|
||||||
flash('error', 'Upload aborted');
|
method: 'POST',
|
||||||
}, false);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Send the request
|
// If it's finished sending all data to the first Tor node, remove cancel button
|
||||||
ajax.open('POST', window.location.pathname + '/upload-ajax', true);
|
// and update the status
|
||||||
ajax.send(formData);
|
if(event.loaded == event.total) {
|
||||||
console.log('upload started');
|
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);
|
||||||
|
}
|
||||||
|
return xhr;
|
||||||
|
},
|
||||||
|
success: function(data, textStatus, xhr){
|
||||||
|
console.log('upload finished', data);
|
||||||
|
|
||||||
|
// Remove the upload div
|
||||||
|
xhr.$upload_div.remove();
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
try {
|
||||||
|
var response = JSON.parse(data);
|
||||||
|
|
||||||
|
// The 'new_body' response replaces the whole HTML document and ends
|
||||||
|
if('new_body' in response) {
|
||||||
|
$('body').html(response['new_body']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error flashes
|
||||||
|
if('error_flashes' in response) {
|
||||||
|
for(var i=0; i<response['error_flashes'].length; i++) {
|
||||||
|
flash('error', response['error_flashes'][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show info flashes
|
||||||
|
if('info_flashes' in response) {
|
||||||
|
for(var i=0; i<response['info_flashes'].length; i++) {
|
||||||
|
flash('info', response['info_flashes'][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.log('invalid response');
|
||||||
|
flash('error', 'Invalid response from server: '+data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, textStatus, errorThrown){
|
||||||
|
console.log('error', errorThrown);
|
||||||
|
flash('error', 'Error uploading: ' + errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('upload started', filenames);
|
||||||
|
|
||||||
|
// Make the upload div
|
||||||
|
xhr.$upload_div = new_upload_div(xhr, filenames);
|
||||||
|
$('#uploads').append(xhr.$upload_div);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -13,41 +13,46 @@
|
||||||
</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-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 }}">
|
|
||||||
<p><input type="file" id="file-select" name="file[]" multiple /></p>
|
|
||||||
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
|
|
||||||
<div>
|
|
||||||
<ul id="flashes" class="flashes">
|
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
||||||
{% if messages %}
|
|
||||||
{% for category, message in messages %}
|
|
||||||
<li class="{{ category }}">{{ message }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!--
|
<p class="upload-header">Send Files</p>
|
||||||
We are not using a <noscript> tag because it only works when the security slider is set to
|
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
|
||||||
Safest, not Safer: https://trac.torproject.org/projects/tor/ticket/29506
|
|
||||||
-->
|
|
||||||
<div id="noscript">
|
|
||||||
<p>
|
|
||||||
<strong>Warning:</strong> Due to a bug in Tor Browser and Firefox, uploads
|
|
||||||
sometimes never finish. To upload reliably, either set your Tor Browser
|
|
||||||
<a rel="noreferrer" target="_blank" href="https://tb-manual.torproject.org/en-US/security-slider/">security slider</a>
|
|
||||||
to Standard or
|
|
||||||
<a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}">
|
||||||
|
<p><input type="file" id="file-select" name="file[]" multiple /></p>
|
||||||
|
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="uploads"></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul id="flashes" class="flashes">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<li class="{{ category }}">{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We are not using a <noscript> tag because it only works when the security slider is set to
|
||||||
|
Safest, not Safer: https://trac.torproject.org/projects/tor/ticket/29506
|
||||||
|
-->
|
||||||
|
<div id="noscript">
|
||||||
|
<p>
|
||||||
|
<strong>Warning:</strong> Due to a bug in Tor Browser and Firefox, uploads
|
||||||
|
sometimes never finish. To upload reliably, either set your Tor Browser
|
||||||
|
<a rel="noreferrer" target="_blank" href="https://tb-manual.torproject.org/en-US/security-slider/">security slider</a>
|
||||||
|
to Standard or
|
||||||
|
<a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
|
||||||
|
</div>
|
||||||
|
<script src="/static/js/receive-noscript.js"></script>
|
||||||
|
|
||||||
</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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue