mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 19:55:16 +00:00
Add polling to currently hacking section (#277)
This commit is contained in:
parent
ee4f893afe
commit
b20509cef0
3 changed files with 137 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
116
app/javascript/controllers/currently_hacking_controller.js
Normal file
116
app/javascript/controllers/currently_hacking_controller.js
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue