UI: guard host actions on missing context (#66)
All checks were successful
CI / test-and-quality (push) Successful in 1m35s
CI / test-and-quality (pull_request) Successful in 1m37s

This commit is contained in:
2026-02-28 02:09:05 +01:00
parent 7e7445cd07
commit c9b4fe0077
2 changed files with 16 additions and 9 deletions

View File

@@ -9,13 +9,14 @@
<button id="startRoundBtn" onclick="startRound()" disabled>2) Start runde</button> <button id="startRoundBtn" onclick="startRound()" disabled>2) Start runde</button>
<p id="startRoundHint">Kræver mindst 3 spillere i lobbyen.</p> <p id="startRoundHint">Kræver mindst 3 spillere i lobbyen.</p>
<p id="playerCountStatus">Spillere i session: ukendt</p> <p id="playerCountStatus">Spillere i session: ukendt</p>
<button onclick="showQuestion()">3) Vis spørgsmål</button> <button id="showQuestionBtn" onclick="showQuestion()" disabled>3) Vis spørgsmål</button>
<input id="roundQuestionId" placeholder="Round question id"> <input id="roundQuestionId" placeholder="Round question id">
<button onclick="mixAnswers()">4) Mix svar</button> <button id="mixAnswersBtn" onclick="mixAnswers()" disabled>4) Mix svar</button>
<button onclick="calcScores()">5) Beregn score</button> <button id="calcScoresBtn" onclick="calcScores()" disabled>5) Beregn score</button>
<button onclick="showScoreboard()">6) Scoreboard</button> <button id="showScoreboardBtn" onclick="showScoreboard()" disabled>6) Scoreboard</button>
<button onclick="nextRound()">7) Næste runde</button> <button id="nextRoundBtn" onclick="nextRound()" disabled>7) Næste runde</button>
<button onclick="finishGame()">8) Afslut spil</button> <button id="finishGameBtn" onclick="finishGame()" disabled>8) Afslut spil</button>
<p id="hostActionHint">Angiv sessionkode for at aktivere host-actions.</p>
<button onclick="sessionDetail()">Session-status</button> <button onclick="sessionDetail()">Session-status</button>
<pre id="out">Klar.</pre> <pre id="out">Klar.</pre>
<script> <script>
@@ -25,7 +26,8 @@ function rq(){return document.getElementById("roundQuestionId").value.trim();}
function saveHostContext(){try{localStorage.setItem("wppHostContext",JSON.stringify({code:code(),round_question_id:rq()}));}catch(_e){}} function saveHostContext(){try{localStorage.setItem("wppHostContext",JSON.stringify({code:code(),round_question_id:rq()}));}catch(_e){}}
function restoreHostContext(){try{var raw=localStorage.getItem("wppHostContext");if(!raw){return false;}var ctx=JSON.parse(raw);if(ctx.code){document.getElementById("code").value=(ctx.code||"").toUpperCase();}if(ctx.round_question_id){document.getElementById("roundQuestionId").value=ctx.round_question_id;}return !!ctx.code;}catch(_e){return false;}} function restoreHostContext(){try{var raw=localStorage.getItem("wppHostContext");if(!raw){return false;}var ctx=JSON.parse(raw);if(ctx.code){document.getElementById("code").value=(ctx.code||"").toUpperCase();}if(ctx.round_question_id){document.getElementById("roundQuestionId").value=ctx.round_question_id;}return !!ctx.code;}catch(_e){return false;}}
function syncStartRoundGuard(data){var btn=document.getElementById("startRoundBtn");var hint=document.getElementById("startRoundHint");var status=document.getElementById("playerCountStatus");if(!btn||!hint||!status){return;}var count=(data&&data.session&&typeof data.session.players_count==="number")?data.session.players_count:null;if(count===null){btn.disabled=true;status.textContent="Spillere i session: ukendt";hint.textContent="Opdatér session-status for at validere min. 3 spillere.";return;}status.textContent="Spillere i session: "+count;if(count<3){btn.disabled=true;hint.textContent="Mangler spillere: kræver mindst 3 for at starte runde.";return;}btn.disabled=false;hint.textContent="Klar: nok spillere til at starte runde.";} function syncStartRoundGuard(data){var btn=document.getElementById("startRoundBtn");var hint=document.getElementById("startRoundHint");var status=document.getElementById("playerCountStatus");if(!btn||!hint||!status){return;}var count=(data&&data.session&&typeof data.session.players_count==="number")?data.session.players_count:null;if(count===null){btn.disabled=true;status.textContent="Spillere i session: ukendt";hint.textContent="Opdatér session-status for at validere min. 3 spillere.";return;}status.textContent="Spillere i session: "+count;if(count<3){btn.disabled=true;hint.textContent="Mangler spillere: kræver mindst 3 for at starte runde.";return;}btn.disabled=false;hint.textContent="Klar: nok spillere til at starte runde.";}
async function api(path,method,payload){var o={method:method||"GET",headers:{"Accept":"application/json"}};if(payload!==null){o.headers["Content-Type"]="application/json";o.headers["X-CSRFToken"]=csrf();o.body=JSON.stringify(payload);}var r=await fetch(path,o);var d=await r.json().catch(function(){return {};});document.getElementById("out").textContent=JSON.stringify({status:r.status,data:d},null,2);if(d.session&&d.session.code){document.getElementById("code").value=d.session.code;}if(d.round_question&&d.round_question.id){document.getElementById("roundQuestionId").value=d.round_question.id;}syncStartRoundGuard(d);saveHostContext();return d;} function updateHostActionState(){var hasCode=!!code();var hasRound=!!rq();var showQuestionBtn=document.getElementById("showQuestionBtn");var mixAnswersBtn=document.getElementById("mixAnswersBtn");var calcScoresBtn=document.getElementById("calcScoresBtn");var showScoreboardBtn=document.getElementById("showScoreboardBtn");var nextRoundBtn=document.getElementById("nextRoundBtn");var finishGameBtn=document.getElementById("finishGameBtn");var hint=document.getElementById("hostActionHint");if(showQuestionBtn){showQuestionBtn.disabled=!hasCode;}if(showScoreboardBtn){showScoreboardBtn.disabled=!hasCode;}if(nextRoundBtn){nextRoundBtn.disabled=!hasCode;}if(finishGameBtn){finishGameBtn.disabled=!hasCode;}if(mixAnswersBtn){mixAnswersBtn.disabled=!hasCode||!hasRound;}if(calcScoresBtn){calcScoresBtn.disabled=!hasCode||!hasRound;}if(!hint){return;}if(!hasCode){hint.textContent="Angiv sessionkode for at aktivere host-actions.";return;}if(!hasRound){hint.textContent="Round question id mangler: mix/beregn score er låst.";return;}hint.textContent="Host-actions er klar.";}
async function api(path,method,payload){var o={method:method||"GET",headers:{"Accept":"application/json"}};if(payload!==null){o.headers["Content-Type"]="application/json";o.headers["X-CSRFToken"]=csrf();o.body=JSON.stringify(payload);}var r=await fetch(path,o);var d=await r.json().catch(function(){return {};});document.getElementById("out").textContent=JSON.stringify({status:r.status,data:d},null,2);if(d.session&&d.session.code){document.getElementById("code").value=d.session.code;}if(d.round_question&&d.round_question.id){document.getElementById("roundQuestionId").value=d.round_question.id;}syncStartRoundGuard(d);updateHostActionState();saveHostContext();return d;}
function createSession(){return api("/lobby/sessions/create","POST",{});} function createSession(){return api("/lobby/sessions/create","POST",{});}
function sessionDetail(){return api("/lobby/sessions/"+code(),"GET",null);} function sessionDetail(){return api("/lobby/sessions/"+code(),"GET",null);}
function startRound(){if(document.getElementById("startRoundBtn").disabled){return Promise.resolve({error:"not_enough_players_client_guard"});}return api("/lobby/sessions/"+code()+"/rounds/start","POST",{category_slug:document.getElementById("category").value});} function startRound(){if(document.getElementById("startRoundBtn").disabled){return Promise.resolve({error:"not_enough_players_client_guard"});}return api("/lobby/sessions/"+code()+"/rounds/start","POST",{category_slug:document.getElementById("category").value});}
@@ -35,8 +37,8 @@ function calcScores(){return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/
function showScoreboard(){return api("/lobby/sessions/"+code()+"/scoreboard","GET",null);} function showScoreboard(){return api("/lobby/sessions/"+code()+"/scoreboard","GET",null);}
function nextRound(){return api("/lobby/sessions/"+code()+"/rounds/next","POST",{});} function nextRound(){return api("/lobby/sessions/"+code()+"/rounds/next","POST",{});}
function finishGame(){return api("/lobby/sessions/"+code()+"/finish","POST",{});} function finishGame(){return api("/lobby/sessions/"+code()+"/finish","POST",{});}
["code","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){syncStartRoundGuard(null);saveHostContext();});field.addEventListener("change",function(){syncStartRoundGuard(null);saveHostContext();});}); ["code","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){syncStartRoundGuard(null);updateHostActionState();saveHostContext();});field.addEventListener("change",function(){syncStartRoundGuard(null);updateHostActionState();saveHostContext();});});
syncStartRoundGuard(null); syncStartRoundGuard(null);updateHostActionState();
if(restoreHostContext()){sessionDetail();}else{saveHostContext();} if(restoreHostContext()){sessionDetail();}else{saveHostContext();}
</script> </script>
</body></html> </body></html>

View File

@@ -775,6 +775,11 @@ class UiScreenTests(TestCase):
self.assertContains(response, "Host panel") self.assertContains(response, "Host panel")
self.assertContains(response, "saveHostContext") self.assertContains(response, "saveHostContext")
self.assertContains(response, "restoreHostContext") self.assertContains(response, "restoreHostContext")
self.assertContains(response, "id=\"showQuestionBtn\"")
self.assertContains(response, "id=\"mixAnswersBtn\"")
self.assertContains(response, "id=\"hostActionHint\"")
self.assertContains(response, "updateHostActionState")
self.assertContains(response, "Angiv sessionkode for at aktivere host-actions.")
def test_player_screen_is_public(self): def test_player_screen_is_public(self):
response = self.client.get(reverse("lobby:player_screen")) response = self.client.get(reverse("lobby:player_screen"))