ui: phase-lock player lie/guess actions (#72)
All checks were successful
CI / test-and-quality (push) Successful in 1m45s
CI / test-and-quality (pull_request) Successful in 1m45s

This commit is contained in:
2026-02-28 02:50:55 +01:00
parent 5c8faf76a9
commit b030ae6d4e
2 changed files with 15 additions and 4 deletions

View File

@@ -7,6 +7,7 @@
#guessStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; } #guessStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; }
#lieStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; } #lieStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; }
#joinStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; } #joinStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; }
#phaseStatus { margin: 6px 0 10px; font-size: 0.95rem; color: #333; }
#lieStatus.locked { color: #0a5f2d; font-weight: 600; } #lieStatus.locked { color: #0a5f2d; font-weight: 600; }
#guessStatus.locked { color: #0a5f2d; font-weight: 600; } #guessStatus.locked { color: #0a5f2d; font-weight: 600; }
</style> </style>
@@ -28,6 +29,7 @@
<p id="guessStatus">Vælg et svar.</p> <p id="guessStatus">Vælg et svar.</p>
<button id="guessSubmitBtn" onclick="submitGuess()" disabled>3) Submit gæt</button> <button id="guessSubmitBtn" onclick="submitGuess()" disabled>3) Submit gæt</button>
<button onclick="sessionDetail()">Opdater session-status</button> <button onclick="sessionDetail()">Opdater session-status</button>
<p id="phaseStatus">Fase: ukendt (opdatér session-status).</p>
<pre id="out">Klar.</pre> <pre id="out">Klar.</pre>
<script> <script>
var availableAnswers=[]; var availableAnswers=[];
@@ -35,9 +37,12 @@ var guessSubmitted=false;
var lieSubmitted=false; var lieSubmitted=false;
var PLAYER_CONTEXT_KEY="wppPlayerContext"; var PLAYER_CONTEXT_KEY="wppPlayerContext";
var joinInFlight=false; var joinInFlight=false;
var currentSessionStatus="";
function code(){return document.getElementById("code").value.trim().toUpperCase();} function code(){return document.getElementById("code").value.trim().toUpperCase();}
function pid(){return document.getElementById("playerId").value.trim();} function pid(){return document.getElementById("playerId").value.trim();}
function rq(){return document.getElementById("roundQuestionId").value.trim();} function rq(){return document.getElementById("roundQuestionId").value.trim();}
function phaseLabel(status){if(status==="lobby"){return"Lobby";}if(status==="lie"){return"Løgn";}if(status==="guess"){return"Gæt";}if(status==="reveal"){return"Reveal";}if(status==="finished"){return"Afsluttet";}return"Ukendt";}
function updatePhaseStatus(){var el=document.getElementById("phaseStatus");if(!el){return;}if(!currentSessionStatus){el.textContent="Fase: ukendt (opdatér session-status).";return;}el.textContent="Fase: "+phaseLabel(currentSessionStatus)+" ("+currentSessionStatus+")";}
function savePlayerContext(){try{localStorage.setItem(PLAYER_CONTEXT_KEY,JSON.stringify({code:code(),nickname:document.getElementById("nickname").value.trim(),player_id:pid(),session_token:document.getElementById("sessionToken").value.trim(),round_question_id:rq()}));}catch(_e){}} function savePlayerContext(){try{localStorage.setItem(PLAYER_CONTEXT_KEY,JSON.stringify({code:code(),nickname:document.getElementById("nickname").value.trim(),player_id:pid(),session_token:document.getElementById("sessionToken").value.trim(),round_question_id:rq()}));}catch(_e){}}
function loadPlayerContext(){try{var raw=localStorage.getItem(PLAYER_CONTEXT_KEY);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}} function loadPlayerContext(){try{var raw=localStorage.getItem(PLAYER_CONTEXT_KEY);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}}
function restorePlayerContext(){var ctx=loadPlayerContext();if(!ctx){return false;}if(ctx.code){document.getElementById("code").value=(ctx.code||"").toUpperCase();}if(ctx.nickname){document.getElementById("nickname").value=ctx.nickname;}if(ctx.player_id){document.getElementById("playerId").value=ctx.player_id;}if(ctx.session_token){document.getElementById("sessionToken").value=ctx.session_token;}if(ctx.round_question_id){document.getElementById("roundQuestionId").value=ctx.round_question_id;}return !!(ctx.code&&ctx.player_id&&ctx.session_token);} function restorePlayerContext(){var ctx=loadPlayerContext();if(!ctx){return false;}if(ctx.code){document.getElementById("code").value=(ctx.code||"").toUpperCase();}if(ctx.nickname){document.getElementById("nickname").value=ctx.nickname;}if(ctx.player_id){document.getElementById("playerId").value=ctx.player_id;}if(ctx.session_token){document.getElementById("sessionToken").value=ctx.session_token;}if(ctx.round_question_id){document.getElementById("roundQuestionId").value=ctx.round_question_id;}return !!(ctx.code&&ctx.player_id&&ctx.session_token);}
@@ -49,22 +54,23 @@ function guessStorageKey(){var c=code();var p=pid();var q=rq();if(!c||!p||!q){re
function lieStorageKey(){var c=code();var p=pid();var q=rq();if(!c||!p||!q){return "";}return ["wppLie",c,p,q].join(":");} function lieStorageKey(){var c=code();var p=pid();var q=rq();if(!c||!p||!q){return "";}return ["wppLie",c,p,q].join(":");}
function persistLieState(text,submitted){var key=lieStorageKey();if(!key){return;}try{localStorage.setItem(key,JSON.stringify({text:text||"",submitted:!!submitted}));}catch(_e){}} function persistLieState(text,submitted){var key=lieStorageKey();if(!key){return;}try{localStorage.setItem(key,JSON.stringify({text:text||"",submitted:!!submitted}));}catch(_e){}}
function loadLieState(){var key=lieStorageKey();if(!key){return null;}try{var raw=localStorage.getItem(key);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}} function loadLieState(){var key=lieStorageKey();if(!key){return null;}try{var raw=localStorage.getItem(key);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}}
function updateLieSubmitState(){var text=(document.getElementById("lieText").value||"").trim();var btn=document.getElementById("lieSubmitBtn");var input=document.getElementById("lieText");var status=document.getElementById("lieStatus");var hasContext=hasSubmitContext();if(input){input.readOnly=lieSubmitted;}if(btn){btn.disabled=lieSubmitted||!text||!hasContext;}if(status){if(lieSubmitted){status.textContent="Løgn sendt input er låst.";status.classList.add("locked");}else{status.classList.remove("locked");if(!hasContext){status.textContent="Join først for at aktivere submit.";}else{status.textContent=text?"Løgn klar til afsendelse.":"Skriv din løgn.";}}}} function updateLieSubmitState(){var text=(document.getElementById("lieText").value||"").trim();var btn=document.getElementById("lieSubmitBtn");var input=document.getElementById("lieText");var status=document.getElementById("lieStatus");var hasContext=hasSubmitContext();var inLiePhase=currentSessionStatus==="lie";if(input){input.readOnly=lieSubmitted||!inLiePhase;}if(btn){btn.disabled=lieSubmitted||!text||!hasContext||!inLiePhase;}if(status){if(lieSubmitted){status.textContent="Løgn sendt input er låst.";status.classList.add("locked");}else{status.classList.remove("locked");if(!hasContext){status.textContent="Join først for at aktivere submit.";}else if(!currentSessionStatus){status.textContent="Opdatér session-status for at validere løgn-fase.";}else if(!inLiePhase){status.textContent="Løgn-input er låst i fase: "+phaseLabel(currentSessionStatus)+".";}else{status.textContent=text?"Løgn klar til afsendelse.":"Skriv din løgn.";}}}}
function setLieState(text,submitted){document.getElementById("lieText").value=text||"";if(typeof submitted==="boolean"){lieSubmitted=submitted;}updateLieSubmitState();} function setLieState(text,submitted){document.getElementById("lieText").value=text||"";if(typeof submitted==="boolean"){lieSubmitted=submitted;}updateLieSubmitState();}
function persistGuessState(text,submitted){var key=guessStorageKey();if(!key){return;}try{localStorage.setItem(key,JSON.stringify({selected_text:text||"",submitted:!!submitted}));}catch(_e){}} function persistGuessState(text,submitted){var key=guessStorageKey();if(!key){return;}try{localStorage.setItem(key,JSON.stringify({selected_text:text||"",submitted:!!submitted}));}catch(_e){}}
function loadGuessState(){var key=guessStorageKey();if(!key){return null;}try{var raw=localStorage.getItem(key);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}} function loadGuessState(){var key=guessStorageKey();if(!key){return null;}try{var raw=localStorage.getItem(key);if(!raw){return null;}return JSON.parse(raw);}catch(_e){return null;}}
function updateGuessStatus(){var el=document.getElementById("guessStatus");if(!el){return;}var selected=document.getElementById("guessText").value;var hasContext=hasSubmitContext();if(guessSubmitted){el.textContent="Gæt sendt valg er låst.";el.classList.add("locked");return;}el.classList.remove("locked");if(!hasContext){el.textContent="Join først for at aktivere gæt.";return;}el.textContent=selected?"Valgt svar klar til afsendelse.":"Vælg et svar.";} function updateGuessStatus(){var el=document.getElementById("guessStatus");if(!el){return;}var selected=document.getElementById("guessText").value;var hasContext=hasSubmitContext();var inGuessPhase=currentSessionStatus==="guess";if(guessSubmitted){el.textContent="Gæt sendt valg er låst.";el.classList.add("locked");return;}el.classList.remove("locked");if(!hasContext){el.textContent="Join først for at aktivere gæt.";return;}if(!currentSessionStatus){el.textContent="Opdatér session-status for at validere gæt-fase.";return;}if(!inGuessPhase){el.textContent="Gæt er låst i fase: "+phaseLabel(currentSessionStatus)+".";return;}el.textContent=selected?"Valgt svar klar til afsendelse.":"Vælg et svar.";}
function updateGuessSubmitState(){var selected=document.getElementById("guessText").value;var hasValid=availableAnswers.indexOf(selected)!==-1;var hasContext=hasSubmitContext();document.getElementById("guessSubmitBtn").disabled=guessSubmitted||!hasValid||!hasContext;var buttons=document.querySelectorAll("#answerOptions button");buttons.forEach(function(btn){btn.disabled=guessSubmitted||!hasContext;});updateGuessStatus();} function updateGuessSubmitState(){var selected=document.getElementById("guessText").value;var hasValid=availableAnswers.indexOf(selected)!==-1;var hasContext=hasSubmitContext();var inGuessPhase=currentSessionStatus==="guess";document.getElementById("guessSubmitBtn").disabled=guessSubmitted||!hasValid||!hasContext||!inGuessPhase;var buttons=document.querySelectorAll("#answerOptions button");buttons.forEach(function(btn){btn.disabled=guessSubmitted||!hasContext||!inGuessPhase;});updateGuessStatus();}
function setGuess(text,submitted){document.getElementById("guessText").value=text||"";if(typeof submitted==="boolean"){guessSubmitted=submitted;}var buttons=document.querySelectorAll("#answerOptions button");buttons.forEach(function(btn){btn.classList.toggle("active",btn.dataset.answer===text);});updateGuessSubmitState(); function setGuess(text,submitted){document.getElementById("guessText").value=text||"";if(typeof submitted==="boolean"){guessSubmitted=submitted;}var buttons=document.querySelectorAll("#answerOptions button");buttons.forEach(function(btn){btn.classList.toggle("active",btn.dataset.answer===text);});updateGuessSubmitState();
updateJoinState();} updateJoinState();}
function renderAnswerOptions(roundQuestion){var wrap=document.getElementById("answerOptions");wrap.innerHTML="";availableAnswers=[];guessSubmitted=false;setGuess("",false);lieSubmitted=false;setLieState("",false);if(!roundQuestion||!Array.isArray(roundQuestion.answers)){updateGuessSubmitState();updateLieSubmitState();return;}roundQuestion.answers.forEach(function(item){if(!item||!item.text){return;}availableAnswers.push(item.text);var btn=document.createElement("button");btn.type="button";btn.dataset.answer=item.text;btn.textContent=item.text;btn.onclick=function(){if(guessSubmitted){return;}setGuess(item.text,false);persistGuessState(item.text,false);};wrap.appendChild(btn);});var saved=loadGuessState();if(saved&&availableAnswers.indexOf(saved.selected_text)!==-1){setGuess(saved.selected_text,!!saved.submitted);}updateGuessSubmitState();} function renderAnswerOptions(roundQuestion){var wrap=document.getElementById("answerOptions");wrap.innerHTML="";availableAnswers=[];guessSubmitted=false;setGuess("",false);lieSubmitted=false;setLieState("",false);if(!roundQuestion||!Array.isArray(roundQuestion.answers)){updateGuessSubmitState();updateLieSubmitState();return;}roundQuestion.answers.forEach(function(item){if(!item||!item.text){return;}availableAnswers.push(item.text);var btn=document.createElement("button");btn.type="button";btn.dataset.answer=item.text;btn.textContent=item.text;btn.onclick=function(){if(guessSubmitted){return;}setGuess(item.text,false);persistGuessState(item.text,false);};wrap.appendChild(btn);});var saved=loadGuessState();if(saved&&availableAnswers.indexOf(saved.selected_text)!==-1){setGuess(saved.selected_text,!!saved.submitted);}updateGuessSubmitState();}
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.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.player&&d.player.id){document.getElementById("playerId").value=d.player.id;}if(d.player&&d.player.session_token){document.getElementById("sessionToken").value=d.player.session_token;}if(d.round_question&&d.round_question.id){document.getElementById("roundQuestionId").value=d.round_question.id;}if(d.round_question){renderAnswerOptions(d.round_question);var savedLie=loadLieState();if(savedLie){setLieState(savedLie.text||"",!!savedLie.submitted);}}if(d.guess&&d.guess.round_question_id){document.getElementById("roundQuestionId").value=d.guess.round_question_id;setGuess(d.guess.selected_text||"",true);persistGuessState(d.guess.selected_text||"",true);}updateLieSubmitState();updateGuessSubmitState();savePlayerContext();return d;} 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.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.player&&d.player.id){document.getElementById("playerId").value=d.player.id;}if(d.player&&d.player.session_token){document.getElementById("sessionToken").value=d.player.session_token;}if(d.round_question&&d.round_question.id){document.getElementById("roundQuestionId").value=d.round_question.id;}if(d.session&&d.session.status){currentSessionStatus=d.session.status;}if(d.round_question){renderAnswerOptions(d.round_question);var savedLie=loadLieState();if(savedLie){setLieState(savedLie.text||"",!!savedLie.submitted);}}if(d.guess&&d.guess.round_question_id){document.getElementById("roundQuestionId").value=d.guess.round_question_id;setGuess(d.guess.selected_text||"",true);persistGuessState(d.guess.selected_text||"",true);}updatePhaseStatus();updateLieSubmitState();updateGuessSubmitState();savePlayerContext();return d;}
function joinSession(){if(joinInFlight){return Promise.resolve({error:"join_in_flight"});}if(!canAttemptJoin()){updateJoinState();return Promise.resolve({error:"missing_join_input"});}if(pid()&&document.getElementById("sessionToken").value.trim()){updateJoinState();return Promise.resolve({error:"already_joined_client"});}joinInFlight=true;updateJoinState();return api("/lobby/sessions/join","POST",{code:code(),nickname:document.getElementById("nickname").value.trim()}).then(function(d){joinInFlight=false;if(d&&d.player&&d.player.id){updateJoinState();return d;}updateJoinState();document.getElementById("joinStatus").textContent="Join fejlede prøv igen.";return d;}).catch(function(err){joinInFlight=false;updateJoinState();document.getElementById("joinStatus").textContent="Join fejlede prøv igen.";throw err;});} function joinSession(){if(joinInFlight){return Promise.resolve({error:"join_in_flight"});}if(!canAttemptJoin()){updateJoinState();return Promise.resolve({error:"missing_join_input"});}if(pid()&&document.getElementById("sessionToken").value.trim()){updateJoinState();return Promise.resolve({error:"already_joined_client"});}joinInFlight=true;updateJoinState();return api("/lobby/sessions/join","POST",{code:code(),nickname:document.getElementById("nickname").value.trim()}).then(function(d){joinInFlight=false;if(d&&d.player&&d.player.id){updateJoinState();return d;}updateJoinState();document.getElementById("joinStatus").textContent="Join fejlede prøv igen.";return d;}).catch(function(err){joinInFlight=false;updateJoinState();document.getElementById("joinStatus").textContent="Join fejlede prøv igen.";throw err;});}
function sessionDetail(){return api("/lobby/sessions/"+code(),"GET",null);} function sessionDetail(){return api("/lobby/sessions/"+code(),"GET",null);}
function submitLie(){if(lieSubmitted){return Promise.resolve({error:"lie_already_submitted_client"});}if(!hasSubmitContext()){updateLieSubmitState();return Promise.resolve({error:"missing_submit_context"});}var text=(document.getElementById("lieText").value||"").trim();if(!text){updateLieSubmitState();return Promise.resolve({error:"empty_lie_text"});}return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/lies/submit","POST",{player_id:parseInt(pid(),10),session_token:document.getElementById("sessionToken").value,text:text}).then(function(d){if(d&&d.lie&&d.lie.id){lieSubmitted=true;persistLieState(text,true);updateLieSubmitState();}return d;});} function submitLie(){if(lieSubmitted){return Promise.resolve({error:"lie_already_submitted_client"});}if(!hasSubmitContext()){updateLieSubmitState();return Promise.resolve({error:"missing_submit_context"});}var text=(document.getElementById("lieText").value||"").trim();if(!text){updateLieSubmitState();return Promise.resolve({error:"empty_lie_text"});}return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/lies/submit","POST",{player_id:parseInt(pid(),10),session_token:document.getElementById("sessionToken").value,text:text}).then(function(d){if(d&&d.lie&&d.lie.id){lieSubmitted=true;persistLieState(text,true);updateLieSubmitState();}return d;});}
document.getElementById("lieText").addEventListener("input",function(){if(!lieSubmitted){updateLieSubmitState();persistLieState(document.getElementById("lieText").value,false);}});updateLieSubmitState(); document.getElementById("lieText").addEventListener("input",function(){if(!lieSubmitted){updateLieSubmitState();persistLieState(document.getElementById("lieText").value,false);}});updateLieSubmitState();
function submitGuess(){if(!hasSubmitContext()){updateGuessSubmitState();document.getElementById("out").textContent=JSON.stringify({status:400,data:{error:"Join først for at aktivere gæt"}},null,2);return Promise.resolve({error:"missing_submit_context"});}var selected=document.getElementById("guessText").value;if(availableAnswers.indexOf(selected)===-1){document.getElementById("out").textContent=JSON.stringify({status:400,data:{error:"Vælg et af de viste svarmuligheder"}},null,2);return Promise.resolve({error:"invalid_client_guess"});}return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/guesses/submit","POST",{player_id:parseInt(pid(),10),session_token:document.getElementById("sessionToken").value,selected_text:selected});} function submitGuess(){if(!hasSubmitContext()){updateGuessSubmitState();document.getElementById("out").textContent=JSON.stringify({status:400,data:{error:"Join først for at aktivere gæt"}},null,2);return Promise.resolve({error:"missing_submit_context"});}var selected=document.getElementById("guessText").value;if(availableAnswers.indexOf(selected)===-1){document.getElementById("out").textContent=JSON.stringify({status:400,data:{error:"Vælg et af de viste svarmuligheder"}},null,2);return Promise.resolve({error:"invalid_client_guess"});}return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/guesses/submit","POST",{player_id:parseInt(pid(),10),session_token:document.getElementById("sessionToken").value,selected_text:selected});}
["code","nickname","playerId","sessionToken","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){updateLieSubmitState();updateGuessSubmitState();updateJoinState();savePlayerContext();});field.addEventListener("change",function(){updateLieSubmitState();updateGuessSubmitState();updateJoinState();savePlayerContext();});}); ["code","nickname","playerId","sessionToken","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){updateLieSubmitState();updateGuessSubmitState();updateJoinState();savePlayerContext();});field.addEventListener("change",function(){updateLieSubmitState();updateGuessSubmitState();updateJoinState();savePlayerContext();});});
updatePhaseStatus();
updateGuessSubmitState(); updateGuessSubmitState();
updateJoinState(); updateJoinState();
if(restorePlayerContext()){sessionDetail();}else{savePlayerContext();} if(restorePlayerContext()){sessionDetail();}else{savePlayerContext();}

View File

@@ -803,6 +803,11 @@ class UiScreenTests(TestCase):
self.assertContains(response, "restorePlayerContext") self.assertContains(response, "restorePlayerContext")
self.assertContains(response, "id=\"lieSubmitBtn\"") self.assertContains(response, "id=\"lieSubmitBtn\"")
self.assertContains(response, "id=\"lieStatus\"") self.assertContains(response, "id=\"lieStatus\"")
self.assertContains(response, "id=\"phaseStatus\"")
self.assertContains(response, "currentSessionStatus")
self.assertContains(response, "updatePhaseStatus")
self.assertContains(response, "Løgn-input er låst i fase")
self.assertContains(response, "Gæt er låst i fase")
self.assertContains(response, "persistLieState") self.assertContains(response, "persistLieState")
self.assertContains(response, "updateLieSubmitState") self.assertContains(response, "updateLieSubmitState")
self.assertContains(response, "hasSubmitContext") self.assertContains(response, "hasSubmitContext")