diff --git a/lobby/templates/lobby/host_screen.html b/lobby/templates/lobby/host_screen.html index 971a619..d898071 100644 --- a/lobby/templates/lobby/host_screen.html +++ b/lobby/templates/lobby/host_screen.html @@ -22,8 +22,10 @@ function csrf(){var m=document.cookie.match(/csrftoken=([^;]+)/);return m?m[1]:"";} function code(){return document.getElementById("code").value.trim().toUpperCase();} 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 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.";} -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);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.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 createSession(){return api("/lobby/sessions/create","POST",{});} 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});} @@ -33,7 +35,8 @@ function calcScores(){return api("/lobby/sessions/"+code()+"/questions/"+rq()+"/ function showScoreboard(){return api("/lobby/sessions/"+code()+"/scoreboard","GET",null);} function nextRound(){return api("/lobby/sessions/"+code()+"/rounds/next","POST",{});} function finishGame(){return api("/lobby/sessions/"+code()+"/finish","POST",{});} -document.getElementById("code").addEventListener("input",function(){syncStartRoundGuard(null);}); +["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();});}); syncStartRoundGuard(null); +if(restoreHostContext()){sessionDetail();}else{saveHostContext();} diff --git a/lobby/templates/lobby/player_screen.html b/lobby/templates/lobby/player_screen.html index 7c7d358..a00ae80 100644 --- a/lobby/templates/lobby/player_screen.html +++ b/lobby/templates/lobby/player_screen.html @@ -31,9 +31,13 @@ var availableAnswers=[]; var guessSubmitted=false; var lieSubmitted=false; +var PLAYER_CONTEXT_KEY="wppPlayerContext"; function code(){return document.getElementById("code").value.trim().toUpperCase();} function pid(){return document.getElementById("playerId").value.trim();} function rq(){return document.getElementById("roundQuestionId").value.trim();} +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 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 hasSubmitContext(){return !!(code()&&pid()&&rq()&&document.getElementById("sessionToken").value.trim());} function guessStorageKey(){var c=code();var p=pid();var q=rq();if(!c||!p||!q){return "";}return ["wppGuess",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(":");} @@ -47,13 +51,14 @@ function updateGuessStatus(){var el=document.getElementById("guessStatus");if(!e 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 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 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();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.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;} function joinSession(){return api("/lobby/sessions/join","POST",{code:code(),nickname:document.getElementById("nickname").value.trim()});} 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;});} 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});} -["code","playerId","sessionToken","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){updateLieSubmitState();updateGuessSubmitState();});field.addEventListener("change",function(){updateLieSubmitState();updateGuessSubmitState();});}); +["code","nickname","playerId","sessionToken","roundQuestionId"].forEach(function(fieldId){var field=document.getElementById(fieldId);if(!field){return;}field.addEventListener("input",function(){updateLieSubmitState();updateGuessSubmitState();savePlayerContext();});field.addEventListener("change",function(){updateLieSubmitState();updateGuessSubmitState();savePlayerContext();});}); updateGuessSubmitState(); +if(restorePlayerContext()){sessionDetail();}else{savePlayerContext();} diff --git a/lobby/tests.py b/lobby/tests.py index 682df49..4ad5e37 100644 --- a/lobby/tests.py +++ b/lobby/tests.py @@ -773,6 +773,8 @@ class UiScreenTests(TestCase): response = self.client.get(reverse("lobby:host_screen")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Host panel") + self.assertContains(response, "saveHostContext") + self.assertContains(response, "restoreHostContext") def test_player_screen_is_public(self): response = self.client.get(reverse("lobby:player_screen")) @@ -785,6 +787,8 @@ class UiScreenTests(TestCase): self.assertContains(response, "availableAnswers") self.assertContains(response, "guessStorageKey") self.assertContains(response, "persistGuessState") + self.assertContains(response, "savePlayerContext") + self.assertContains(response, "restorePlayerContext") self.assertContains(response, "id=\"lieSubmitBtn\"") self.assertContains(response, "id=\"lieStatus\"") self.assertContains(response, "persistLieState")