javascript: move the autosave files to a sub-directory

This commit is contained in:
Pierre de La Morinerie 2020-03-19 16:48:21 +00:00
parent 7e513cc5f6
commit e908b42b43
3 changed files with 6 additions and 6 deletions

View file

@ -0,0 +1,93 @@
import { fire, timeoutable } from '@utils';
// Manages a queue of Autosave operations,
// and sends `autosave:*` events to indicate the state of the requests.
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 repond 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 fetchOptions = {
method: form.method,
body: formData,
credentials: 'same-origin',
headers: { Accept: 'application/json' }
};
return window.fetch(form.action, fetchOptions).then(response => {
if (response.ok) {
resolve(response);
} else {
const message = `Network request failed (${response.status}, "${response.statusText}")`;
reject(new Error(message));
}
});
});
// 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

@ -0,0 +1,85 @@
import AutoSaveController from './auto-save-controller.js';
import {
debounce,
delegate,
fire,
enable,
disable,
hasClass,
addClass,
removeClass
} from '@utils';
const AUTOSAVE_DEBOUNCE_DELAY = gon.autosave.debounce_delay;
const AUTOSAVE_STATUS_VISIBLE_DURATION = gon.autosave.status_visible_duration;
// Create a controller responsible for queuing autosave operations.
const autoSaveController = new AutoSaveController();
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
const formSelector = 'form#dossier-edit-form.autosave-enabled';
const formInputsSelector = `${formSelector} input:not([type=file]), ${formSelector} select, ${formSelector} textarea`;
delegate(
'change',
formInputsSelector,
debounce(() => {
const form = document.querySelector(formSelector);
autoSaveController.enqueueAutosaveRequest(form);
}, AUTOSAVE_DEBOUNCE_DELAY)
);
delegate('click', '.autosave-retry', () => {
const form = document.querySelector(formSelector);
autoSaveController.enqueueAutosaveRequest(form);
});
// 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 => {
enable(document.querySelector('button.autosave-retry'));
setState('failed');
logError(event.detail);
});
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);
}
}