Add progress bar to model uploads

This commit is contained in:
Paul Chavard 2019-02-13 14:16:22 +01:00
parent 1e8bc3e14c
commit 5e806aa39e
7 changed files with 171 additions and 82 deletions

View file

@ -1,5 +1,5 @@
import { getJSON, debounce } from '@utils';
import { DirectUpload } from 'activestorage';
import Uploader from '../../shared/activestorage/uploader';
export default {
props: ['state', 'index', 'item'],
@ -181,7 +181,12 @@ export default {
const file = input.files[0];
if (file) {
this.isUploading = true;
uploadFile(this.state.directUploadUrl, file).then(({ signed_id }) => {
const controller = new Uploader(
input,
file,
this.state.directUploadUrl
);
controller.start().then(signed_id => {
this.pieceJustificativeTemplate = signed_id;
this.isUploading = false;
this.debouncedSave();
@ -247,17 +252,3 @@ const EXCLUDE_FROM_REPETITION = [
function castBoolean(value) {
return value && value != 0;
}
function uploadFile(directUploadUrl, file) {
const upload = new DirectUpload(file, directUploadUrl);
return new Promise((resolve, reject) => {
upload.create((error, blob) => {
if (error) {
reject(error);
} else {
resolve(blob);
}
});
});
}

View file

@ -4,7 +4,7 @@ import Rails from 'rails-ujs';
import * as ActiveStorage from 'activestorage';
import jQuery from 'jquery';
import '../shared/activestorage/progress';
import '../shared/activestorage/ujs';
import '../shared/sentry';
import '../shared/rails-ujs-fix';
import '../shared/safari-11-file-xhr-workaround';

View file

@ -5,7 +5,7 @@ import * as ActiveStorage from 'activestorage';
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import '../shared/activestorage/progress';
import '../shared/activestorage/ujs';
import '../shared/sentry';
import '../shared/rails-ujs-fix';
import '../shared/safari-11-file-xhr-workaround';

View file

@ -0,0 +1,92 @@
const PENDING_CLASS = 'direct-upload--pending';
const ERROR_CLASS = 'direct-upload--error';
const COMPLETE_CLASS = 'direct-upload--complete';
/**
ProgressBar is and utility class responsible for
rendering upload progress bar. It is used to handle
direct-upload form ujs events but also in the
Uploader delegate used with uploads on json api.
*/
export default class ProgressBar {
static init(input, id, file) {
clearErrors(input);
const html = this.render(id, file.name);
input.insertAdjacentHTML('beforebegin', html);
}
static start(id) {
const element = getDirectUploadElement(id);
element.classList.remove(PENDING_CLASS);
}
static progress(id, progress) {
const element = getDirectUploadProgressElement(id);
element.style.width = `${progress}%`;
}
static error(id, error) {
const element = getDirectUploadElement(id);
element.classList.add(ERROR_CLASS);
element.setAttribute('title', error);
}
static end(id) {
const element = getDirectUploadElement(id);
element.classList.add(COMPLETE_CLASS);
}
static render(id, filename) {
return `<div id="direct-upload-${id}" class="direct-upload ${PENDING_CLASS}">
<div class="direct-upload__progress" style="width: 0%"></div>
<span class="direct-upload__filename">${filename}</span>
</div>`;
}
constructor(input, id, file) {
this.constructor.init(input, id, file);
this.id = id;
}
start() {
this.constructor.start(this.id);
}
progress(progress) {
this.constructor.progress(this.id, progress);
}
error(error) {
this.constructor.error(this.id, error);
}
end() {
this.constructor.end(this.id);
}
destroy() {
const element = getDirectUploadElement(this.id);
element.remove();
}
}
function clearErrors(input) {
const errorElements = input.parentElement.querySelectorAll(`.${ERROR_CLASS}`);
for (let element of errorElements) {
element.remove();
}
}
function getDirectUploadElement(id) {
return document.getElementById(`direct-upload-${id}`);
}
function getDirectUploadProgressElement(id) {
return document.querySelector(
`#direct-upload-${id} .direct-upload__progress`
);
}

View file

@ -1,64 +0,0 @@
addEventListener('direct-upload:initialize', event => {
const {
target,
detail: {
id,
file: { name: filename }
}
} = event;
const errorElements = target.parentElement.querySelectorAll(
'.direct-upload--error'
);
for (let element of errorElements) {
element.remove();
}
target.insertAdjacentHTML('beforebegin', template(id, filename));
});
addEventListener('direct-upload:start', event => {
const id = event.detail.id,
element = getDirectUploadElement(id);
element.classList.remove('direct-upload--pending');
return false;
});
addEventListener('direct-upload:progress', event => {
const { id, progress } = event.detail,
progressElement = getDirectUploadProgressElement(id);
progressElement.style.width = `${progress}%`;
});
addEventListener('direct-upload:error', event => {
const { id, error } = event.detail,
element = getDirectUploadElement(id);
element.classList.add('direct-upload--error');
element.setAttribute('title', error);
});
addEventListener('direct-upload:end', event => {
const { id } = event.detail,
element = getDirectUploadElement(id);
element.classList.add('direct-upload--complete');
});
function template(id, filename) {
return `<div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
<div class="direct-upload__progress" style="width: 0%"></div>
<span class="direct-upload__filename">${filename}</span>
</div>`;
}
function getDirectUploadElement(id) {
return document.getElementById(`direct-upload-${id}`);
}
function getDirectUploadProgressElement(id) {
return document.querySelector(
`#direct-upload-${id} .direct-upload__progress`
);
}

View file

@ -0,0 +1,27 @@
import ProgressBar from './progress-bar';
const INITIALIZE_EVENT = 'direct-upload:initialize';
const START_EVENT = 'direct-upload:start';
const PROGRESS_EVENT = 'direct-upload:progress';
const ERROR_EVENT = 'direct-upload:error';
const END_EVENT = 'direct-upload:end';
addEventListener(INITIALIZE_EVENT, ({ target, detail: { id, file } }) => {
ProgressBar.init(target, id, file);
});
addEventListener(START_EVENT, ({ detail: { id } }) => {
ProgressBar.start(id);
});
addEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => {
ProgressBar.progress(id, progress);
});
addEventListener(ERROR_EVENT, ({ detail: { id, error } }) => {
ProgressBar.error(id, error);
});
addEventListener(END_EVENT, ({ detail: { id } }) => {
ProgressBar.end(id);
});

View file

@ -0,0 +1,43 @@
import { DirectUpload } from 'activestorage';
import ProgressBar from './progress-bar';
/**
Uploader class is a delegate for DirectUpload instance
used to track lifecycle and progress of un upload.
*/
export default class Uploader {
constructor(input, file, directUploadUrl) {
this.directUpload = new DirectUpload(file, directUploadUrl, this);
this.progressBar = new ProgressBar(input, this.directUpload.id, file);
}
start() {
this.progressBar.start();
return new Promise((resolve, reject) => {
this.directUpload.create((error, attributes) => {
if (error) {
this.progressBar.error(error);
reject(error);
} else {
resolve(attributes.signed_id);
}
this.progressBar.end();
this.progressBar.destroy();
});
});
}
uploadRequestDidProgress(event) {
const progress = (event.loaded / event.total) * 100;
if (progress) {
this.progressBar.progress(progress);
}
}
directUploadWillStoreFileWithXHR(xhr) {
xhr.upload.addEventListener('progress', event =>
this.uploadRequestDidProgress(event)
);
}
}