pref-matrix

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

spa.html (13007B)


      1 <!DOCTYPE html>
      2 <html lang="fr">
      3   <head>
      4     <meta charset="utf-8" />
      5     <meta name="viewport" content="width=device-width">
      6     <title>Planification JDR</title>
      7     <link rel="stylesheet" type="text/css" href="style.css" />
      8     <script>
      9 var val_class=["val0","val1","val2","val3","val4","val5"];
     10 var val_text=["?","−−","−","0","+","++"];
     11 var all_data=null;
     12 var radio_map=new Map();
     13 var spinner_map=new Map();
     14 var subject=null;
     15 var sent=null;
     16 var send_subject=null;
     17 var outbox=new Map();
     18 var sender=null;
     19 var base_timeout=2000;
     20 var retry_timeout=base_timeout;
     21 
     22 window.onerror = function(msg, url, line) {
     23   var item = document.createElement("li");
     24   item.appendChild(document.createTextNode(url + "@" + line + ": " + msg));
     25   document.getElementById("error-log").appendChild(item);
     26   document.getElementById("error-div").style = "display:block";
     27   item.scrollIntoView();
     28 }
     29 
     30 function pathname_to_topic(str) {
     31   var components = str.split("/");
     32   var fileName = components[components.length - 1];
     33   var lastDotIndex = fileName.lastIndexOf('.');
     34   var basename = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
     35   return basename;
     36 }
     37 
     38 var topic=pathname_to_topic(window.location.pathname);
     39 
     40 /************************************/
     41 /* Asynchronous Push of Preferences */
     42 /************************************/
     43 
     44 async function send(){
     45   var req_body = "topic=" + encodeURIComponent(topic) + "&sub=" + encodeURIComponent(send_subject);
     46   sent = outbox.get(send_subject);
     47   outbox.delete(send_subject);
     48 
     49   for (const [key, value] of sent) {
     50     req_body += "&" + encodeURIComponent(key) + "=" + encodeURIComponent(value);
     51   }
     52 
     53   const response = await fetch("do/set-pref", {
     54     method: "POST",
     55     cache: "no-store",
     56     headers: { "Content-Type": "application/x-www-form-urlencoded" },
     57     body: req_body,
     58   });
     59 
     60   var to = base_timeout;
     61 
     62   if (response.ok) {
     63     for (const [key, value] of sent) {
     64       if (!outbox.has(send_subject) || !outbox.get(send_subject).has(key)) {
     65         spinner_map.get(key).style = "display:none";
     66       }
     67     }
     68     retry_timeout = base_timeout;
     69 
     70   } else {
     71     if (!outbox.has(send_subject) && sent.size > 0) {
     72       outbox.set(send_subject, new Map());
     73     }
     74 
     75     for (const [key, value] of sent) {
     76       if (!outbox.get(send_subject).has(key)) {
     77         outbox.get(send_subject).set(key, value);
     78       }
     79     }
     80     to = retry_timeout;
     81     retry_timeout *= 2;
     82   }
     83 
     84   sent = null;
     85   if (outbox.has(send_subject)) {
     86     sender = setTimeout(send, to);
     87   } else if (outbox.size > 0) {
     88     send_subject = outbox.keys().next().value;
     89     sender = setTimeout(send, to);
     90   } else {
     91     send_subject = null;
     92   }
     93 
     94   if (!subject) {
     95     reload_data();
     96   }
     97 }
     98 
     99 function radio_click(name, value){
    100   spinner_map.get(name).style = "display:inline";
    101 
    102   if (!outbox.has(subject)) {
    103     outbox.set(subject, new Map());
    104   }
    105   outbox.get(subject).set(name, value);
    106 
    107   if (!sent) {
    108     if (!send_subject) {
    109       send_subject = subject;
    110     }
    111 
    112     if (sender) {
    113       clearTimeout(sender);
    114     }
    115     sender = setTimeout(send, base_timeout);
    116   }
    117 }
    118 
    119 /********************/
    120 /* Table Generation */
    121 /********************/
    122 
    123 function append_cell(line, type, cl, text) {
    124   var elt = document.createElement(type);
    125   elt.className = cl;
    126   elt.appendChild(document.createTextNode(text));
    127   line.appendChild(elt);
    128 }
    129 
    130 function set_overview_table() {
    131   var holder = document.getElementById("overview-table");
    132   while (holder.childNodes.length > 0) {
    133     holder.removeChild(holder.childNodes[0]);
    134   }
    135   var line = document.createElement("tr");
    136 
    137   append_cell(line, "th", "", "Date");
    138   for (const name in all_data[1]) {
    139     var elt = document.createElement("th");
    140     var link = document.createElement("a");
    141     link.href = "#" + encodeURIComponent(name);
    142     link.appendChild(document.createTextNode(name));
    143     elt.appendChild(link);
    144     line.appendChild(elt);
    145   }
    146   holder.appendChild(line);
    147 
    148   for (const name of all_data[0]) {
    149     line = document.createElement("tr");
    150     append_cell(line, "td", "date", name);
    151     for (const [tmp, prefs] of Object.entries(all_data[1])) {
    152       var v = prefs[name] || 0;
    153       append_cell(line, "td", val_class[v], val_text[v]);
    154     }
    155     holder.appendChild(line);
    156   }
    157 }
    158 
    159 function set_pref_table() {
    160   var holder = document.getElementById("pref-table-body");
    161   while (holder.childNodes.length > 0) {
    162     holder.removeChild(holder.childNodes[0]);
    163   }
    164 
    165   radio_map.clear();
    166   spinner_map.clear();
    167 
    168   for (const name of all_data[0]) {
    169     var line = document.createElement("tr");
    170 
    171     var cell = document.createElement("td");
    172     cell.className = "date";
    173     cell.appendChild(document.createTextNode(name));
    174     line.appendChild(cell);
    175 
    176     var radios = new Array;
    177 
    178     for (const i in val_text) {
    179       cell = document.createElement("td");
    180       var elt1 = document.createElement("label");
    181       var elt2 = document.createElement("input");
    182       elt2.type = "radio";
    183       elt2.name = name;
    184       elt2.value = i;
    185       elt2.onchange = function() { radio_click(name, i) };
    186       radios[i] = elt2;
    187       elt1.appendChild(elt2);
    188       elt1.appendChild(document.createTextNode(val_text[i]));
    189       elt1.className = val_class[i];
    190       cell.appendChild(elt1);
    191       if (i > 0) {
    192         line.appendChild(cell);
    193       } else {
    194         line.insertBefore(cell, line.childNodes[0]);
    195       }
    196     }
    197     radio_map.set(name, radios);
    198 
    199     cell = document.createElement("td");
    200     elt1 = document.createElement("img");
    201     elt1.className = "spinner";
    202     elt1.src = "spinner.svg";
    203     elt1.style = "display:none";
    204     spinner_map.set(name, elt1);
    205     cell.appendChild(elt1);
    206     line.appendChild(cell);
    207     holder.appendChild(line);
    208   }
    209 
    210   for (const [name, radios] of radio_map) {
    211     for (const i in val_text) {
    212       radios[i].checked = (i == (all_data[1][subject][name] || 0));
    213     }
    214   }
    215 }
    216 
    217 /*********************/
    218 /* Page Presentation */
    219 /*********************/
    220 
    221 function redisplay() {
    222   var prev_subject = subject;
    223   subject = decodeURIComponent(window.location.hash.substring(1));
    224   document.getElementById("cur-subject").textContent = subject;
    225 
    226   if (!subject) {
    227     document.title = "Planification JDR";
    228 
    229     if (prev_subject) {
    230       reload_data();
    231     }
    232 
    233     set_overview_table();
    234 
    235     document.getElementById("pref-form").style = "display:none";
    236     document.getElementById("change-subject-form").style = "display:none";
    237     document.getElementById("overview-link").style = "display:none";
    238 
    239     document.getElementById("overview-table").style = "";
    240     document.getElementById("subject-name-label").style = "display:inline";
    241     document.getElementById("create-subject").style = "";
    242     document.getElementById("create-subject-name").type = "text";
    243     document.getElementById("create-subject-name").value = "";
    244 
    245     open_change_form();
    246 
    247   } else if (subject in all_data[1]) {
    248     set_pref_table();
    249 
    250     document.title = "Planification JDR - " + subject;
    251 
    252     document.getElementById("create-subject").style = "display:none";
    253     document.getElementById("overview-table").style = "display:none";
    254 
    255     document.getElementById("pref-form").style = "display:block";
    256     document.getElementById("overview-link").style = "display:inline";
    257     close_change_form();
    258 
    259   } else {
    260     document.title = "Planification JDR - " + subject;
    261 
    262     document.getElementById("pref-form").style = "display:none";
    263     document.getElementById("overview-table").style = "display:none";
    264 
    265     document.getElementById("create-subject").style = "display:block";
    266     document.getElementById("subject-name-label").style = "display:none";
    267     document.getElementById("create-subject-name").value = subject;
    268 
    269     document.getElementById("overview-link").style = "display:inline";
    270     close_change_form();
    271   }
    272 }
    273 
    274 async function reload_data(){
    275   document.getElementById("reload-spinner").style = "display:inline";
    276   const response = await fetch(topic + ".json", { cache: "no-cache" });
    277   all_data = await response.json();
    278 
    279   redisplay();
    280 
    281   document.getElementById("reload-spinner").style = "display:none";
    282 }
    283 
    284 async function create_subject(){
    285   const name = document.getElementById("create-subject-name").value;
    286   document.getElementById("create-spinner").style = "display:inline";
    287   const response = await fetch("do/new-subject", {
    288     method: "POST",
    289     cache: "no-store",
    290     headers: { "Content-Type": "application/x-www-form-urlencoded" },
    291     body: "topic=" + encodeURIComponent(topic) + "&name=" + encodeURIComponent(name),
    292   }) ;
    293   document.getElementById("create-subject-name").value = "";
    294   document.getElementById("create-spinner").style = "display:none";
    295 
    296   if (response.ok) {
    297     if (subject !== name) {
    298       window.location.hash = "#" + encodeURIComponent(name);
    299     }
    300     reload_data();
    301   }
    302 }
    303 
    304 function set_subject(){
    305   window.location.hash = "#" + encodeURIComponent(document.getElementById("new-subject").value);
    306   return false;
    307 }
    308 
    309 function close_change_form(){
    310   document.getElementById("change-subject-form").style = "display:block";
    311   document.getElementById("close-subject").style = "display:none";
    312   document.getElementById("cur-subject").style = "display:inline";
    313   document.getElementById("new-subject").style = "display:none";
    314   document.getElementById("open-subject").style = "display:inline";
    315   document.getElementById("set-subject").style = "display:none";
    316 }
    317 
    318 function open_change_form(){
    319   document.getElementById("new-subject").value = "";
    320   document.getElementById("change-subject-form").style = "display:block";
    321   document.getElementById("close-subject").style = subject && "display:inline" || "display:none";
    322   document.getElementById("cur-subject").style = "display:none";
    323   document.getElementById("new-subject").style = "display:inline";
    324   document.getElementById("open-subject").style = "display:none";
    325   document.getElementById("set-subject").style = "display:inline";
    326 }
    327 
    328 window.addEventListener("hashchange", redisplay);
    329     </script>
    330   </head>
    331   <body onload="reload_data()">
    332     <h1>Planification JDR</h1>
    333     <p>
    334       <a href="#" id="overview-link">Retour à la vue d'ensemble</a>
    335       <input name="test" value="Recharger" type="button" onclick="reload_data()">
    336       <img id="reload-spinner" class="spinner" src="spinner.svg" style="display: none">
    337     </p>
    338     <table style="margin: 1em">
    339       <tr><td colspan="2" style="text-align: left"><strong>Symboles des préférences :</strong></td></tr>
    340       <tr>
    341         <td class="val5">++</td>
    342         <td style="text-align: left">j'ai très envie de jouer ce soir-là, je vais m'ennuyer autrement</td>
    343       </tr>
    344       <tr>
    345         <td class="val4">+</td>
    346         <td style="text-align: left">j'aimerais bien jouer ce soir-là</td>
    347       </tr>
    348       <tr>
    349         <td class="val3">0</td>
    350         <td style="text-align: left">je veux bien jouer mais ça ne me dérange pas de ne pas jouer</td>
    351       </tr>
    352       <tr>
    353         <td class="val2">−</td>
    354         <td style="text-align: left">je peux jouer ce soir-là mais ça ne m'arrange pas vraiment</td>
    355       </tr>
    356       <tr>
    357         <td class="val1">−−</td>
    358         <td style="text-align: left">je ne suis pas du tout disponible ce soir-là</td>
    359       </tr>
    360       <tr>
    361         <td class="val0">?</td>
    362         <td style="text-align: left">je ne veux pas me prononcer</td>
    363       </tr>
    364     </table>
    365     <form id="change-subject-form" style="display:none" onsubmit="return set_subject()">
    366       <p>
    367         <label>
    368           Préferences pour <strong id="cur-subject"></strong><input name="subject" id="new-subject" value="" type="text">
    369         </label>
    370         <input id="open-subject" name="change" value="Changer" type="button" onclick="open_change_form()">
    371         <input id="set-subject" name="submit" value="Valider" type="submit">
    372         <input id="close-subject" name="cancel" value="Annuler" type="button" onclick="close_change_form()">
    373       </p>
    374     </form>
    375     <form id="create-subject" style="display:none" onsubmit="create_subject(); return false;">
    376       <label id="subject-name-label">Nouveau pseudo :
    377         <input id="create-subject-name" type="hidden" value="">
    378       </label>
    379       <input name="submit" type="submit" value="Créer">
    380       <img id="create-spinner" class="spinner" src="spinner.svg" style="display: none">
    381     </form>
    382     <table id="overview-table">
    383       <tr><th>Chargement</th></tr>
    384       <tr><td><img src="spinner.svg" style="display: inline; width: 5em; height: 5em"></td></tr>
    385     </table>
    386     <form id="pref-form" style="display:none">
    387       <table>
    388         <thead>
    389           <tr id="table-header">
    390             <th></th>
    391             <th>Date</th>
    392             <th colspan="5">Préférence</th>
    393             <th></th>
    394           </tr>
    395         </thead>
    396         <tbody id="pref-table-body">
    397           <tr>
    398             <td colspan="8">
    399               <img src="spinner.svg" with="5em" height"5em">
    400             </td>
    401           </tr>
    402         </tbody>
    403       </table>
    404     </form>
    405     <div id="error-div" style="display:none">
    406       <p>Error log:</p>
    407       <ul id="error-log"><ul>
    408     </div>
    409   </body>
    410 </html>