Add progress bar to model uploads
This commit is contained in:
parent
1e8bc3e14c
commit
5e806aa39e
7 changed files with 171 additions and 82 deletions
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
92
app/javascript/shared/activestorage/progress-bar.js
Normal file
92
app/javascript/shared/activestorage/progress-bar.js
Normal 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`
|
||||
);
|
||||
}
|
|
@ -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`
|
||||
);
|
||||
}
|
27
app/javascript/shared/activestorage/ujs.js
Normal file
27
app/javascript/shared/activestorage/ujs.js
Normal 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);
|
||||
});
|
43
app/javascript/shared/activestorage/uploader.js
Normal file
43
app/javascript/shared/activestorage/uploader.js
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue