javascript: move auto-upload attachment to the Uploader class

Rationale:

- It makes more sense to handle the progress bar updates in a single class;
- This will allow us to unify the error handling.
This commit is contained in:
Pierre de La Morinerie 2020-04-15 14:20:31 +00:00
parent 256362efd3
commit d8f3b86b0e
2 changed files with 57 additions and 59 deletions

View file

@ -1,6 +1,5 @@
import Uploader from '../../shared/activestorage/uploader'; import Uploader from '../../shared/activestorage/uploader';
import ProgressBar from '../../shared/activestorage/progress-bar'; import { show, hide, toggle } from '@utils';
import { ajax, show, hide, toggle } from '@utils';
// Given a file input in a champ with a selected file, upload a file, // Given a file input in a champ with a selected file, upload a file,
// then attach it to the dossier. // then attach it to the dossier.
@ -11,27 +10,19 @@ export default class AutoUploadController {
constructor(input, file) { constructor(input, file) {
this.input = input; this.input = input;
this.file = file; this.file = file;
this.uploader = new Uploader(
input,
file,
input.dataset.directUploadUrl,
input.dataset.autoAttachUrl
);
} }
async start() { async start() {
try { try {
this._begin(); this._begin();
await this.uploader.start();
// Sanity checks this._succeeded();
const autoAttachUrl = this.input.dataset.autoAttachUrl;
if (!autoAttachUrl) {
throw new Error('Lattribut "data-auto-attach-url" est manquant');
}
// Upload the file (using Direct Upload)
let blobSignedId = await this._upload();
// Attach the blob to the champ
// (The request responds with Javascript, which displays the attachment HTML fragment).
await this._attach(blobSignedId, autoAttachUrl);
// Everything good: clear the original file input value
this.input.value = null;
} catch (error) { } catch (error) {
this._failed(error); this._failed(error);
throw error; throw error;
@ -45,35 +36,8 @@ export default class AutoUploadController {
this._hideErrorMessage(); this._hideErrorMessage();
} }
async _upload() { _succeeded() {
const uploader = new Uploader( this.input.value = null;
this.input,
this.file,
this.input.dataset.directUploadUrl
);
return await uploader.start();
}
async _attach(blobSignedId, autoAttachUrl) {
// Now that the upload is done, display a new progress bar
// to show that the attachment request is still pending.
const progressBar = new ProgressBar(
this.input,
`${this.input.id}-progress-bar`,
this.file
);
progressBar.progress(100);
progressBar.end();
const attachmentRequest = {
url: autoAttachUrl,
type: 'PUT',
data: `blob_signed_id=${blobSignedId}`
};
await ajax(attachmentRequest);
// The progress bar has been destroyed by the attachment HTML fragment that replaced the input,
// so no further cleanup is needed.
} }
_failed(error) { _failed(error) {
@ -81,12 +45,10 @@ export default class AutoUploadController {
return; return;
} }
let progressBar = this.input.parentElement.querySelector('.direct-upload'); this.uploader.progressBar.destroy();
if (progressBar) {
progressBar.remove();
}
this._displayErrorMessage(error); let message = this._messageFromError(error);
this._displayErrorMessage(message);
} }
_done() { _done() {
@ -126,11 +88,10 @@ export default class AutoUploadController {
} }
} }
_displayErrorMessage(error) { _displayErrorMessage(message) {
let errorNode = this.input.parentElement.querySelector('.attachment-error'); let errorNode = this.input.parentElement.querySelector('.attachment-error');
if (errorNode) { if (errorNode) {
show(errorNode); show(errorNode);
let message = this._messageFromError(error);
errorNode.querySelector('.attachment-error-title').textContent = errorNode.querySelector('.attachment-error-title').textContent =
message.title || ''; message.title || '';
errorNode.querySelector('.attachment-error-description').textContent = errorNode.querySelector('.attachment-error-description').textContent =

View file

@ -1,4 +1,5 @@
import { DirectUpload } from '@rails/activestorage'; import { DirectUpload } from '@rails/activestorage';
import { ajax } from '@utils';
import ProgressBar from './progress-bar'; import ProgressBar from './progress-bar';
import errorFromDirectUploadMessage from './errors'; import errorFromDirectUploadMessage from './errors';
@ -7,29 +8,65 @@ import errorFromDirectUploadMessage from './errors';
used to track lifecycle and progress of an upload. used to track lifecycle and progress of an upload.
*/ */
export default class Uploader { export default class Uploader {
constructor(input, file, directUploadUrl) { constructor(input, file, directUploadUrl, autoAttachUrl) {
this.directUpload = new DirectUpload(file, directUploadUrl, this); this.directUpload = new DirectUpload(file, directUploadUrl, this);
this.progressBar = new ProgressBar(input, this.directUpload.id, file); this.progressBar = new ProgressBar(input, this.directUpload.id, file);
this.autoAttachUrl = autoAttachUrl;
} }
start() { /**
Upload (and optionally attach) the file.
Returns the blob signed id on success.
*/
async start() {
this.progressBar.start(); this.progressBar.start();
try {
let blobSignedId = await this._upload();
if (this.autoAttachUrl) {
await this._attach(blobSignedId);
}
this.progressBar.end();
this.progressBar.destroy();
return blobSignedId;
} catch (error) {
this.progressBar.error(error.message);
throw error;
}
}
/**
Upload the file using the DirectUpload instance, and return the blob signed_id.
*/
async _upload() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.directUpload.create((errorMsg, attributes) => { this.directUpload.create((errorMsg, attributes) => {
if (errorMsg) { if (errorMsg) {
this.progressBar.error(errorMsg);
let error = errorFromDirectUploadMessage(errorMsg); let error = errorFromDirectUploadMessage(errorMsg);
reject(error); reject(error);
} else { } else {
resolve(attributes.signed_id); resolve(attributes.signed_id);
} }
this.progressBar.end();
this.progressBar.destroy();
}); });
}); });
} }
/**
Attach the file by sending a POST request to the autoAttachUrl.
*/
async _attach(blobSignedId) {
const attachmentRequest = {
url: this.autoAttachUrl,
type: 'PUT',
data: `blob_signed_id=${blobSignedId}`
};
await ajax(attachmentRequest);
}
uploadRequestDidProgress(event) { uploadRequestDidProgress(event) {
const progress = (event.loaded / event.total) * 100; const progress = (event.loaded / event.total) * 100;
if (progress) { if (progress) {