From ee250b96adddd209f717ebab45f1ceadd2c64b6a Mon Sep 17 00:00:00 2001 From: sto Date: Mon, 8 Dec 2025 17:11:47 +0100 Subject: [PATCH] Auto refresh feature on public scoreboards https://gitea.puzzle-scoreboard.org/sto/puzzle-scoreboard/issues/12 https://gitea.puzzle-scoreboard.org/sto/puzzle-scoreboard/issues/13 --- app/controllers/contests_controller.rb | 25 +++++---- .../_scoreboard_desktop_single.html.slim | 2 - .../_scoreboard_mobile_single.html.slim | 2 - app/views/contests/_selectors.html.slim | 44 ++++++--------- app/views/contests/scoreboard.html.slim | 17 +++--- app/views/layouts/authenticated.html.slim | 53 +++++++++++++++++-- config/locales/en.yml | 1 + config/locales/fr.yml | 1 + 8 files changed, 89 insertions(+), 56 deletions(-) diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 411d56f..55014fe 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -123,20 +123,19 @@ class ContestsController < ApplicationController @title = I18n.t("contests.scoreboard.title", name: @contest.name) @contestants = ranked_contestants(@contest) - filter_contestants_per_category + if params.key?(:category) + @category = params[:category] + filter_contestants_per_category + end if params.key?(:hide_offline) && params[:hide_offline] == "true" @contestants = @contestants.select { |contestant| !contestant.offline.present? } + @hide_offline = true + end + if params.key?(:autorefresh) + @autorefresh = true end @puzzles = @contest.puzzles.where(hidden: false).or(@contest.puzzles.where(hidden: nil)).order(:id) - if params.key?(:hide_offline) && params.key?(:category) - @action_path = "/public/#{@contest.friendly_id}?hide_offline=#{params[:hide_offline]}&category=#{params[:category]}" - elsif params.key?(:category) - @action_path = "/public/#{@contest.friendly_id}?category=#{params[:category]}" - elsif params.key?(:hide_offline) - @action_path = "/public/#{@contest.friendly_id}?hide_offline=#{params[:hide_offline]}" - else - @action_path = "/public/#{@contest.friendly_id}" - end + @action_path = "/public/#{@contest.friendly_id}" @space = " " render :scoreboard end @@ -254,11 +253,11 @@ class ContestsController < ApplicationController end def filter_contestants_per_category - if params.key?(:category) && params[:category] != "-1" - if params[:category] == "-2" + if @category != "-1" + if @category == "-2" @contestants = @contestants.select { |contestant| contestant.categories.size == 0 } else - @contestants = @contestants.select { |contestant| contestant.categories.where(id: params[:category]).any? } + @contestants = @contestants.select { |contestant| contestant.categories.where(id: @category).any? } end end end diff --git a/app/views/contests/_scoreboard_desktop_single.html.slim b/app/views/contests/_scoreboard_desktop_single.html.slim index 35bed93..95b3b76 100644 --- a/app/views/contests/_scoreboard_desktop_single.html.slim +++ b/app/views/contests/_scoreboard_desktop_single.html.slim @@ -1,5 +1,3 @@ -= render "selectors" - .row .mt-3.col-6.d-flex.flex-column style="height: calc(100vh - 250px)" .d-flex.flex-column style="overflow-y: auto" diff --git a/app/views/contests/_scoreboard_mobile_single.html.slim b/app/views/contests/_scoreboard_mobile_single.html.slim index 636527e..047721b 100644 --- a/app/views/contests/_scoreboard_mobile_single.html.slim +++ b/app/views/contests/_scoreboard_mobile_single.html.slim @@ -9,8 +9,6 @@ css: => "#{@puzzles[0].name} -" = "#{@puzzles[0].brand} #{@puzzles[0].pieces}p" -= render "selectors" - .row .mt-3.d-flex.flex-column style="height: calc(100vh - 250px)" .d-flex.flex-column style="overflow-y: auto" diff --git a/app/views/contests/_selectors.html.slim b/app/views/contests/_selectors.html.slim index 031767f..3665065 100644 --- a/app/views/contests/_selectors.html.slim +++ b/app/views/contests/_selectors.html.slim @@ -1,15 +1,3 @@ -javascript: - function updateParams() { - categorySelectEl = document.getElementById('categories'); - offlineInputEl = document.getElementById('offline'); - if (categorySelectEl && !offlineInputEl) { - window.location.replace(`/public/#{@contest.slug}?category=${categorySelectEl.value}`); - } else if (!categorySelectEl) { - window.location.replace(`/public/#{@contest.slug}?hide_offline=${offlineInputEl.checked}`); - } else { - window.location.replace(`/public/#{@contest.slug}?category=${categorySelectEl.value}&hide_offline=${offlineInputEl.checked}`); - } - } - if @contest.categories.size > 0 .row .col @@ -17,29 +5,29 @@ javascript: option value=-1 = t("contests.scoreboard.all_categories") - @contest.categories.each do |category| - option value=category.id - = category.name + - if @category == category.id.to_s + option value=category.id selected=true + = category.name + - else + option value=category.id + = category.name javascript: - categorySelectEl = document.getElementById('categories'); - urlParams = new URLSearchParams(window.location.search); - selectedCategory = urlParams.get('category'); - Array.from(categorySelectEl.children).forEach((option) => { - if (option.value == selectedCategory) option.selected = true; - }); - categorySelectEl.addEventListener('change', (e) => { - updateParams(); + document.getElementById('categories').addEventListener('change', (e) => { + addParam('category', e.target.value); }) - if @contest.offline_form && @contest.puzzles.length < 2 .row .col - input type="checkbox" id="offline" style="padding: 5px;" + - if @hide_offline + input type="checkbox" id="offline" style="padding: 5px;" checked=true + - else + input type="checkbox" id="offline" style="padding: 5px;" label for="offline" .ms-2 = t("contests.scoreboard.hide_offline") javascript: - offlineInputEl = document.getElementById('offline'); - urlParams = new URLSearchParams(window.location.search); - offlineInputEl.checked = urlParams.get('hide_offline') == "true"; - offlineInputEl.addEventListener('change', (e) => { - updateParams(); + document.getElementById('offline').addEventListener('change', (e) => { + console.log('changed'); + if (e.target.checked) addParam('hide_offline', e.target.checked); + else removeParam('hide_offline'); }) \ No newline at end of file diff --git a/app/views/contests/scoreboard.html.slim b/app/views/contests/scoreboard.html.slim index 7c8db60..c4c3f40 100644 --- a/app/views/contests/scoreboard.html.slim +++ b/app/views/contests/scoreboard.html.slim @@ -2,15 +2,20 @@ css: @media (max-width: 800px) { .mobile-single { display: block !important; } .desktop-single { display: none; } + #scoreboard-switches { display: none; } } +- if @contest.puzzles.size < 2 = render "selectors" -- if @contest.puzzles.size < 2 - .mobile-single style="display: none;" - = render "scoreboard_mobile_single" - .desktop-single - = render "scoreboard_desktop_single" + turbo-frame id="scoreboard" + a.btn.btn-primary href="" id="refresh-button" style="display: none;" + .mobile-single style="display: none;" + = render "scoreboard_mobile_single" + .desktop-single + = render "scoreboard_desktop_single" - else - = render "scoreboard_desktop_marathon" \ No newline at end of file + turbo-frame id="scoreboard" + a.btn.btn-primary href="" id="refresh-button" style="display: none;" + = render "scoreboard_desktop_marathon" \ No newline at end of file diff --git a/app/views/layouts/authenticated.html.slim b/app/views/layouts/authenticated.html.slim index fbfe452..418ff5c 100644 --- a/app/views/layouts/authenticated.html.slim +++ b/app/views/layouts/authenticated.html.slim @@ -43,19 +43,62 @@ html .toast-body = msg - h1.mb-5 + h1.mb-4 - if @contest && @contest.id.present? - = @contest.name - if active_page("/public") == "active" && @action_path - a.ms-4.btn.btn-primary href=@action_path style="margin-top: -6px" - = t("helpers.buttons.refresh") - - if active_page("/contests") == "active" + = @contest.name + .float-end style="margin-top: -5px;" id="scoreboard-switches" + .d-inline-flex.align-items-center + .ms-4.form-check.form-switch style="font-size: 16px; font-weight: 300;" + input.form-check-input type="checkbox" id="refresh-checkbox" + label.ms-1 style="font-size: 16px; font-weight: 300;" + = t("contests.scoreboard.auto_refresh") + .js data-turbo="false" + javascript: + function refresh() { + if (document.getElementById('refresh-checkbox').checked) { + addParam('autorefresh', 1); + setTimeout(refresh, 30000); + } + } + function addParam(key, value) { + const urlParams = new URLSearchParams(window.location.search); + urlParams.delete(key); + urlParams.append(key, value); + const refreshBtn = document.getElementById('refresh-button') + refreshBtn.href = `/public/#{@contest.friendly_id}?${urlParams.toString()}`; + refreshBtn.click(); + } + function removeParam(key) { + const urlParams = new URLSearchParams(window.location.search); + urlParams.delete(key); + const refreshBtn = document.getElementById('refresh-button') + refreshBtn.href = `/public/#{@contest.friendly_id}?${urlParams.toString()}`; + refreshBtn.click(); + } + function autoRefresh() { + if (document.getElementById('refresh-checkbox').checked) setTimeout(refresh, 30000); + document.getElementById('refresh-checkbox').addEventListener('change', (e) => { + if (e.target.checked) refresh(); + else removeParam('autorefresh'); + }); + } + async function startAutoRefresh(count) { + if (count == 0) return; + if (document.getElementById('refresh-button') && document.getElementById('refresh-checkbox')) autoRefresh(); + else setTimeout(() => startAutoRefresh(count - 1), 10); + } + startAutoRefresh(200); + - elsif active_page("/contests") == "active" + = @contest.name - if @contest.public a.ms-4.btn.btn-success href="/public/#{@contest.slug}" style="margin-top: -6px;" = t("contests.show.open_public_scoreboard") - else a.ms-4.btn.btn-success.disabled style="margin-top: -6px;" = t("contests.show.public_scoreboard_disabled") + - else + = @contest.name - else = @title diff --git a/config/locales/en.yml b/config/locales/en.yml index a7c64ed..cfe2a22 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -211,6 +211,7 @@ en: title: New jigsaw puzzle contest scoreboard: all_categories: All categories + auto_refresh: Auto refresh hide_offline: Hide offline participants refresh: Activate auto-refresh (every 5s) title: "%{name}" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 84e1d0c..04604b3 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -182,6 +182,7 @@ fr: title: Nouveau concours scoreboard: all_categories: Toutes les catégories + auto_refresh: Auto-rafraichissement hide_offline: Cacher les participant.e.s hors-ligne refresh: Activer le rafraichissement automatique de la page (toutes les 5s) title: "%{name}"