demarches-normaliennes/app/javascript/controllers/autosave_status_controller.ts

108 lines
3.1 KiB
TypeScript
Raw Normal View History

import {
enable,
disable,
hasClass,
addClass,
removeClass,
ResponseError,
getConfig
} from '@utils';
import { ApplicationController } from './application_controller';
const {
autosave: { status_visible_duration }
} = getConfig();
const AUTOSAVE_STATUS_VISIBLE_DURATION = status_visible_duration;
// This is a controller we attach to the status area in the main form. It
// coordinates notifications and will dispatch `autosave:retry` event if user
// decides to retry after an error.
//
export class AutosaveStatusController extends ApplicationController {
static targets = ['retryButton'];
declare readonly retryButtonTarget: HTMLButtonElement;
connect(): void {
this.onGlobal('autosave:enqueue', () => this.didEnqueue());
this.onGlobal('autosave:end', () => this.didSucceed());
this.onGlobal<CustomEvent>('autosave:error', (event) =>
this.didFail(event)
);
this.onGlobal('debounced:added', () => this.debouncedAdded());
this.onGlobal('debounced:empty', () => this.debouncedEmpty());
}
private debouncedAdded() {
const autosave = this.element as HTMLDivElement;
removeClass(autosave, 'debounced-empty');
addClass(autosave, 'debounced-added');
}
private debouncedEmpty() {
const autosave = this.element as HTMLDivElement;
addClass(autosave, 'debounced-empty');
removeClass(autosave, 'debounced-added');
}
onClickRetryButton() {
this.globalDispatch('autosave:retry');
}
private didEnqueue() {
disable(this.retryButtonTarget);
}
private didSucceed() {
enable(this.retryButtonTarget);
this.setState('succeeded');
this.debounce(this.hideSucceededStatus, AUTOSAVE_STATUS_VISIBLE_DURATION);
}
private didFail(event: CustomEvent<{ error: ResponseError }>) {
const error = event.detail.error;
if (error.response?.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(this.retryButtonTarget);
this.setState('failed');
const shouldLogError = !error.response || error.response.status != 0; // ignore timeout errors
if (shouldLogError) {
this.logError(error);
}
}
private setState(state: 'succeeded' | 'failed' | 'idle') {
const autosave = this.element as HTMLDivElement;
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}`);
}
}
private hideSucceededStatus() {
if (hasClass(this.element as HTMLElement, 'autosave-state-succeeded')) {
this.setState('idle');
}
}
private logError(error: ResponseError) {
if (error && error.message) {
console.error(error);
this.globalDispatch('sentry:capture-exception', error);
}
}
}