cleanup(js): remove old autosave/autoupload code

This commit is contained in:
Paul Chavard 2022-05-06 19:39:19 +02:00
parent 3d8980f686
commit 39761f45a0
6 changed files with 0 additions and 399 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 na 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 narrivons pas à lire ce fichier sur votre appareil.',
description: 'Essayez à nouveau, ou sélectionnez un autre fichier.',
retry: false
};
} else {
return {
title: 'Le fichier na 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);
}
}
}

View file

@ -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);
});

View file

@ -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));
}
}
}

View file

@ -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';