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/support';
|
||||
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/drop-down-list';
|
||||
|
|
Loading…
Reference in a new issue