feat(turbo): add turbo-poll controller
This commit is contained in:
parent
144a086dce
commit
df914d273f
2 changed files with 104 additions and 8 deletions
|
@ -1,18 +1,20 @@
|
|||
import { Application } from '@hotwired/stimulus';
|
||||
|
||||
import { ReactController } from './react_controller';
|
||||
import { TurboEventController } from './turbo_event_controller';
|
||||
import { GeoAreaController } from './geo_area_controller';
|
||||
import { TurboInputController } from './turbo_input_controller';
|
||||
import { AutosaveController } from './autosave_controller';
|
||||
import { AutosaveStatusController } from './autosave_status_controller';
|
||||
import { GeoAreaController } from './geo_area_controller';
|
||||
import { MenuButtonController } from './menu_button_controller';
|
||||
import { ReactController } from './react_controller';
|
||||
import { TurboEventController } from './turbo_event_controller';
|
||||
import { TurboInputController } from './turbo_input_controller';
|
||||
import { TurboPollController } from './turbo_poll_controller';
|
||||
|
||||
const Stimulus = Application.start();
|
||||
Stimulus.register('autosave-status', AutosaveStatusController);
|
||||
Stimulus.register('autosave', AutosaveController);
|
||||
Stimulus.register('geo-area', GeoAreaController);
|
||||
Stimulus.register('menu-button', MenuButtonController);
|
||||
Stimulus.register('react', ReactController);
|
||||
Stimulus.register('turbo-event', TurboEventController);
|
||||
Stimulus.register('geo-area', GeoAreaController);
|
||||
Stimulus.register('turbo-input', TurboInputController);
|
||||
Stimulus.register('autosave', AutosaveController);
|
||||
Stimulus.register('autosave-status', AutosaveStatusController);
|
||||
Stimulus.register('menu-button', MenuButtonController);
|
||||
Stimulus.register('turbo-poll', TurboPollController);
|
||||
|
|
94
app/javascript/controllers/turbo_poll_controller.ts
Normal file
94
app/javascript/controllers/turbo_poll_controller.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { httpRequest } from '@utils';
|
||||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
const DEFAULT_POLL_INTERVAL = 3000;
|
||||
const DEFAULT_MAX_CHECKS = 5;
|
||||
|
||||
// Periodically check the state of a URL.
|
||||
//
|
||||
// Each time the given URL is requested, a turbo-stream is rendered, causing the state to be refreshed.
|
||||
//
|
||||
// This is used mainly to refresh attachments during the anti-virus check,
|
||||
// but also to refresh the state of a pending spreadsheet export.
|
||||
export class TurboPollController extends ApplicationController {
|
||||
static values = {
|
||||
url: String,
|
||||
maxChecks: { type: Number, default: DEFAULT_MAX_CHECKS },
|
||||
interval: { type: Number, default: DEFAULT_POLL_INTERVAL }
|
||||
};
|
||||
|
||||
declare readonly urlValue: string;
|
||||
declare readonly intervalValue: number;
|
||||
declare readonly maxChecksValue: number;
|
||||
|
||||
#timer?: number;
|
||||
#abortController?: AbortController;
|
||||
|
||||
connect(): void {
|
||||
const state = this.nextState();
|
||||
if (state) {
|
||||
this.schedule(state);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.cancel();
|
||||
this.#abortController = new AbortController();
|
||||
|
||||
httpRequest(this.urlValue, { signal: this.#abortController.signal })
|
||||
.turbo()
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
private schedule(state: PollState): void {
|
||||
this.cancel();
|
||||
this.#timer = setTimeout(() => {
|
||||
this.refresh();
|
||||
}, state.interval);
|
||||
}
|
||||
|
||||
private cancel(): void {
|
||||
clearTimeout(this.#timer);
|
||||
this.#abortController?.abort();
|
||||
this.#abortController = window.AbortController
|
||||
? new AbortController()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private nextState(): PollState | false {
|
||||
const state = pollers.get(this.urlValue);
|
||||
if (!state) {
|
||||
return this.resetState();
|
||||
}
|
||||
state.interval *= 1.5;
|
||||
state.checks += 1;
|
||||
if (state.checks <= this.maxChecksValue) {
|
||||
return state;
|
||||
} else {
|
||||
this.resetState();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private resetState(): PollState {
|
||||
const state = {
|
||||
interval: this.intervalValue,
|
||||
checks: 0
|
||||
};
|
||||
pollers.set(this.urlValue, state);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
type PollState = {
|
||||
interval: number;
|
||||
checks: number;
|
||||
};
|
||||
|
||||
// We keep a global state of the pollers. It will be reset on every page change.
|
||||
const pollers = new Map<string, PollState>();
|
Loading…
Reference in a new issue