TenantAtlas/public/js/tenantpilot/ops-ux-progress-widget-poller.js
2026-03-23 22:52:37 +01:00

170 lines
4.8 KiB
JavaScript

(() => {
if (typeof window === 'undefined') {
return;
}
if (typeof window.opsUxProgressWidgetPoller === 'function') {
return;
}
window.opsUxProgressWidgetPoller = function opsUxProgressWidgetPoller() {
return {
timer: null,
activeSinceMs: null,
fastUntilMs: null,
teardownObserver: null,
init() {
this.onVisibilityChange = this.onVisibilityChange.bind(this);
window.addEventListener('visibilitychange', this.onVisibilityChange);
this.onNavigated = this.onNavigated.bind(this);
window.addEventListener('livewire:navigated', this.onNavigated);
this.teardownObserver = new MutationObserver(() => {
if (!this.$el || this.$el.isConnected !== true) {
this.destroy();
}
});
this.teardownObserver.observe(document.body, { childList: true, subtree: true });
this.schedule(0);
},
destroy() {
this.stop();
window.removeEventListener('visibilitychange', this.onVisibilityChange);
window.removeEventListener('livewire:navigated', this.onNavigated);
if (this.teardownObserver) {
this.teardownObserver.disconnect();
this.teardownObserver = null;
}
},
stop() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
},
isModalOpen() {
return document.querySelector('[role="dialog"][aria-modal="true"]') !== null;
},
isPaused() {
if (document.hidden === true) {
return true;
}
if (this.isModalOpen()) {
return true;
}
if (!this.$el || this.$el.isConnected !== true) {
return true;
}
return false;
},
onVisibilityChange() {
if (!this.isPaused()) {
this.schedule(0);
}
},
onNavigated() {
if (!this.isPaused()) {
this.schedule(0);
}
},
activeAgeSeconds() {
if (this.activeSinceMs === null) {
return 0;
}
return Math.floor((Date.now() - this.activeSinceMs) / 1000);
},
nextIntervalMs() {
if (this.$wire?.disabled === true) {
return null;
}
if (this.$wire?.hasActiveRuns !== true) {
this.activeSinceMs = null;
return 30_000;
}
if (this.activeSinceMs === null) {
this.activeSinceMs = Date.now();
}
const now = Date.now();
if (this.fastUntilMs && now < this.fastUntilMs) {
return 1_000;
}
const age = this.activeAgeSeconds();
if (age < 10) {
return 1_000;
}
if (age < 60) {
return 5_000;
}
return 10_000;
},
async tick() {
if (this.isPaused()) {
this.schedule(2_000);
return;
}
try {
await this.$wire.refreshRuns();
} catch (error) {
const isCancellation = Boolean(
error &&
typeof error === 'object' &&
error.status === null &&
error.body === null &&
error.json === null &&
error.errors === null,
);
if (!isCancellation) {
console.warn('Ops UX widget refreshRuns failed', error);
}
}
const next = this.nextIntervalMs();
if (next === null) {
this.stop();
return;
}
this.schedule(next);
},
schedule(delayMs) {
this.stop();
const delay = Math.max(0, Number(delayMs ?? 0));
this.timer = setTimeout(() => {
this.tick().catch(() => {});
}, delay);
},
};
};
})();