client/posts: add upload cancelling

This commit is contained in:
rr- 2016-09-29 21:36:59 +02:00
parent 049a0dc351
commit 7862fecbc9
6 changed files with 87 additions and 21 deletions

View File

@ -1,5 +1,7 @@
@import colors @import colors
$cancel-button-color = tomato
#post-upload #post-upload
form form
width: 100% width: 100%
@ -19,6 +21,17 @@
input[type=submit] input[type=submit]
margin-top: 1em margin-top: 1em
&[disabled]
display: none
.cancel
margin-top: 1em
background: $cancel-button-color
border-color: $cancel-button-color
&[disabled]
display: none
&:focus
border: 2px solid $text-color
.messages .messages
margin-top: 1em margin-top: 1em

View File

@ -3,6 +3,7 @@
<div class='dropper-container'></div> <div class='dropper-container'></div>
<input type='submit' value='Upload all' class='submit'/> <input type='submit' value='Upload all' class='submit'/>
<input type='button' value='Cancel' class='cancel'/>
<div class='messages'></div> <div class='messages'></div>

View File

@ -64,11 +64,13 @@ class Api extends events.EventTarget {
_process(url, requestFactory, data, files, options) { _process(url, requestFactory, data, files, options) {
options = options || {}; options = options || {};
const [fullUrl, query] = this._getFullUrl(url); const [fullUrl, query] = this._getFullUrl(url);
return new Promise((resolve, reject) => {
if (!options.noProgress) { let abortFunction = null;
nprogress.start();
} let promise = new Promise((resolve, reject) => {
let req = requestFactory(fullUrl); let req = requestFactory(fullUrl);
req.set('Accept', 'application/json');
if (query) { if (query) {
req.query(query); req.query(query);
} }
@ -94,8 +96,21 @@ class Api extends events.EventTarget {
title: 'Authentication error', title: 'Authentication error',
description: 'Malformed credentials'}); description: 'Malformed credentials'});
} }
req.set('Accept', 'application/json')
.end((error, response) => { if (!options.noProgress) {
nprogress.start();
}
abortFunction = () => {
req.abort(); // does *NOT* call the callback passed in .end()
nprogress.done();
reject({
title: 'Cancelled',
description:
'The request was aborted due to user cancel.'});
};
req.end((error, response) => {
nprogress.done(); nprogress.done();
if (error) { if (error) {
reject(response && response.body ? response.body : { reject(response && response.body ? response.body : {
@ -106,6 +121,10 @@ class Api extends events.EventTarget {
} }
}); });
}); });
promise.abort = () => abortFunction();
return promise;
} }
hasPrivilege(lookup) { hasPrivilege(lookup) {

View File

@ -10,6 +10,8 @@ const EmptyView = require('../views/empty_view.js');
class PostUploadController { class PostUploadController {
constructor() { constructor() {
this._lastPromise = null;
if (!api.hasPrivilege('posts:create')) { if (!api.hasPrivilege('posts:create')) {
this._view = new EmptyView(); this._view = new EmptyView();
this._view.showError('You don\'t have privileges to upload posts.'); this._view.showError('You don\'t have privileges to upload posts.');
@ -23,6 +25,7 @@ class PostUploadController {
}); });
this._view.addEventListener('change', e => this._evtChange(e)); this._view.addEventListener('change', e => this._evtChange(e));
this._view.addEventListener('submit', e => this._evtSubmit(e)); this._view.addEventListener('submit', e => this._evtSubmit(e));
this._view.addEventListener('cancel', e => this._evtCancel(e));
} }
_evtChange(e) { _evtChange(e) {
@ -34,6 +37,12 @@ class PostUploadController {
this._view.clearMessages(); this._view.clearMessages();
} }
_evtCancel(e) {
if (this._lastPromise) {
this._lastPromise.abort();
}
}
_evtSubmit(e) { _evtSubmit(e) {
this._view.disableForm(); this._view.disableForm();
this._view.clearMessages(); this._view.clearMessages();
@ -48,7 +57,9 @@ class PostUploadController {
} else { } else {
post.newContent = uploadable.file; post.newContent = uploadable.file;
} }
return post.save(uploadable.anonymous) let modelPromise = post.save(uploadable.anonymous);
this._lastPromise = modelPromise;
return modelPromise
.then(() => { .then(() => {
this._view.removeUploadable(uploadable); this._view.removeUploadable(uploadable);
return Promise.resolve(); return Promise.resolve();

View File

@ -135,11 +135,11 @@ class Post extends events.EventTarget {
files.thumbnail = this._newThumbnail; files.thumbnail = this._newThumbnail;
} }
let promise = this._id ? let apiPromise = this._id ?
api.put('/post/' + this._id, detail, files) : api.put('/post/' + this._id, detail, files) :
api.post('/posts', detail, files); api.post('/posts', detail, files);
return promise.then(response => { let returnedPromise = apiPromise.then(response => {
this._updateFromResponse(response); this._updateFromResponse(response);
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('change', {detail: {post: this}})); new CustomEvent('change', {detail: {post: this}}));
@ -159,6 +159,12 @@ class Post extends events.EventTarget {
} }
return Promise.reject(response.description); return Promise.reject(response.description);
}); });
returnedPromise.abort = () => {
apiPromise.abort();
};
return returnedPromise;
} }
feature() { feature() {

View File

@ -133,10 +133,13 @@ class PostUploadView extends events.EventTarget {
super(); super();
this._ctx = ctx; this._ctx = ctx;
this._hostNode = document.getElementById('content-holder'); this._hostNode = document.getElementById('content-holder');
this._enabled = true;
views.replaceContent(this._hostNode, template()); views.replaceContent(this._hostNode, template());
views.syncScrollPosition(); views.syncScrollPosition();
this._enabled = true;
this._cancelButtonNode.disabled = true;
this._uploadables = new Map(); this._uploadables = new Map();
this._contentFileDropper = new FileDropperControl( this._contentFileDropper = new FileDropperControl(
this._contentInputNode, this._contentInputNode,
@ -150,18 +153,22 @@ class PostUploadView extends events.EventTarget {
this._contentFileDropper.addEventListener( this._contentFileDropper.addEventListener(
'urladd', e => this._evtUrlsAdded(e)); 'urladd', e => this._evtUrlsAdded(e));
this._cancelButtonNode.addEventListener(
'click', e => this._evtCancelButtonClick(e));
this._formNode.addEventListener('submit', e => this._evtFormSubmit(e)); this._formNode.addEventListener('submit', e => this._evtFormSubmit(e));
this._formNode.classList.add('inactive'); this._formNode.classList.add('inactive');
} }
enableForm() { enableForm() {
this._enabled = true;
views.enableForm(this._formNode); views.enableForm(this._formNode);
this._cancelButtonNode.disabled = true;
this._enabled = true;
} }
disableForm() { disableForm() {
this._enabled = false;
views.disableForm(this._formNode); views.disableForm(this._formNode);
this._cancelButtonNode.disabled = false;
this._enabled = false;
} }
clearMessages() { clearMessages() {
@ -226,6 +233,11 @@ class PostUploadView extends events.EventTarget {
this.addUploadables(e.detail.urls.map(url => new Url(url))); this.addUploadables(e.detail.urls.map(url => new Url(url)));
} }
_evtCancelButtonClick(e) {
e.preventDefault();
this._emit('cancel');
}
_evtFormSubmit(e) { _evtFormSubmit(e) {
e.preventDefault(); e.preventDefault();
this._emit('submit'); this._emit('submit');
@ -342,6 +354,10 @@ class PostUploadView extends events.EventTarget {
return this._hostNode.querySelector('form [type=submit]'); return this._hostNode.querySelector('form [type=submit]');
} }
get _cancelButtonNode() {
return this._hostNode.querySelector('form .cancel');
}
get _contentInputNode() { get _contentInputNode() {
return this._formNode.querySelector('.dropper-container'); return this._formNode.querySelector('.dropper-container');
} }