fear_tracker/fear_tracker/templates/game.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 %}