javascript: move the autosave files to a sub-directory
This commit is contained in:
parent
7e513cc5f6
commit
e908b42b43
3 changed files with 6 additions and 6 deletions
93
app/javascript/new_design/dossiers/auto-save-controller.js
Normal file
93
app/javascript/new_design/dossiers/auto-save-controller.js
Normal 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);
|
||||
}
|
||||
}
|
85
app/javascript/new_design/dossiers/auto-save.js
Normal file
85
app/javascript/new_design/dossiers/auto-save.js
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue