406 lines
16 KiB
HTML
406 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<header>
|
|
<div class="phase"><span class="status-phase">{{ status.phase }}</span> phase of turn #<span class="status-turn">{{ status.turn }}</span></div>
|
|
<div class="fear_summary">
|
|
<span class="available_fear_cards">{{ status.available_fear_cards }}</span>
|
|
😱🎴;
|
|
<span class="fear_to_next_card">{{ status.fear_to_next_card }}</span>
|
|
😱 to next 😱🎴;
|
|
<span class="fear_this_phase">{{ status.fear_this_phase }}</span>
|
|
😱 this phase
|
|
</div>
|
|
<div class="access-code">
|
|
<span class="header">🔐</span> <a href="{% url 'qr_code' access_code=access_code %}" target="_blank">{{ access_code }}</a>
|
|
</div>
|
|
</header>
|
|
<div class="errors">
|
|
{% for error in errors %}
|
|
<p class="error">
|
|
{{ error }}
|
|
</p>
|
|
{% endfor %}
|
|
</div>
|
|
<form action="#" method="POST">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="game_turn" value="{{ status.turn }}">
|
|
<input type="hidden" name="game_phase" value="{{ status.phase_id }}">
|
|
<input type="hidden" name="phase_name" value="{{ status.phase }} phase of turn #{{ status.turn }}">
|
|
<div class="players">
|
|
{% for order, player in players.items %}
|
|
<div class="player player-{{ player.order }}">
|
|
<label for="player-{{ player.order }}-visible">
|
|
<div class="player-summary">
|
|
<span class="player-{{ player.order }}-ready">{% if player.ready %}🏁{% else %}⏳{% endif %}</span>
|
|
(<span class="player-total-fear player-{{ player.order }}-total-fear">{{ player.total_fear }}</span> 😱)
|
|
{{ player.name }} ({{ player.get_spirit_name }})
|
|
</div>
|
|
</label>
|
|
<input type="checkbox"
|
|
class="player-visible"
|
|
name="player-{{ player.order }}-visible"
|
|
id="player-{{ player.order }}-visible"
|
|
{% if player.visible %} checked{% endif %}/>
|
|
<div class="player-body">
|
|
<label>
|
|
<input type="hidden"
|
|
name="player-{{ player.order }}-ready-orig"
|
|
id="player-{{ player.order }}-ready-orig"
|
|
value="{{ player.ready|lower }}" />
|
|
<input type="hidden"
|
|
name="player-{{ player.order }}-ready"
|
|
value="false" />
|
|
<input type="checkbox"
|
|
name="player-{{ player.order }}-ready"
|
|
id="player-{{ player.order }}-ready"
|
|
value="true"
|
|
{% if player.ready %} checked{% endif %}/>Done with
|
|
<span class="phase"><span class="status-phase">{{ status.phase }}</span> phase of turn #<span class="status-turn">{{ status.turn }}</span></span>
|
|
</label>
|
|
<div class="player-effects" id="player-{{ player.order }}-effects">
|
|
{% for effect_num, effect in player.fear.items %}
|
|
<div class="effect effect-{{ effect_num }}">
|
|
#{{ effect_num|add:1 }}
|
|
|
|
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-fear-orig" value="{{ effect.pure_fear }}">
|
|
<select name="player-{{ player.order }}-effect-{{ effect_num }}-fear"{% if player.ready %} disabled{% endif %}>
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == effect.pure_fear %} selected="selected"{% endif %}>{{ val }}😱</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-towns-orig" value="{{ effect.towns }}">
|
|
<select name="player-{{ player.order }}-effect-{{ effect_num }}-towns"{% if player.ready %} disabled{% endif %}>
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == effect.towns %} selected="selected"{% endif %}>{{ val }}🏠</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<input type="hidden" name="player-{{ player.order }}-effect-{{ effect_num }}-cities-orig" value="{{ effect.cities }}">
|
|
<select name="player-{{ player.order }}-effect-{{ effect_num }}-cities"{% if player.ready %} disabled{% endif %}>
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == effect.cities %} selected="selected"{% endif %}>{{ val }}🏙️</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<input type="submit" name="update" class="update-button" value="Update fear">
|
|
<input type="submit" name="advance" class="advance-button" value="Advance to next phase"{% if not status.all_ready %} disabled{% endif %}>
|
|
<input type="submit" name="revert" class="revert-button" value="Revert to previous phase">
|
|
</form>
|
|
{% block game_refresh %}
|
|
{% if not results_only %}
|
|
<div class="button-container">
|
|
<a href="{% url 'game' access_code=access_code %}" class="button" id="button-refresh">Refresh</a>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
{% endblock %}
|
|
{% block header_script %}
|
|
{% if not results_only %}
|
|
<template id="effect-template">
|
|
<div class="effect">
|
|
#<span class="effect-num">?</span>
|
|
|
|
<input type="hidden" class="fear-orig" value="0">
|
|
<select class="fear">
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }}😱</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<input type="hidden" class="towns-orig" value="0">
|
|
<select class="towns">
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }}🏠</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<input type="hidden" class="cities-orig" value="0">
|
|
<select class="cities">
|
|
{% for val in range %}
|
|
<option value="{{ val }}"{% if val == 0 %} selected="selected"{% endif %}>{{ val }}🏙️</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
var statusObj = JSON.parse("{{ status_json|escapejs }}");
|
|
function ensureEffectRowExists(playerOrd, effectNum) {
|
|
if(document.querySelector(".player-" + playerOrd + " .effect-" + effectNum)) {
|
|
return;
|
|
}
|
|
if(effectNum > 0) {
|
|
ensureEffectRowExists(playerOrd, effectNum-1);
|
|
}
|
|
|
|
let effDiv = document.getElementById("player-" + playerOrd + "-effects");
|
|
let effectTemplate = document.querySelector("#effect-template");
|
|
let clone = effectTemplate.content.cloneNode(true);
|
|
clone.querySelector("div.effect").classList.add("effect-" + effectNum);
|
|
clone.querySelector(".effect-num").innerText = effectNum + 1;
|
|
let prefix = "player-" + playerOrd + "-effect-" + effectNum + "-";
|
|
["fear", "towns", "cities"].forEach(suffix => {
|
|
clone.querySelector("." + suffix).name = prefix + suffix;
|
|
clone.querySelector("." + suffix + "-orig").name = prefix + suffix + "-orig";
|
|
});
|
|
for(let el of clone.querySelectorAll('select')) {
|
|
el.addEventListener("change", formElementChanged);
|
|
}
|
|
effDiv.appendChild(clone);
|
|
}
|
|
function addEmptyEffects(clearAll) {
|
|
for(let effDiv of document.getElementsByClassName("player-effects")) {
|
|
if(clearAll) {
|
|
while(effDiv.lastElementChild) {
|
|
effDiv.removeChild(effDiv.lastElementChild);
|
|
}
|
|
}
|
|
|
|
var newEffectNum = null;
|
|
if(effDiv.childElementCount == 0) {
|
|
newEffectNum = 0;
|
|
} else {
|
|
let lastEffectDiv = effDiv.children[effDiv.children.length-1];
|
|
var lastEffectNum = null;
|
|
lastEffectDiv.classList.forEach(cls => {
|
|
if(cls.startsWith("effect-")) {
|
|
lastEffectNum = parseInt(cls.substring(7));
|
|
}
|
|
});
|
|
lastEffectDiv.querySelectorAll("select").forEach(child => {
|
|
if(child.value != "0") {
|
|
newEffectNum = lastEffectNum + 1;
|
|
}
|
|
});
|
|
}
|
|
if(newEffectNum != null) {
|
|
let playerOrd = effDiv.id.split("-")[1];
|
|
ensureEffectRowExists(playerOrd, newEffectNum);
|
|
}
|
|
}
|
|
}
|
|
function handleNewStatus(oldStatus, newStatus) {
|
|
{% block game_handle_new_status %}
|
|
if(oldStatus.hash == newStatus.hash) return true;
|
|
if(oldStatus.phase_id != newStatus.phase_id
|
|
|| oldStatus.turn != newStatus.turn) {
|
|
addEmptyEffects(true);
|
|
}
|
|
|
|
let form = document.forms[0];
|
|
for(let key in newStatus) {
|
|
if(key == "hash" || key == "phase_id" || key == "total_fear") {
|
|
// ignored
|
|
} else if(key == "all_ready") {
|
|
let all_ready = newStatus[key]
|
|
for(let el of document.getElementsByClassName("advance-button")) {
|
|
el.disabled = !all_ready;
|
|
}
|
|
} else if(key == "players") {
|
|
let players = newStatus[key];
|
|
for(let ord in players) {
|
|
let prefix = "player-" + ord + "-";
|
|
let player = players[ord];
|
|
for(let pkey in player) {
|
|
if(pkey == "ready") {
|
|
let ready = player[pkey];
|
|
for(let el of document.getElementsByClassName(prefix + "ready")) {
|
|
el.innerText = ready ? "🏁" : "⏳";
|
|
}
|
|
document.getElementById(prefix + "ready-orig").value = ready;
|
|
document.getElementById(prefix + "ready").checked = ready;
|
|
|
|
for(let el of document.querySelectorAll(".player-" + ord + " select")) {
|
|
el.disabled = ready;
|
|
}
|
|
} else if(pkey == "total_fear") {
|
|
let total_fear = player[pkey];
|
|
for(let el of document.getElementsByClassName(prefix + "total-fear")) {
|
|
el.innerText = total_fear;
|
|
}
|
|
} else if(pkey == "fear") {
|
|
let fear = player[pkey];
|
|
for(let effect_num in fear) {
|
|
ensureEffectRowExists(ord, effect_num);
|
|
let fprefix = prefix + "effect-" + effect_num + "-";
|
|
let effect = fear[effect_num];
|
|
for(let ekey in effect) {
|
|
let value = effect[ekey];
|
|
let eclass = ekey == "pure_fear" ? "fear" : ekey;
|
|
let eprefix = fprefix + eclass;
|
|
form.elements[eprefix].value = value;
|
|
form.elements[eprefix + "-orig"].value = value;
|
|
}
|
|
}
|
|
} else {
|
|
alert("Unknown player status key: " + pkey + "=" + player[pkey]);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
addEmptyEffects(false);
|
|
} else {
|
|
var matching_elements = document.getElementsByClassName("status-"
|
|
+ key);
|
|
if(matching_elements.length == 0) {
|
|
matching_elements = document.getElementsByClassName(key);
|
|
}
|
|
if(matching_elements.length == 0) {
|
|
alert("Unknown status key: " + key + "=" + newStatus[key]);
|
|
return false;
|
|
}
|
|
|
|
let value = newStatus[key];
|
|
for(let el of matching_elements) {
|
|
el.innerText = value;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
{% endblock %}
|
|
}
|
|
var activeRequests = new Set();
|
|
function formElementChanged(e) {
|
|
let form = document.forms[0];
|
|
let el = e.target;
|
|
let name = el.name;
|
|
let origName = name + "-orig";
|
|
let origEl = form.elements[origName];
|
|
|
|
let newValue = el.type == "checkbox" ? el.checked : el.value;
|
|
let oldValue = origEl.value;
|
|
|
|
el.disabled = true;
|
|
if(el.name.endsWith("ready") && newValue) {
|
|
let ord = name.split("-")[1];
|
|
// If marking a player ready, disable all selects immediately.
|
|
for(let el of document.querySelectorAll(".player-" + ord + " select")) {
|
|
el.disabled = true;
|
|
}
|
|
}
|
|
|
|
activeRequests.add(el);
|
|
statusObj.hash = '';
|
|
|
|
// From https://stackoverflow.com/a/5588435
|
|
function getCookie(name) {
|
|
var cookieValue = null;
|
|
if (document.cookie && document.cookie != '') {
|
|
var cookies = document.cookie.split(';');
|
|
for (var i = 0; i < cookies.length; i++) {
|
|
var cookie = cookies[i];
|
|
// Does this cookie string begin with the name we want?
|
|
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
fetch("{% url 'update_game' access_code=access_code %}",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"X-CSRFToken": getCookie("csrftoken"),
|
|
},
|
|
body: name + "=" + newValue + "&" + origName + "=" + oldValue
|
|
+ "&game_turn=" + statusObj["turn"]
|
|
+ "&game_phase=" + statusObj["phase_id"]
|
|
+ "&phase_name=" + statusObj["phase"]
|
|
+ "&csrfmiddlewaretoken=" + form.elements["csrfmiddlewaretoken"].value,
|
|
}).then(response => response.json()
|
|
).then(data => {
|
|
if("success" in data && data["success"]) {
|
|
form.elements[origName].value = newValue;
|
|
} else {
|
|
var updatedValue = oldValue;
|
|
if("value" in data) {
|
|
let updatedValue = data['value'];
|
|
origEl.value = updatedValue;
|
|
}
|
|
if(el.type == "checkbox") {
|
|
el.checked = updatedValue == "true";
|
|
} else {
|
|
el.value = updatedValue;
|
|
}
|
|
|
|
if("errors" in data) {
|
|
alert(data["errors"]);
|
|
} else {
|
|
alert("Unknown error: " + data);
|
|
}
|
|
}
|
|
el.disabled = false;
|
|
activeRequests.delete(el);
|
|
});
|
|
}
|
|
window.addEventListener("DOMContentLoaded", e => {
|
|
for(let updateButton of document.getElementsByClassName("update-button")) {
|
|
updateButton.disabled = true;
|
|
updateButton.style.display = "none";
|
|
}
|
|
let form = document.forms[0];
|
|
for(let el of form.elements) {
|
|
if(el.type == 'checkbox' && !el.name.endsWith("visible")) {
|
|
el.addEventListener("change", formElementChanged);
|
|
}
|
|
}
|
|
|
|
for(let el of document.querySelectorAll('select')) {
|
|
el.addEventListener("change", formElementChanged);
|
|
}
|
|
|
|
function checkStatus() {
|
|
if(activeRequests.size != 0) return;
|
|
// From https://stackoverflow.com/a/50101022
|
|
|
|
const abort = new AbortController();
|
|
const signal = abort.signal;
|
|
|
|
// 50 second timeout:
|
|
const timeoutId = setTimeout(() => abort.abort(), 50000);
|
|
|
|
fetch(new Request("{% url 'status' access_code=access_code %}"
|
|
+ (statusObj.hash != ""
|
|
? statusObj.hash + "/"
|
|
: "")),
|
|
{signal})
|
|
.then(response => {
|
|
clearTimeout(timeoutId);
|
|
if(response.status === 304) {
|
|
// TODO Just skip the next step?
|
|
return statusObj;
|
|
} else {
|
|
return response.json();
|
|
}
|
|
})
|
|
.then(data => {
|
|
if(activeRequests.size != 0) return;
|
|
if(!handleNewStatus(statusObj, data)) {
|
|
document.getElementById("button-refresh").click();
|
|
} else {
|
|
statusObj = data;
|
|
}
|
|
})
|
|
.then(checkStatus)
|
|
.catch(() => {
|
|
// If something went wrong, wait a few seconds before retrying.
|
|
setTimeout(checkStatus, 5000);
|
|
});
|
|
}
|
|
checkStatus();
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|