pref-matrix

Web interface to coordinate preferences
git clone https://git.instinctive.eu/pref-matrix.git
Log | Files | Refs | README | LICENSE

commit 730de26cf29ae7fc7f50e70ad0672234517b88ba
parent f82ded7dd07fddbae0b1f091e5d8fec3f2b89ac7
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Sun, 14 Jan 2024 19:38:37 +0000

Single page application example
Diffstat:
Asample-assets/spa.html | 401+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 401 insertions(+), 0 deletions(-)

diff --git a/sample-assets/spa.html b/sample-assets/spa.html @@ -0,0 +1,401 @@ +<!DOCTYPE html> +<html lang="fr"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width"> + <title>Planification JDR</title> + <link rel="stylesheet" type="text/css" href="style.css" /> + <script> +var val_class=["val0","val1","val2","val3","val4","val5"]; +var val_text=["?","−−","−","0","+","++"]; +var all_data=null; +var radio_map=new Map(); +var spinner_map=new Map(); +var subject=null; +var sent=null; +var send_subject=null; +var outbox=new Map(); +var sender=null; +var base_timeout=2000; +var retry_timeout=base_timeout; + +window.onerror = function(msg, url, line) { + var item = document.createElement("li"); + item.appendChild(document.createTextNode(url + "@" + line + ": " + msg)); + document.getElementById("error-log").appendChild(item); + document.getElementById("error-div").style = "display:block"; + item.scrollIntoView(); +} + +/************************************/ +/* Asynchronous Push of Preferences */ +/************************************/ + +async function send(){ + var req_body = "sub=" + encodeURIComponent(send_subject); + sent = outbox.get(send_subject); + outbox.delete(send_subject); + + for (const [key, value] of sent) { + req_body += "&" + encodeURIComponent(key) + "=" + encodeURIComponent(value); + } + + console.log("Start fetch"); + const response = await fetch("do/set-pref", { + method: "POST", + cache: "no-store", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: req_body, + }); + console.log("End of fetch"); + + var to = base_timeout; + + if (response.ok) { + for (const [key, value] of sent) { + if (!outbox.has(send_subject) || !outbox.get(send_subject).has(key)) { + spinner_map.get(key).style = "display:none"; + } + } + retry_timeout = base_timeout; + + } else { + if (!outbox.has(send_subject) && sent.size > 0) { + outbox.set(send_subject, new Map()); + } + + for (const [key, value] of sent) { + if (!outbox.get(send_subject).has(key)) { + outbox.get(send_subject).set(key, value); + } + } + to = retry_timeout; + retry_timeout *= 2; + } + + sent = null; + if (outbox.has(send_subject)) { + sender = setTimeout(send, to); + } else if (outbox.size > 0) { + send_subject = outbox.keys().next().value; + sender = setTimeout(send, to); + } else { + send_subject = null; + } + + if (!subject) { + reload_data(); + } +} + +function radio_click(name, value){ + spinner_map.get(name).style = "display:inline"; + + if (!outbox.has(subject)) { + outbox.set(subject, new Map()); + } + outbox.get(subject).set(name, value); + + if (!sent) { + if (!send_subject) { + send_subject = subject; + } + + if (sender) { + clearTimeout(sender); + } + sender = setTimeout(send, base_timeout); + } +} + +/********************/ +/* Table Generation */ +/********************/ + +function append_cell(line, type, cl, text) { + var elt = document.createElement(type); + elt.className = cl; + elt.appendChild(document.createTextNode(text)); + line.appendChild(elt); +} + +function set_overview_table() { + var holder = document.getElementById("overview-table"); + while (holder.childNodes.length > 0) { + holder.removeChild(holder.childNodes[0]); + } + var line = document.createElement("tr"); + + append_cell(line, "th", "", "Date"); + for (const name in all_data[1]) { + var elt = document.createElement("th"); + var link = document.createElement("a"); + link.href = "#" + encodeURIComponent(name); + link.appendChild(document.createTextNode(name)); + elt.appendChild(link); + line.appendChild(elt); + } + holder.appendChild(line); + + for (const name of all_data[0]) { + line = document.createElement("tr"); + append_cell(line, "td", "date", name); + for (const [tmp, prefs] of Object.entries(all_data[1])) { + var v = prefs[name] || 0; + append_cell(line, "td", val_class[v], val_text[v]); + } + holder.appendChild(line); + } +} + +function set_pref_table() { + var holder = document.getElementById("pref-table-body"); + while (holder.childNodes.length > 0) { + holder.removeChild(holder.childNodes[0]); + } + + radio_map.clear(); + spinner_map.clear(); + + for (const name of all_data[0]) { + var line = document.createElement("tr"); + + var cell = document.createElement("td"); + cell.className = "date"; + cell.appendChild(document.createTextNode(name)); + line.appendChild(cell); + + var radios = new Array; + + for (const i in val_text) { + cell = document.createElement("td"); + var elt1 = document.createElement("label"); + var elt2 = document.createElement("input"); + elt2.type = "radio"; + elt2.name = name; + elt2.value = i; + elt2.onchange = function() { radio_click(name, i) }; + radios[i] = elt2; + elt1.appendChild(elt2); + elt1.appendChild(document.createTextNode(val_text[i])); + elt1.className = val_class[i]; + cell.appendChild(elt1); + if (i > 0) { + line.appendChild(cell); + } else { + line.insertBefore(cell, line.childNodes[0]); + } + } + radio_map.set(name, radios); + + cell = document.createElement("td"); + elt1 = document.createElement("img"); + elt1.className = "spinner"; + elt1.src = "spinner.svg"; + elt1.style = "display:none"; + spinner_map.set(name, elt1); + cell.appendChild(elt1); + line.appendChild(cell); + holder.appendChild(line); + } + + for (const [name, radios] of radio_map) { + for (const i in val_text) { + radios[i].checked = (i == (all_data[1][subject][name] || 0)); + } + } +} + +/*********************/ +/* Page Presentation */ +/*********************/ + +function redisplay() { + var prev_subject = subject; + subject = decodeURIComponent(window.location.hash.substring(1)); + document.getElementById("cur-subject").textContent = subject; + + if (!subject) { + document.title = "Planification JDR"; + + if (prev_subject) { + reload_data(); + } + + set_overview_table(); + + document.getElementById("pref-form").style = "display:none"; + document.getElementById("change-subject-form").style = "display:none"; + document.getElementById("overview-link").style = "display:none"; + + document.getElementById("overview-table").style = ""; + document.getElementById("subject-name-label").style = "display:inline"; + document.getElementById("create-subject-name").type = "text"; + document.getElementById("create-subject-name").value = ""; + + open_change_form(); + + } else if (subject in all_data[1]) { + set_pref_table(); + + document.title = "Planification JDR - " + subject; + + document.getElementById("create-subject").style = "display:none"; + document.getElementById("overview-table").style = "display:none"; + + document.getElementById("pref-form").style = "display:block"; + document.getElementById("overview-link").style = "display:inline"; + close_change_form(); + + } else { + document.title = "Planification JDR - " + subject; + + document.getElementById("pref-form").style = "display:none"; + document.getElementById("overview-table").style = "display:none"; + + document.getElementById("create-subject").style = "display:block"; + document.getElementById("subject-name-label").style = "display:none"; + document.getElementById("create-subject-name").value = subject; + + document.getElementById("overview-link").style = "display:inline"; + close_change_form(); + } +} + +async function reload_data(){ + document.getElementById("reload-spinner").style = "display:inline"; + const response = await fetch("all.json", { cache: "no-cache" }); + all_data = await response.json(); + + redisplay(); + + document.getElementById("reload-spinner").style = "display:none"; +} + +async function create_subject(){ + const name = document.getElementById("create-subject-name").value; + document.getElementById("create-spinner").style = "display:inline"; + const response = await fetch("do/new-subject", { + method: "POST", + cache: "no-store", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: "name=" + encodeURIComponent(name), + }); + document.getElementById("create-subject-name").value = ""; + document.getElementById("create-spinner").style = "display:none"; + + if (response.ok) { + if (subject !== name) { + window.location.hash = "#" + encodeURIComponent(name); + } + reload_data(); + } +} + +function set_subject(){ + window.location.hash = "#" + encodeURIComponent(document.getElementById("new-subject").value); + return false; +} + +function close_change_form(){ + document.getElementById("change-subject-form").style = "display:block"; + document.getElementById("close-subject").style = "display:none"; + document.getElementById("cur-subject").style = "display:inline"; + document.getElementById("new-subject").style = "display:none"; + document.getElementById("open-subject").style = "display:inline"; + document.getElementById("set-subject").style = "display:none"; +} + +function open_change_form(){ + document.getElementById("new-subject").value = ""; + document.getElementById("change-subject-form").style = "display:block"; + document.getElementById("close-subject").style = "display:inline"; + document.getElementById("cur-subject").style = "display:none"; + document.getElementById("new-subject").style = "display:inline"; + document.getElementById("open-subject").style = "display:none"; + document.getElementById("set-subject").style = "display:inline"; +} + +window.addEventListener("hashchange", redisplay); + </script> + </head> + <body onload="reload_data()"> + <h1>Planification JDR</h1> + <p> + <a href="#" id="overview-link">Retour à la vue d'ensemble</a> + <input name="test" value="Recharger" type="button" onclick="reload_data()"> + <img id="reload-spinner" class="spinner" src="spinner.svg" style="display: none"> + </p> + <table style="margin: 1em"> + <tr><td colspan="2" style="text-align: left"><strong>Symboles des préférences :</strong></td></tr> + <tr> + <td class="val5">++</td> + <td style="text-align: left">j'ai très envie de jouer ce soir-là, je vais m'ennuyer autrement</td> + </tr> + <tr> + <td class="val4">+</td> + <td style="text-align: left">j'aimerais bien jouer ce soir-là</td> + </tr> + <tr> + <td class="val3">0</td> + <td style="text-align: left">je veux bien jouer mais ça ne me dérange pas de ne pas jouer</td> + </tr> + <tr> + <td class="val2">−</td> + <td style="text-align: left">je peux jouer ce soir-là mais ça ne m'arrange pas vraiment</td> + </tr> + <tr> + <td class="val1">−−</td> + <td style="text-align: left">je ne suis pas du tout disponible ce soir-là</td> + </tr> + <tr> + <td class="val0">?</td> + <td style="text-align: left">je ne veux pas me prononcer</td> + </tr> + </table> + <form id="change-subject-form" style="display:none" onsubmit="return set_subject()"> + <p> + <label> + Préferences pour <strong id="cur-subject"></strong><input name="subject" id="new-subject" value="" type="text"> + </label> + <input id="open-subject" name="change" value="Changer" type="button" onclick="open_change_form()"> + <input id="set-subject" name="submit" value="Valider" type="submit"> + <input id="close-subject" name="cancel" value="Annuler" type="button" onclick="close_change_form()"> + </p> + </form> + <form id="create-subject" style="display:none" onsubmit="create_subject(); return false;"> + <label id="subject-name-label">Nouveau pseudo : + <input id="create-subject-name" type="hidden" value=""> + </label> + <input name="submit" type="submit" value="Créer"> + <img id="create-spinner" class="spinner" src="spinner.svg" style="display: none"> + </form> + <table id="overview-table"> + <tr><th>Chargement</th></tr> + <tr><td><img src="spinner.svg" style="display: inline; width: 5em; height: 5em"></td></tr> + </table> + <form id="pref-form" style="display:none"> + <table> + <thead> + <tr id="table-header"> + <th></th> + <th>Date</th> + <th colspan="5">Préférence</th> + <th></th> + </tr> + </thead> + <tbody id="pref-table-body"> + <tr> + <td colspan="8"> + <img src="spinner.svg" with="5em" height"5em"> + </td> + </tr> + </tbody> + </table> + </form> + <div id="error-div" style="display:none"> + <p>Error log:</p> + <ul id="error-log"><ul> + </div> + </body> +</html>