summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2019-02-15 18:52:28 -0800
committerMicah Lee <micah@micahflee.com>2019-02-15 18:52:28 -0800
commit2d78b545a587c4334624d869997b2326dad32573 (patch)
tree40f72091cb8dc146a7823baa25a58eb3bf18a55a
parent7e709bb234552467ebabb33407a25a9f6e8f0411 (diff)
downloadonionshare-2d78b545a587c4334624d869997b2326dad32573.tar.gz
onionshare-2d78b545a587c4334624d869997b2326dad32573.zip
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
-rw-r--r--share/static/css/style.css72
-rw-r--r--share/static/img/ajax.gifbin0 -> 847 bytes
-rw-r--r--share/static/js/receive-noscript.js2
-rw-r--r--share/static/js/receive.js218
-rw-r--r--share/templates/receive.html69
5 files changed, 226 insertions, 135 deletions
diff --git a/share/static/css/style.css b/share/static/css/style.css
index 73d3e6f9..65bafba5 100644
--- a/share/static/css/style.css
+++ b/share/static/css/style.css
@@ -9,7 +9,7 @@
body {
margin: 0;
- font-family: Helvetica;
+ font-family: Helvetica, sans-serif;
}
header {
@@ -103,58 +103,96 @@ table.file-list td:last-child {
}
.upload-wrapper {
- display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
-}
-
-.upload {
text-align: center;
}
-.upload img {
+.upload-wrapper img.logo {
width: 120px;
height: 120px;
}
-.upload .upload-header {
+.upload-wrapper .upload-header {
font-size: 30px;
font-weight: normal;
color: #666666;
margin: 0 0 10px 0;
}
-.upload .upload-description {
+.upload-wrapper .upload-description {
color: #666666;
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 {
list-style: none;
margin: 0;
padding: 0;
- color: #cc0000;
- text-align: left;
+ width: 800px;
+ max-width: 90%;
+ margin: 0 auto;
}
ul.flashes li {
margin: 0 0 5px 0;
- padding: 10px;
+ padding: 5px;
list-style: none;
- border: 0;
- border-radius: 3px;
text-align: left;
}
li.error {
- color: #ffffff;
- background-color: #c90c0c;
+ color: #dd4040;
}
li.info {
- color: #000000;
- background-color: #a9e26c;
+ color: #3e933f;
}
.closed-wrapper {
diff --git a/share/static/img/ajax.gif b/share/static/img/ajax.gif
new file mode 100644
index 00000000..01d955aa
--- /dev/null
+++ b/share/static/img/ajax.gif
Binary files differ
diff --git a/share/static/js/receive-noscript.js b/share/static/js/receive-noscript.js
new file mode 100644
index 00000000..0f4ac1bc
--- /dev/null
+++ b/share/static/js/receive-noscript.js
@@ -0,0 +1,2 @@
+// Hide the noscript div, because our javascript is executing
+document.getElementById('noscript').style.display = 'none';
diff --git a/share/static/js/receive.js b/share/static/js/receive.js
index 45cbc9dc..0de80952 100644
--- a/share/static/js/receive.js
+++ b/share/static/js/receive.js
@@ -1,94 +1,140 @@
-// Hide the noscript div, because our javascript is executing
-document.getElementById('noscript').style.display = 'none';
-
-var form = document.getElementById('send');
-var fileSelect = document.getElementById('file-select');
-var uploadButton = document.getElementById('send-button');
-var flashes = document.getElementById('flashes');
-
-// Add a flash message
-function flash(category, message) {
- var el = document.createElement('li');
- el.innerText = message;
- el.className = category;
- flashes.appendChild(el);
-}
-
-form.onsubmit = function(event) {
- event.preventDefault();
-
- // Disable button, and update text
- uploadButton.innerHTML = 'Uploading ...';
- uploadButton.disabled = true;
- fileSelect.disabled = true;
-
- // Create form data
- 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
- var ajax = new XMLHttpRequest();
-
- ajax.upload.addEventListener('progress', function(event){
- console.log('upload progress', 'uploaded '+event.loaded+' bytes / '+event.total+' bytes');
- var percent = parseInt((event.loaded / event.total) * 100, 10);
- uploadButton.innerHTML = 'Uploading '+percent+'%';
- }, false);
-
- ajax.addEventListener('load', function(event){
- console.log('upload finished', ajax.response);
- 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;
+$(function(){
+ // Add a flash message
+ var flash = function(category, message) {
+ $('#flashes').append($('<li>').addClass(category).text(message));
+ };
+
+ // Add an upload
+ var new_upload_div = function(xhr, filenames) {
+ /*
+ The DOM for an upload looks something like this:
+
+ <div class="upload">
+ <div class="upload-meta">
+ <input class="cancel" type="button" value="Cancel" />
+ <div class="upload-filename">educational-video.mp4, secret-plans.pdf</div>
+ <div class="upload-status">Sending to first Tor node ...</div>
+ </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 ...');
+
+ 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();
+
+ // Create form data, and list of filenames
+ var files = $('#file-select').get(0).files;
+ var filenames = [];
+ var formData = new FormData();
+ for(var i = 0; i < files.length; i++) {
+ var file = files[i];
+ filenames.push(file.name);
+ formData.append('file[]', file, file.name);
+ }
+
+ // Reset the upload form
+ $('#send').get(0).reset();
+
+ // Start upload
+ xhr = $.ajax({
+ method: 'POST',
+ 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);
}
+ 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);
- // Show error flashes
- if('error_flashes' in response) {
- for(var i=0; i<response['error_flashes'].length; i++) {
- flash('error', response['error_flashes'][i]);
+ // 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]);
+ // 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);
}
- } catch(e) {
- console.log('invalid response', ajax.response);
- flash('error', 'Invalid response from server: '+ajax.response);
+ },
+ error: function(xhr, textStatus, errorThrown){
+ console.log('error', errorThrown);
+ flash('error', 'Error uploading: ' + errorThrown);
}
+ });
+ console.log('upload started', filenames);
- // Re-enable button, and update text
- uploadButton.innerHTML = 'Send Files';
- uploadButton.disabled = false;
- fileSelect.disabled = false;
- }
- }, false);
-
- ajax.addEventListener('error', function(event){
- console.log('error', event);
- flash('error', 'Error uploading');
- }, 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');
-}
+ // Make the upload div
+ xhr.$upload_div = new_upload_div(xhr, filenames);
+ $('#uploads').append(xhr.$upload_div);
+ });
+});
diff --git a/share/templates/receive.html b/share/templates/receive.html
index 65aeff7a..a557a967 100644
--- a/share/templates/receive.html
+++ b/share/templates/receive.html
@@ -13,41 +13,46 @@
</header>
<div class="upload-wrapper">
- <div class="upload">
- <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>
-
- <!--
- 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>
+ <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>
+ </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>
+
+ <!--
+ 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>
+ <script src="/static/js/jquery-3.3.1.min.js"></script>
<script src="/static/js/receive.js"></script>
</body>
</html>