Add polling to currently hacking section (#277)

This commit is contained in:
Max Wofford 2025-06-01 13:41:46 -04:00 committed by GitHub
parent ee4f893afe
commit b20509cef0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 137 additions and 3 deletions

View file

@ -140,7 +140,19 @@ class StaticPagesController < ApplicationController
def currently_hacking
locals = Cache::CurrentlyHackingJob.perform_now
render partial: "currently_hacking", locals: locals
respond_to do |format|
format.html { render partial: "currently_hacking", locals: locals }
format.json do
json_response = { count: locals[:users].count }
# Only include HTML if explicitly requested (when list is visible)
if params[:include_list] == "true"
json_response[:html] = render_to_string(partial: "currently_hacking", locals: locals, formats: [ :html ])
end
render json: json_response
end
end
end
def streak

View file

@ -0,0 +1,116 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["container", "count"]
static values = {
interval: { type: Number, default: 60000 }, // 60 seconds to match cron
url: String
}
connect() {
this.lastFullFetch = Date.now() // Initialize to now to prevent immediate refetch on click
this.startPolling()
// Listen for visibility changes
this.boundToggleHandler = this.handleVisibilityToggle.bind(this)
document.addEventListener('click', this.boundToggleHandler)
}
disconnect() {
this.stopPolling()
document.removeEventListener('click', this.boundToggleHandler)
}
handleVisibilityToggle(event) {
const header = event.target.closest('.currently-hacking')
if (header && this.containerTarget.contains(header)) {
// Poll immediately when opening if we haven't fetched the list recently
setTimeout(() => {
if (this.isVisible()) {
const now = Date.now()
const timeSinceLastFetch = now - this.lastFullFetch
// Only fetch if we haven't fetched in the last 30 seconds
if (timeSinceLastFetch > 30000) {
this.poll()
}
}
}, 50) // Small delay to allow class toggle to complete
}
}
isVisible() {
return this.containerTarget.classList.contains('visible')
}
startPolling() {
this.stopPolling() // Clear any existing interval
this.poll() // Initial poll
this.intervalId = setInterval(() => {
this.poll()
}, this.intervalValue)
}
stopPolling() {
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
}
}
async poll() {
try {
const includeList = this.isVisible()
const url = new URL(this.urlValue, window.location.origin)
url.searchParams.set('include_list', includeList.toString())
// Track when we request the full list, not just when we get it back
if (includeList) {
this.lastFullFetch = Date.now()
}
const response = await fetch(url, {
headers: {
"Accept": "application/json"
}
})
if (response.ok) {
const data = await response.json()
this.updateCount(data.count)
if (data.html) {
this.updateFrame(data.html)
}
}
} catch (error) {
console.error("Failed to poll currently hacking:", error)
}
}
updateCount(count) {
if (this.hasCountTarget) {
const plural = count === 1 ? "person" : "people"
this.countTarget.textContent = `${count} ${plural} currently hacking`
}
}
updateFrame(html) {
const frame = document.getElementById("currently_hacking")
if (frame && html) {
// Save scroll position before updating
const scrollContainer = frame.querySelector(".currently-hacking-list")
const scrollTop = scrollContainer ? scrollContainer.scrollTop : 0
// Update content
frame.innerHTML = html
// Restore scroll position after a brief delay to allow DOM update
if (scrollTop > 0) {
requestAnimationFrame(() => {
const newScrollContainer = frame.querySelector(".currently-hacking-list")
if (newScrollContainer) {
newScrollContainer.scrollTop = scrollTop
}
})
}
}
}
}

View file

@ -168,12 +168,18 @@
</footer>
</main>
<div class="currently-hacking-container" data-controller="currently-hacking" data-currently-hacking-target="container">
<div class="currently-hacking-container"
data-controller="currently-hacking"
data-currently-hacking-target="container"
data-currently-hacking-url-value="<%= currently_hacking_static_pages_path %>"
data-currently-hacking-interval-value="60000">
<div class="currently-hacking">
<div class="currently-hacking-header">
<span>
<div class="live-indicator"></div>
<%= pluralize(Cache::CurrentlyHackingJob.perform_now[:users].count, "person") %> currently hacking
<span data-currently-hacking-target="count">
<%= pluralize(Cache::CurrentlyHackingJob.perform_now[:users].count, "person") %> currently hacking
</span>
</span>
</div>
</div>