cleanup(js): remove old autosave/autoupload code
This commit is contained in:
parent
3d8980f686
commit
39761f45a0
6 changed files with 0 additions and 399 deletions
|
@ -1,97 +0,0 @@
|
||||||
import { ajax, fire, timeoutable } from '@utils';
|
|
||||||
|
|
||||||
// Manages a queue of Autosave operations,
|
|
||||||
// and sends `autosave:*` events to indicate the state of the requests:
|
|
||||||
//
|
|
||||||
// - autosave:enqueue () when an autosave request has been enqueued
|
|
||||||
// - autosave:end ({ response, statusText, xhr }) when an autosave request finished successfully
|
|
||||||
// - autosave:failure (Error) when an autosave request failed
|
|
||||||
//
|
|
||||||
export default class AutoSaveController {
|
|
||||||
constructor() {
|
|
||||||
this.timeoutDelay = 60000; // 1mn
|
|
||||||
this.latestPromise = Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new autosave request to the queue.
|
|
||||||
// It will be started after the previous one finishes (to prevent older form data
|
|
||||||
// to overwrite newer data if the server does not respond in order.)
|
|
||||||
enqueueAutosaveRequest(form) {
|
|
||||||
this.latestPromise = this.latestPromise.finally(() => {
|
|
||||||
return this._sendAutosaveRequest(form)
|
|
||||||
.then(this._didSucceed)
|
|
||||||
.catch(this._didFail);
|
|
||||||
});
|
|
||||||
this._didEnqueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a fetch request that saves the form.
|
|
||||||
// Returns a promise fulfilled when the request completes.
|
|
||||||
_sendAutosaveRequest(form) {
|
|
||||||
const autosavePromise = new Promise((resolve, reject) => {
|
|
||||||
if (!document.body.contains(form)) {
|
|
||||||
return reject(new Error('The form can no longer be found.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const [formData, formDataError] = this._formDataForDraft(form);
|
|
||||||
if (formDataError) {
|
|
||||||
formDataError.message = `Error while generating the form data (${formDataError.message})`;
|
|
||||||
return reject(formDataError);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
url: form.action,
|
|
||||||
type: form.method,
|
|
||||||
data: formData,
|
|
||||||
dataType: 'script'
|
|
||||||
};
|
|
||||||
|
|
||||||
return ajax(params)
|
|
||||||
.then(({ response }) => {
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Time out the request after a while, to avoid recent requests not starting
|
|
||||||
// because an older one is stuck.
|
|
||||||
return timeoutable(autosavePromise, this.timeoutDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a FormData object of the form fields.
|
|
||||||
_formDataForDraft(form) {
|
|
||||||
// File inputs are handled separatly by ActiveStorage:
|
|
||||||
// exclude them from the draft (by disabling them).
|
|
||||||
// (Also Safari has issue with FormData containing empty file inputs)
|
|
||||||
const fileInputs = form.querySelectorAll(
|
|
||||||
'input[type="file"]:not([disabled]), .editable-champ-piece_justificative input:not([disabled])'
|
|
||||||
);
|
|
||||||
fileInputs.forEach((fileInput) => (fileInput.disabled = true));
|
|
||||||
|
|
||||||
// Generate the form data
|
|
||||||
let formData = null;
|
|
||||||
try {
|
|
||||||
formData = new FormData(form);
|
|
||||||
return [formData, null];
|
|
||||||
} catch (error) {
|
|
||||||
return [null, error];
|
|
||||||
} finally {
|
|
||||||
// Re-enable disabled file inputs
|
|
||||||
fileInputs.forEach((fileInput) => (fileInput.disabled = false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_didEnqueue() {
|
|
||||||
fire(document, 'autosave:enqueue');
|
|
||||||
}
|
|
||||||
|
|
||||||
_didSucceed(response) {
|
|
||||||
fire(document, 'autosave:end', response);
|
|
||||||
}
|
|
||||||
|
|
||||||
_didFail(error) {
|
|
||||||
fire(document, 'autosave:error', error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
import AutoSaveController from './auto-save-controller.js';
|
|
||||||
import {
|
|
||||||
debounce,
|
|
||||||
delegate,
|
|
||||||
fire,
|
|
||||||
enable,
|
|
||||||
disable,
|
|
||||||
hasClass,
|
|
||||||
addClass,
|
|
||||||
removeClass
|
|
||||||
} from '@utils';
|
|
||||||
|
|
||||||
const AUTOSAVE_DEBOUNCE_DELAY = window?.gon?.autosave?.debounce_delay;
|
|
||||||
const AUTOSAVE_STATUS_VISIBLE_DURATION =
|
|
||||||
window?.gon?.autosave?.status_visible_duration;
|
|
||||||
|
|
||||||
// Create a controller responsible for queuing autosave operations.
|
|
||||||
const autoSaveController = new AutoSaveController();
|
|
||||||
|
|
||||||
function enqueueAutosaveRequest() {
|
|
||||||
const form = document.querySelector(FORM_SELECTOR);
|
|
||||||
autoSaveController.enqueueAutosaveRequest(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
|
|
||||||
//
|
|
||||||
|
|
||||||
const FORM_SELECTOR = 'form#dossier-edit-form.autosave-enabled';
|
|
||||||
const INPUTS_SELECTOR = `${FORM_SELECTOR} input:not([type=file]), ${FORM_SELECTOR} select, ${FORM_SELECTOR} textarea`;
|
|
||||||
const RETRY_BUTTON_SELECTOR = '.autosave-retry';
|
|
||||||
|
|
||||||
// When an autosave is requested programmatically, auto-save the form immediately
|
|
||||||
addEventListener('autosave:trigger', (event) => {
|
|
||||||
const form = event.target.closest('form');
|
|
||||||
if (form && form.classList.contains('autosave-enabled')) {
|
|
||||||
enqueueAutosaveRequest();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// When the "Retry" button is clicked, auto-save the form immediately
|
|
||||||
delegate('click', RETRY_BUTTON_SELECTOR, enqueueAutosaveRequest);
|
|
||||||
|
|
||||||
// When an input changes, batches changes for N seconds, then auto-save the form
|
|
||||||
delegate(
|
|
||||||
'change',
|
|
||||||
INPUTS_SELECTOR,
|
|
||||||
debounce(enqueueAutosaveRequest, AUTOSAVE_DEBOUNCE_DELAY)
|
|
||||||
);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Display some UI during the autosave
|
|
||||||
//
|
|
||||||
|
|
||||||
addEventListener('autosave:enqueue', () => {
|
|
||||||
disable(document.querySelector('button.autosave-retry'));
|
|
||||||
});
|
|
||||||
|
|
||||||
addEventListener('autosave:end', () => {
|
|
||||||
enable(document.querySelector('button.autosave-retry'));
|
|
||||||
setState('succeeded');
|
|
||||||
hideSucceededStatusAfterDelay();
|
|
||||||
});
|
|
||||||
|
|
||||||
addEventListener('autosave:error', (event) => {
|
|
||||||
let error = event.detail;
|
|
||||||
|
|
||||||
if (error.xhr && error.xhr.status == 401) {
|
|
||||||
// If we are unauthenticated, reload the page using a GET request.
|
|
||||||
// This will allow Devise to properly redirect us to sign-in, and then back to this page.
|
|
||||||
document.location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
enable(document.querySelector('button.autosave-retry'));
|
|
||||||
setState('failed');
|
|
||||||
|
|
||||||
const shouldLogError = !error.xhr || error.xhr.status != 0; // ignore timeout errors
|
|
||||||
if (shouldLogError) {
|
|
||||||
logError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function setState(state) {
|
|
||||||
const autosave = document.querySelector('.autosave');
|
|
||||||
if (autosave) {
|
|
||||||
// Re-apply the state even if already present, to get a nice animation
|
|
||||||
removeClass(autosave, 'autosave-state-idle');
|
|
||||||
removeClass(autosave, 'autosave-state-succeeded');
|
|
||||||
removeClass(autosave, 'autosave-state-failed');
|
|
||||||
autosave.offsetHeight; // flush animations
|
|
||||||
addClass(autosave, `autosave-state-${state}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSucceededStatus() {
|
|
||||||
const autosave = document.querySelector('.autosave');
|
|
||||||
if (hasClass(autosave, 'autosave-state-succeeded')) {
|
|
||||||
setState('idle');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hideSucceededStatusAfterDelay = debounce(
|
|
||||||
hideSucceededStatus,
|
|
||||||
AUTOSAVE_STATUS_VISIBLE_DURATION
|
|
||||||
);
|
|
||||||
|
|
||||||
function logError(error) {
|
|
||||||
if (error && error.message) {
|
|
||||||
error.message = `[Autosave] ${error.message}`;
|
|
||||||
console.error(error);
|
|
||||||
fire(document, 'sentry:capture-exception', error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
import Uploader from '../../shared/activestorage/uploader';
|
|
||||||
import { show, hide, toggle } from '@utils';
|
|
||||||
import {
|
|
||||||
ERROR_CODE_READ,
|
|
||||||
FAILURE_CONNECTIVITY
|
|
||||||
} from '../../shared/activestorage/file-upload-error';
|
|
||||||
|
|
||||||
// Given a file input in a champ with a selected file, upload a file,
|
|
||||||
// then attach it to the dossier.
|
|
||||||
//
|
|
||||||
// On success, the champ is replaced by an HTML fragment describing the attachment.
|
|
||||||
// On error, a error message is displayed above the input.
|
|
||||||
export default class AutoUploadController {
|
|
||||||
constructor(input, file) {
|
|
||||||
this.input = input;
|
|
||||||
this.file = file;
|
|
||||||
this.uploader = new Uploader(
|
|
||||||
input,
|
|
||||||
file,
|
|
||||||
input.dataset.directUploadUrl,
|
|
||||||
input.dataset.autoAttachUrl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create, upload and attach the file.
|
|
||||||
// On failure, display an error message and throw a FileUploadError.
|
|
||||||
async start() {
|
|
||||||
try {
|
|
||||||
this._begin();
|
|
||||||
await this.uploader.start();
|
|
||||||
this._succeeded();
|
|
||||||
} catch (error) {
|
|
||||||
this._failed(error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
this._done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_begin() {
|
|
||||||
this.input.disabled = true;
|
|
||||||
this._hideErrorMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
_succeeded() {
|
|
||||||
this.input.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_failed(error) {
|
|
||||||
if (!document.body.contains(this.input)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.uploader.progressBar.destroy();
|
|
||||||
|
|
||||||
let message = this._messageFromError(error);
|
|
||||||
this._displayErrorMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_done() {
|
|
||||||
this.input.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_messageFromError(error) {
|
|
||||||
let message = error.message || error.toString();
|
|
||||||
let canRetry = error.status && error.status != 422;
|
|
||||||
|
|
||||||
if (error.failureReason == FAILURE_CONNECTIVITY) {
|
|
||||||
return {
|
|
||||||
title: 'Le fichier n’a pas pu être envoyé.',
|
|
||||||
description: 'Vérifiez votre connexion à Internet, puis ré-essayez.',
|
|
||||||
retry: true
|
|
||||||
};
|
|
||||||
} else if (error.code == ERROR_CODE_READ) {
|
|
||||||
return {
|
|
||||||
title: 'Nous n’arrivons pas à lire ce fichier sur votre appareil.',
|
|
||||||
description: 'Essayez à nouveau, ou sélectionnez un autre fichier.',
|
|
||||||
retry: false
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
title: 'Le fichier n’a pas pu être envoyé.',
|
|
||||||
description: message,
|
|
||||||
retry: canRetry
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_displayErrorMessage(message) {
|
|
||||||
let errorNode = this.input.parentElement.querySelector('.attachment-error');
|
|
||||||
if (errorNode) {
|
|
||||||
show(errorNode);
|
|
||||||
errorNode.querySelector('.attachment-error-title').textContent =
|
|
||||||
message.title || '';
|
|
||||||
errorNode.querySelector('.attachment-error-description').textContent =
|
|
||||||
message.description || '';
|
|
||||||
toggle(errorNode.querySelector('.attachment-error-retry'), message.retry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_hideErrorMessage() {
|
|
||||||
let errorElement =
|
|
||||||
this.input.parentElement.querySelector('.attachment-error');
|
|
||||||
if (errorElement) {
|
|
||||||
hide(errorElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import AutoUploadsControllers from './auto-uploads-controllers.js';
|
|
||||||
import { delegate } from '@utils';
|
|
||||||
|
|
||||||
// Create a controller responsible for managing several concurrent uploads.
|
|
||||||
const autoUploadsControllers = new AutoUploadsControllers();
|
|
||||||
|
|
||||||
function startUpload(input) {
|
|
||||||
Array.from(input.files).forEach((file) => {
|
|
||||||
autoUploadsControllers.upload(input, file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileInputSelector = `input[type=file][data-direct-upload-url][data-auto-attach-url]:not([disabled])`;
|
|
||||||
delegate('change', fileInputSelector, (event) => {
|
|
||||||
startUpload(event.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
const retryButtonSelector = `button.attachment-error-retry`;
|
|
||||||
delegate('click', retryButtonSelector, function () {
|
|
||||||
const inputSelector = this.dataset.inputTarget;
|
|
||||||
const input = document.querySelector(inputSelector);
|
|
||||||
startUpload(input);
|
|
||||||
});
|
|
|
@ -1,56 +0,0 @@
|
||||||
import Rails from '@rails/ujs';
|
|
||||||
import AutoUploadController from './auto-upload-controller.js';
|
|
||||||
import {
|
|
||||||
FAILURE_CLIENT,
|
|
||||||
ERROR_CODE_READ
|
|
||||||
} from '../../shared/activestorage/file-upload-error';
|
|
||||||
|
|
||||||
// Manage multiple concurrent uploads.
|
|
||||||
//
|
|
||||||
// When the first upload starts, all the form "Submit" buttons are disabled.
|
|
||||||
// They are enabled again when the last upload ends.
|
|
||||||
export default class AutoUploadsControllers {
|
|
||||||
constructor() {
|
|
||||||
this.inFlightUploadsCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async upload(input, file) {
|
|
||||||
let form = input.form;
|
|
||||||
this._incrementInFlightUploads(form);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let controller = new AutoUploadController(input, file);
|
|
||||||
await controller.start();
|
|
||||||
} catch (err) {
|
|
||||||
// Report unexpected client errors to Sentry.
|
|
||||||
// (But ignore usual client errors, or errors we can monitor better on the server side.)
|
|
||||||
if (err.failureReason == FAILURE_CLIENT && err.code != ERROR_CODE_READ) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this._decrementInFlightUploads(form);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_incrementInFlightUploads(form) {
|
|
||||||
this.inFlightUploadsCount += 1;
|
|
||||||
|
|
||||||
if (form) {
|
|
||||||
form
|
|
||||||
.querySelectorAll('button[type=submit]')
|
|
||||||
.forEach((submitButton) => Rails.disableElement(submitButton));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_decrementInFlightUploads(form) {
|
|
||||||
if (this.inFlightUploadsCount > 0) {
|
|
||||||
this.inFlightUploadsCount -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.inFlightUploadsCount == 0 && form) {
|
|
||||||
form
|
|
||||||
.querySelectorAll('button[type=submit]')
|
|
||||||
.forEach((submitButton) => Rails.enableElement(submitButton));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,8 +26,6 @@ import '../new_design/procedure-form';
|
||||||
import '../new_design/spinner';
|
import '../new_design/spinner';
|
||||||
import '../new_design/support';
|
import '../new_design/support';
|
||||||
import '../new_design/messagerie';
|
import '../new_design/messagerie';
|
||||||
import '../new_design/dossiers/auto-save';
|
|
||||||
import '../new_design/dossiers/auto-upload';
|
|
||||||
|
|
||||||
import '../new_design/champs/linked-drop-down-list';
|
import '../new_design/champs/linked-drop-down-list';
|
||||||
import '../new_design/champs/drop-down-list';
|
import '../new_design/champs/drop-down-list';
|
||||||
|
|
Loading…
Add table
Reference in a new issue