1import { Controller } from "@hotwired/stimulus"; 2 3export default class extends Controller { 4 static values = { 5 intervalMs: { type: Number, default: 20000 } 6 }; 7 8 connect() { 9 this.reloading = false; 10 // The refresh is tracked as an absolute deadline rather than a repeating timer because 11 // every refresh is a full page load, so there is only ever one pending. Keeping the 12 // deadline fixed across a hide/show is what stops a brief alt-tab from pushing an 13 // already-due refresh out by another full interval â and a page that loads straight into 14 // a background tab ages from load, so its deadline is set here rather than on first focus. 15 this.deadline = Date.now() + this.intervalMsValue; 16 this.boundVisibilityChange = this.handleVisibilityChange.bind(this); 17 document.addEventListener("visibilitychange", this.boundVisibilityChange); 18 this.arm(); 19 } 20 21 disconnect() { 22 document.removeEventListener("visibilitychange", this.boundVisibilityChange); 23 this.disarm(); 24 } 25 26 handleVisibilityChange() { 27 // window.location.reload() does not commit synchronously, so a visibility event arriving 28 // in the gap before the navigation lands would otherwise start a second one. 29 if (this.reloading) return; 30 31 if (document.visibilityState !== "visible") { 32 // Polling stops entirely while hidden: a background tab that reloads on a timer spends 33 // requests nobody is reading, and the deadline above is enough to make the page current 34 // the moment someone looks at it again. 35 this.disarm(); 36 return; 37 } 38 39 this.arm(); 40 } 41 42 arm() { 43 if (this.timer) return; 44 if (document.visibilityState !== "visible") return; 45 46 const remaining = this.deadline - Date.now(); 47 48 // The deadline passed while the tab was hidden, so the page is showing state from before 49 // the operator looked away â with the spinner still claiming it is live. Refresh at once 50 // instead of waiting out a fresh interval. 51 if (remaining <= 0) { 52 this.refresh(); 53 return; 54 } 55 56 this.timer = setTimeout(() => this.refresh(), remaining); 57 } 58 59 disarm() { 60 if (!this.timer) return; 61 clearTimeout(this.timer); 62 this.timer = null; 63 } 64 65 refresh() { 66 this.reloading = true; 67 window.location.reload(); 68 } 69}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.