Linting and refactor
authorRaphaël Jakse <raphael.trivabble@jakse.fr>
Sat, 16 May 2020 08:02:18 +0000 (10:02 +0200)
committerRaphaël Jakse <raphael.trivabble@jakse.fr>
Sat, 16 May 2020 08:59:25 +0000 (10:59 +0200)
.eslintrc.json
Makefile
l10n/makejs.js
public/alert.js
public/index.html
public/l10n.js
public/touch2click.js
public/trivabble.js
server/trivabble-server.js

index efff6bd7f9072c99ab813be1fb56da7e1a3ea7e6..7950b8d52d9376cf121a206e20169ee2728a800e 100644 (file)
         "sort-keys": "off",
         "sort-vars": "off",
         "space-before-blocks": "error",
-        "space-before-function-paren": "off",
+        "space-before-function-paren": ["error", {
+            "anonymous": "always",
+            "named": "never",
+            "asyncArrow": "always"
+        }],
         "space-in-parens": [
             "error",
             "never"
         ],
-        "space-infix-ops": "off",
+        "space-infix-ops": ["error", { "int32Hint": false }],
         "space-unary-ops": "error",
         "spaced-comment": "off",
         "strict": [
index cf4b24716219e408dac7d2728ddbb0a5f295d124..f2b9c4df09f43b32bfcbdea2645156c54a4cd2ac 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,10 @@
 # -*- Makefile -*-
 
-PORT = 3000
+ESLINT?=eslint
+
+ifeq (, $(shell which $(firstword ${ESLINT})))
+       ESLINT?=npx eslint
+endif
 
 help:
        @echo make lang: build translation files
@@ -11,11 +15,7 @@ lang:
        cd l10n; make
 
 eslint:
-       -npx eslint server/trivabble-server.js
-       -npx eslint public/alert.js
-       -npx eslint public/config.js
-       -npx eslint public/l10n.js
-       -npx eslint public/trivabble.js
+       -${ESLINT} **/*.js
 
 start-dev-server:
        cd server && make start-dev-server
index bef4edd5ad60688f984f07bbf1a0d1604e343cac..e278d27ba2ed4a9cf44f0c17cac7c56c5be2afb1 100755 (executable)
 #!/usr/bin/env node
+/*eslint strict: [2, "global"]*/
+/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/
 
-var ROOT = "../public/l10n/";
+"use strict";
 
-/* Builds translation files.*/
+const ROOT = "../public/l10n/";
 
-var fs = require('fs');
+/*  Builds translation files. */
 
-var langs = fs.readdirSync("po");
-var po, i, len;
+const fs = require("fs");
+
+let po;
+let i;
+let len;
 
 function skipLine() {
-   while (i < len && po[i] !== '\n') {
-      ++i;
-   }
-   ++i;
+    while (i < len && po[i] !== "\n") {
+        ++i;
+    }
+    ++i;
 }
 
 function skipSpaces() {
-   while (i < len && !po[i].trim()) {
-      ++i;
-   }
+    while (i < len && !po[i].trim()) {
+        ++i;
+    }
 }
 
 function parseString() {
-   skipSpaces();
-   if (po[i] !== '"') {
-      return "";
-   }
-   ++i;
-   var deb = i, end;
-   while (i < len) {
-      if (po[i] === "\\") {
-         ++i;
-      } else if (po[i] === '"') {
-         var str1 = po.substring(deb, i++);
-         var end = i;
-         skipSpaces();
-         var ndeb = i;
-         var str2 = parseString();
-         if (i === ndeb) { // we did not parse anything
-            i = end;
-            return str1;
-         }
-
-         return str1 + str2;
-      }
-      ++i;
-   }
-   throw new Error("not ended string at character " + deb);
-}
+    skipSpaces();
+    if (po[i] !== '"') {
+        return "";
+    }
+    ++i;
+    const deb = i;
+
+    while (i < len) {
+        if (po[i] === "\\") {
+            ++i;
+        } else if (po[i] === '"') {
+            const str1 = po.substring(deb, i++);
+            const end = i;
+            skipSpaces();
+            const ndeb = i;
+            const str2 = parseString();
 
-for (var l in langs) {
-   var lang = langs[l];
-   var jsFile = fs.openSync(ROOT + "js/" + lang + ".js", "w");
-   fs.writeSync(jsFile, "(function(){var ");
-   var poFiles = fs.readdirSync("po/" + lang);
-   for (var p in poFiles) {
-      var poFile = poFiles[p];
+            if (i === ndeb) { // we did not parse anything
+                i = end;
+                return str1;
+            }
 
-      var translationFunction = fs.readFileSync("pot/" + poFile + 't', {encoding:'utf-8'})
-                               .match(/\#TranslationFunction[\s]+([\S]+)/)[1];
+            return str1 + str2;
+        }
+        ++i;
+    }
+    throw new Error("not ended string at character " + deb);
+}
 
-      fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
+let msgid;
+let msgstr;
 
-      po = fs.readFileSync("po/" + lang + '/' + poFile, {encoding:'utf-8'});
+for (const lang of fs.readdirSync("po")) {
+    const jsFile = fs.openSync(ROOT + "js/" + lang + ".js", "w");
+    fs.writeSync(jsFile, "(function(){var ");
+    let translationFunction = "translationFunction";
 
-      i = 0; len = po.length;
-      while (i < len) {
-         skipSpaces();
+    for (const poFile of fs.readdirSync("po/" + lang)) {
+        translationFunction = fs.readFileSync("pot/" + poFile + "t", {encoding: "utf-8"})
+            .match(/#TranslationFunction[\s]+(?<functionName>[\S]+)/u).groups.functionName;
 
-         if (po[i] === '#') {
-            skipLine();
-            continue;
-         }
+        fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
 
-         if (po.substr(i, 5) === "msgid") {
-            if (po[i+5].trim() && po[i+5] !== '"') {
-               skipLine(); // don't understand this line
-               continue;
-            }
-            i+=5;
+        po = fs.readFileSync("po/" + lang + "/" + poFile, {encoding: "utf-8"});
+
+        i = 0;
+        len = po.length;
+        while (i < len) {
             skipSpaces();
-            msgid = parseString();
-         } else if (po.substr(i, 6) === "msgstr") {
-            if (po[i+6].trim() && po[i+6] !== '"') {
-               skipLine(); // don't understand this line
-               continue;
+
+            if (po.substr(i, 5) === "msgid") {
+                if (po[i + 5].trim() && po[i + 5] !== '"') {
+                    skipLine(); // don't understand this line
+                } else {
+                    i += 5;
+                    skipSpaces();
+                    msgid = parseString();
+                }
+            } else if (po.substr(i, 6) === "msgstr") {
+                if (po[i + 6].trim() && po[i + 6] !== '"') {
+                    skipLine(); // don't understand this line
+                } else {
+                    i += 6;
+                    msgstr = parseString();
+                    fs.writeSync(
+                        jsFile,
+                        '_("' + lang + '","' + msgid.replace(/\n/gu, "") + '","' + msgstr.replace(/\n/gu, "") + '");'
+                    );
+                }
             }
-            i+=6;
-            msgstr = parseString();
-            fs.writeSync(jsFile, '_("' + lang + '","' + msgid.replace(/\n/g,"") + '","' + msgstr.replace(/\n/g,"") + '");');
-         }
-         skipLine();
-      }
-   }
-
-   fs.writeSync(jsFile, "if(" + translationFunction + ".applyL10n){" + translationFunction + ".applyL10n();}})();");
-   fs.close(jsFile, function (e) {
-       if (e) {
-        console.error(e);
-       }
-   });
+
+            // if po[i] === "#", ignore
+            skipLine();
+        }
+    }
+
+    fs.writeSync(
+        jsFile,
+        "if(" + translationFunction + ".applyL10n){" + translationFunction + ".applyL10n();}})();"
+    );
+
+    fs.close(jsFile, function (e) {
+        if (e) {
+            console.error(e);
+        }
+    });
 }
index d31cdc2e8682a4a38f57d60c7a8e1fd3e86937ee..8a2b7c44837b4e925ba8eb80d1a897a4c0c62ed1 100644 (file)
@@ -14,7 +14,9 @@
     let alertInput;
     let divAlertContent;
 
-    const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {return s;};
+    const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {
+        return s;
+    };
 
     function promptOK() {
         divAlert.style.display = "none";
index 7071251981bca50514dfc210690e60a78fc2b651..5b993e4b55a62860255fa8bf4c70fa703f138729 100644 (file)
@@ -38,7 +38,7 @@
                     </select>
                 </label>
             </div>
-            <div id="board-lang-selection">
+            <div id="board-lang-selection" style="display:none">
                 <label>
                     <span data-l10n="text-content">Board language: </span>
                     <select id="board-lang">
index 1200f088b67a8967d80b429e774a0f65d1147968..1b0f4e855e81552b7582dde8ac2dde8e05b4af4b 100644 (file)
@@ -6,7 +6,7 @@ window.libD = {
 
         const t = [];
 
-        function f (lang, orig, translated) {
+        function f(lang, orig, translated) {
             if (!orig) {
                 return (
                     (libD.lang && t[libD.lang] && t[libD.lang][lang])
@@ -20,7 +20,7 @@ window.libD = {
             }
 
             t[lang][orig] = translated;
-        };
+        }
 
         return f;
     },
index 56e05aadb6d21e3ab1b97c37372a4a6cf93bec40..faf7de1db2d26e5f4be69218066b267fb4612dc2 100644 (file)
@@ -1,58 +1,70 @@
 //thx http://stackoverflow.com/questions/1517924/javascript-mapping-touch-events-to-mouse-events#1781750
+/*global jQuery*/
 
 function touchHandler(event) {
-    var nn = event.target.nodeName.toLowerCase();
+    "use strict";
+
+    const nn = event.target.nodeName.toLowerCase();
 
     if (nn === "input" || nn === "select" || nn === "button" || nn === "textarea") {
         return;
     }
 
-    var touches = event.changedTouches,
-        first   = touches ? touches[0] : event,
-        type    = "";
+    const touches = event.changedTouches;
+    const first   = touches ? touches[0] : event;
+    let type      = "";
 
     switch (event.type) {
-    case "touchstart": type = "mousedown"; break;
-    case "touchmove":  type = "mousemove"; break;
-    case "touchend":   type = "mouseup";   break;
-    case "tap":        type = "click";     break;
-    case "dbltap":     type = "dblclick";  break;
-    default: return;
+        case "touchstart":
+            type = "mousedown";
+            break;
+        case "touchmove":
+            type = "mousemove";
+            break;
+        case "touchend":
+            type = "mouseup";
+            break;
+        case "tap":
+            type = "click";
+            break;
+        case "dbltap":
+            type = "dblclick";
+            break;
+        default:
+            return;
     }
 
-//initMouseEvent(type, canBubble, cancelable, view, clickCount,
-//              screenX, screenY, clientX, clientY, ctrlKey,
-//              altKey, shiftKey, metaKey, button, relatedTarget);
-
-     var simulatedEvent = document.createEvent("MouseEvent");
-     simulatedEvent.initMouseEvent(type, true, true, window, 1,
-            first.screenX, first.screenY,
-            first.clientX, first.clientY, false,
-            false, false, false, 0/*left*/, null);
-     first.target.dispatchEvent(simulatedEvent);
-     event.preventDefault();
+    //initMouseEvent(type, canBubble, cancelable, view, clickCount,
+    //              screenX, screenY, clientX, clientY, ctrlKey,
+    //              altKey, shiftKey, metaKey, button, relatedTarget);
+
+    const simulatedEvent = document.createEvent("MouseEvent");
+    simulatedEvent.initMouseEvent(type, true, true, window, 1,
+        first.screenX, first.screenY,
+        first.clientX, first.clientY, false,
+        false, false, false, 0/*left*/, null);
+    first.target.dispatchEvent(simulatedEvent);
+    event.preventDefault();
 }
 
-document.addEventListener('touchend', (function(speed, distance) {
-/*
- * Copyright (c)2012 Stephen M. McKamey.
- * Licensed under The MIT License.
- * src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
- */
+document.addEventListener("touchend", (function (speed, distance) {
+    // Copyright (c)2012 Stephen M. McKamey.
+    // Licensed under The MIT License.
+    // src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
 
     "use strict";
 
     // default dblclick speed to half sec (default for Windows & Mac OS X)
-    speed = Math.abs(+speed) || 500;//ms
+    speed = Math.abs(Number(speed)) || 500;//ms
     // default dblclick distance to within 40x40 pixel area
-    distance = Math.abs(+distance) || 40;//px
+    distance = Math.abs(Number(distance)) || 40;//px
 
     // Date.now() polyfill
-    var now = Date.now || function() {
-        return +new Date();
+    const now = Date.now || function () {
+        return Number(new Date());
     };
 
-    var cancelEvent = function(e) {
+    function cancelEvent(e) {
         e = (e || window.event);
 
         if (e) {
@@ -69,24 +81,24 @@ document.addEventListener('touchend', (function(speed, distance) {
             }
         }
         return false;
-    };
+    }
 
-    var taps = 0,
-        last = 0,
-        // NaN will always test false
-        x = NaN,
-        y = NaN;
+    let taps = 0;
+    let last = 0;
+    // NaN will always test false
+    let x = NaN;
+    let y = NaN;
 
     return function (e) {
         e = (e || window.event);
 
-        var time   = now(),
-            touch  = e.changedTouches ? e.changedTouches[0] : e,
-            nextX  = +touch.clientX,
-            nextY  = +touch.clientY,
-            target = e.target || e.srcElement,
-            e2,
-            parent;
+        const time   = now();
+        const touch  = e.changedTouches ? e.changedTouches[0] : e;
+        const nextX  = Number(touch.clientX);
+        const nextY  = Number(touch.clientY);
+        const target = e.target || e.srcElement;
+        let e2;
+        let parent;
 
         if ((last + speed) > time &&
             Math.abs(nextX - x) < distance &&
@@ -106,9 +118,9 @@ document.addEventListener('touchend', (function(speed, distance) {
 
         // fire tap event
         if (document.createEvent) {
-            e2 = document.createEvent('MouseEvents');
+            e2 = document.createEvent("MouseEvents");
             e2.initMouseEvent(
-                'tap',
+                "tap",
                 true,             // click bubbles
                 true,             // click cancelable
                 e.view,              // copy view
@@ -145,18 +157,18 @@ document.addEventListener('touchend', (function(speed, distance) {
                 // DOM Level 0, IE
                 parent.ontap(e);
 
-            } else if (typeof jQuery !== 'undefined') {
+            } else if (typeof jQuery !== "undefined") {
                 // cop out and patch IE6-8 with jQuery
-                jQuery(this).trigger('tap', e);
+                jQuery(this).trigger("tap", e); /* eslint-disable-line no-invalid-this */
             }
         }
 
         if (taps === 2) {
             // fire dbltap event only for 2nd click
             if (document.createEvent) {
-                e2 = document.createEvent('MouseEvents');
+                e2 = document.createEvent("MouseEvents");
                 e2.initMouseEvent(
-                    'dbltap',
+                    "dbltap",
                     true,             // dblclick bubbles
                     true,             // dblclick cancelable
                     e.view,              // copy view
@@ -193,9 +205,9 @@ document.addEventListener('touchend', (function(speed, distance) {
                     // DOM Level 0, IE
                     parent.ondbltap(e);
 
-                } else if (typeof jQuery !== 'undefined') {
+                } else if (typeof jQuery !== "undefined") {
                     // cop out and patch IE6-8 with jQuery
-                    jQuery(this).trigger('dbltap', e);
+                    jQuery(this).trigger("dbltap", e); /* eslint-disable-line no-invalid-this */
                 }
             }
         }
index 7e8a9c9f16b9c7e93ff49762e14ca6d92126c351..2d897cb1263ef7274895cfe6b2bd7bd2f2d67516 100644 (file)
@@ -56,9 +56,9 @@
     let board;
     let rack;
     const boardCells = [];
-    let langName;
     let scoreOf;
     let bag;
+    let boardLangSelect;
 
     const playerLetters = [];
     let currentPlayer = "";
         refreshCurrentPlayer();
     }
 
+    function setPlayers(players) {
+        if (participantPlaceholder) {
+            participantPlaceholder.parentNode.removeChild(participantPlaceholder);
+            participantPlaceholder = null;
+        }
+
+        for (let i = 0; i < players.length; i++) {
+            const player = players[i];
+            const playerName = players[i].player;
+
+            if (!tablePlayers[playerName]) {
+                let before = null;
+
+                for (let j = 1;  j < participants.rows.length; j++) {
+                    if (playerName < participants.rows[j].cells[0].textContent) {
+                        before = participants.rows[j];
+                        break;
+                    }
+                }
+
+                const row = document.createElement("tr");
+                participants.insertBefore(row, before);
+                row.appendChild(document.createElement("td"));
+                row.lastChild.textContent = playerName;
+
+                row.appendChild(document.createElement("td"));
+
+                row.appendChild(document.createElement("td"));
+                row.lastChild.className = "score-cell";
+
+                row.lastChild.appendChild(document.createElement("span"));
+                row.lastChild.lastChild.onclick = (
+                    function (row) {
+                        return function () {
+                            if (!pollingReady) {
+                                showConnectionLost();
+                                return;
+                            }
+
+                            myPrompt(
+                                format(
+                                    _("Enter the new score of {0}:"),
+                                    row.firstChild.textContent
+                                ),
+                                function (res) {
+                                    res = parseInt(res);
+                                    if (!isNaN(res)) {
+                                        sendCmds([{
+                                            cmd: "score",
+                                            player: row.firstChild.textContent,
+                                            score: res
+                                        }]);
+                                    }
+                                },
+                                row.lastChild.firstChild.textContent
+                            );
+                        };
+                    }
+                )(row);
+                row.lastChild.appendChild(document.createTextNode(" "));
+                row.lastChild.appendChild(document.createElement("button"));
+                row.lastChild.lastChild.textContent = "+";
+                row.lastChild.lastChild.onclick = (
+                    function (row) {
+                        return function () {
+                            if (!pollingReady) {
+                                showConnectionLost();
+                                return;
+                            }
+
+                            myPrompt(
+                                format(
+                                    _("Enter the score to add to {0}:"),
+                                    row.firstChild.textContent
+                                ),
+                                function (res) {
+                                    res = parseInt(res);
+                                    if (!isNaN(res)) {
+                                        sendCmds([{
+                                            cmd: "score",
+                                            player: row.firstChild.textContent,
+                                            score: parseInt(row.childNodes[2].childNodes[0].textContent) + res
+                                        }]);
+                                    }
+                                },
+                                row.lastChild.firstChild.textContent
+                            );
+                        };
+                    }
+                )(row);
+
+
+                row.appendChild(document.createElement("td"));
+                row.lastChild.className = "turn-cell";
+                row.lastChild.appendChild(document.createElement("button"));
+                const img = new Image();
+                img.src = "baton.svg";
+                row.lastChild.lastChild.appendChild(img);
+                row.lastChild.lastChild.onclick = (function (playerName) {
+                    sendCmds([{
+                        cmd: "currentPlayer",
+                        player: playerName === currentPlayer ? "" : playerName
+                    }]);
+                }).bind(null, playerName);
+
+                tablePlayers[playerName] = row;
+            }
+
+            if (Object.prototype.hasOwnProperty.call(player, "score")) {
+                const scoreCell = tablePlayers[playerName].childNodes[2].childNodes[0];
+                scoreCell.textContent = player.score;
+                blink(scoreCell);
+            }
+
+            if (Object.prototype.hasOwnProperty.call(player, "rackCount")) {
+                const countCell = tablePlayers[playerName].childNodes[1];
+                countCell.textContent = player.rackCount;
+                blink(countCell);
+            }
+        }
+
+        refreshCurrentPlayer();
+    }
+
+    function applyAction(data) {
+        switch (data.action) {
+            case "pushBag": //TODO
+                break;
+
+            case "popBag": //TODO
+                break;
+
+            case "reset": //TODO
+                tablePlayers = {};
+                while (participants.rows[1]) {
+                    participants.removeChild(participants.rows[1]);
+                }
+                sendCmds([{cmd: "hello"}]);
+                break;
+
+            case "moveLetter":
+                if (data.from === "board") {
+                    setCell(data.indexFrom, "");
+                } else if (data.from === "rack") {
+                    setRackCell(data.indexFrom, "");
+                }
+
+                if (data.to === "board") {
+                    setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
+                } else if (data.to === "rack") {
+                    setRackCell(data.indexTo, data.letter);
+                }
+                break;
+            case "setCell":
+                setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
+                break;
+            case "setRackCell":
+                setRackCell(data.indexTo, data.letter);
+        }
+    }
+
     function handleReceivedData(data) {
         if (Array.isArray(data)) {
             data.forEach(handleReceivedData);
         }
 
         if (data.players) {
-            if (participantPlaceholder) {
-                participantPlaceholder.parentNode.removeChild(participantPlaceholder);
-                participantPlaceholder = null;
-            }
-
-            for (let i = 0; i < data.players.length; i++) {
-                const player = data.players[i];
-                const playerName = data.players[i].player;
-
-                if (!tablePlayers[playerName]) {
-                    let before = null;
-
-                    for (let j = 1;  j < participants.rows.length; j++) {
-                        if (playerName < participants.rows[j].cells[0].textContent) {
-                            before = participants.rows[j];
-                            break;
-                        }
-                    }
-
-                    const row = document.createElement("tr");
-                    participants.insertBefore(row, before);
-                    row.appendChild(document.createElement("td"));
-                    row.lastChild.textContent = playerName;
-
-                    row.appendChild(document.createElement("td"));
-
-                    row.appendChild(document.createElement("td"));
-                    row.lastChild.className = "score-cell";
-
-                    row.lastChild.appendChild(document.createElement("span"));
-                    row.lastChild.lastChild.onclick = (
-                        function (row) {
-                            return function () {
-                                if (!pollingReady) {
-                                    showConnectionLost();
-                                    return;
-                                }
-
-                                myPrompt(
-                                    format(
-                                        _("Enter the new score of {0}:"),
-                                        row.firstChild.textContent
-                                    ),
-                                    function (res) {
-                                        res = parseInt(res);
-                                        if (!isNaN(res)) {
-                                            sendCmds([{
-                                                cmd: "score",
-                                                player: row.firstChild.textContent,
-                                                score: res
-                                            }]);
-                                        }
-                                    },
-                                    row.lastChild.firstChild.textContent
-                                );
-                            };
-                        }
-                    )(row);
-                    row.lastChild.appendChild(document.createTextNode(" "));
-                    row.lastChild.appendChild(document.createElement("button"));
-                    row.lastChild.lastChild.textContent = "+";
-                    row.lastChild.lastChild.onclick = (
-                        function (row) {
-                            return function() {
-                                if (!pollingReady) {
-                                    showConnectionLost();
-                                    return;
-                                }
-
-                                myPrompt(
-                                    format(
-                                        _("Enter the score to add to {0}:"),
-                                        row.firstChild.textContent
-                                    ),
-                                    function (res) {
-                                        res = parseInt(res);
-                                        if (!isNaN(res)) {
-                                            sendCmds([{
-                                                cmd: "score",
-                                                player: row.firstChild.textContent,
-                                                score: parseInt(row.childNodes[2].childNodes[0].textContent) + res
-                                            }]);
-                                        }
-                                    },
-                                    row.lastChild.firstChild.textContent
-                                );
-                            };
-                        }
-                    )(row);
-
-
-                    row.appendChild(document.createElement("td"));
-                    row.lastChild.className = "turn-cell";
-                    row.lastChild.appendChild(document.createElement("button"));
-                    const img = new Image();
-                    img.src = "baton.svg";
-                    row.lastChild.lastChild.appendChild(img);
-                    row.lastChild.lastChild.onclick = (function (playerName) {
-                        sendCmds([{
-                            cmd: "currentPlayer",
-                            player: playerName === currentPlayer ? "" : playerName
-                        }]);
-                    }).bind(null, playerName);
-
-                    tablePlayers[playerName] = row;
-                }
-
-                if (Object.prototype.hasOwnProperty.call(player, "score")) {
-                    const scoreCell = tablePlayers[playerName].childNodes[2].childNodes[0];
-                    scoreCell.textContent = player.score;
-                    blink(scoreCell);
-                }
-
-                if (Object.prototype.hasOwnProperty.call(player, "rackCount")) {
-                    const countCell = tablePlayers[playerName].childNodes[1];
-                    countCell.textContent = player.rackCount;
-                    blink(countCell);
-                }
-            }
-
-            refreshCurrentPlayer();
+            setPlayers(data.players);
         }
 
         if (data.playerName) {
             set("gameNumber", data.gameNumber);
         }
 
-        if (data.boardLang) {
-            set("boardLang", data.boardLang);
+        if (data.availableBoardLangs) {
+            setAvailableBoardLangs(data.availableBoardLangs);
         }
 
-        if (data.langName) {
-            langName = data.langName;
-            createLangBoardSelect(langName);
+        if (data.boardLang) {
+            set("boardLang", data.boardLang);
+            (boardLangSelect || {}).value = data.boardLang;
         }
 
         if (data.letterValues) {
             setRack(data.rack);
         }
 
-        if (!data.action) {
-            return;
-        }
-
-        switch (data.action) {
-            case "pushBag": //TODO
-                break;
-
-            case "popBag": //TODO
-                break;
-
-            case "reset": //TODO
-                tablePlayers = {};
-                while (participants.rows[1]) {
-                    participants.removeChild(participants.rows[1]);
-                }
-                sendCmds([{cmd: "hello"}]);
-                break;
-
-            case "moveLetter":
-                if (data.from === "board") {
-                    setCell(data.indexFrom, "");
-                } else if (data.from === "rack") {
-                    setRackCell(data.indexFrom, "");
-                }
-
-                if (data.to === "board") {
-                    setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
-                } else if (data.to === "rack") {
-                    setRackCell(data.indexTo, data.letter);
-                }
-                break;
-            case "setCell":
-                setCell(data.indexTo, data.letter, Object.prototype.hasOwnProperty.call(data, "player") && data.player !== localStorage.trivabblePlayerName);
-                break;
-            case "setRackCell":
-                setRackCell(data.indexTo, data.letter);
+        if (data.action) {
+            applyAction(data);
         }
     }
 
         );
     }
 
-    function changeBoardLang(lang) {
+    function onChangeBoardLang() {
+        const code = document.getElementById("board-lang").value;
+        const lang = document.getElementById("board-lang").textContent;
+
         myConfirm(
-            format(_("Are you sure you want to change board to '{0}'? This will put all the tiles back in the bag and start another game."), _(langName[lang])),
+            format(_("Are you sure you want to change board to '{0}'? This will put all the tiles back in the bag and start another game."), _(lang)),
             function () {
-                sendCmds([{cmd: "changeBoard", lang: lang}]);
+                sendCmds([{cmd: "changeBoard", lang: code}]);
             },
             function () {
                 document.getElementById("board-lang").value = localStorage.trivabbleBoardLang;
             }
         );
     }
-    function onChangeBoardLang() {
-        changeBoardLang(document.getElementById("board-lang").value);
-    }
 
     function clearRack() {
         myConfirm(
         trivabble.run();
     };
 
-    function createLangBoardSelect(langName) {
-
-        const select = document.getElementById("board-lang");
-
-        for (let i = select.options.length - 1; i >=0; i--) {
-            select.remove(i);
+    function setAvailableBoardLangs(availableBoardLangs) {
+        if (!boardLangSelect) {
+            const boardLangSelection = document.getElementById("board-lang-selection");
+            boardLangSelect = boardLangSelection.querySelector("select");
+            boardLangSelection.style.display = "";
         }
 
-        for (const key in langName) {/* eslint-disable-line guard-for-in */
-            select.add(new Option(_(langName[key]), key));
+        boardLangSelect.textContent = "";
+
+        for (const key of Object.keys(availableBoardLangs)) {
+            boardLangSelect.add(new Option(_(availableBoardLangs[key]), key));
         }
 
-        Array.prototype.sort.call(select.options,
-            function(a, b) {return (a.textContent > b.textContent) ? 1 : ((b.textContent > a.textContent) ? -1 : 0);});
+        Array.prototype.sort.call(boardLangSelect.options, function (a, b) {
+            return (
+                (a.textContent > b.textContent)
+                    ? 1
+                    : (
+                        (b.textContent > a.textContent)
+                            ? -1
+                            : 0
+                    )
+            );
+        });
 
-        select.value = localStorage.trivabbleBoardLang;
+        boardLangSelect.value = localStorage.trivabbleBoardLang;
     }
 
     function langSelectionChange(e) {
                     specialCell("doubleWord", board.lastChild.lastChild);
                 } else if ((i % 4 === 1) && (j % 4 === 1)) {
                     specialCell("tripleLetter", board.lastChild.lastChild);
-                } else if ((i < 8 && doubleLetter[i + "," + j]) || (i > 7 && doubleLetter[(14-i) + "," + j]) || (i === 7 && (j === 3 || j === 11))) {
+                } else if ((i < 8 && doubleLetter[i + "," + j]) || (i > 7 && doubleLetter[(14 - i) + "," + j]) || (i === 7 && (j === 3 || j === 11))) {
                     specialCell("doubleLetter", board.lastChild.lastChild);
                 }
             }
index b3c1f613c545dbc9840f46281895b89f46a256ad..85a1be0cf6e78a961cbbefb55027f1c6742b644e 100644 (file)
@@ -31,6 +31,7 @@ const port = parseInt(process.env.TRIVABBLE_PORT || "3000");
 const SAVE_TIMEOUT = 5000;
 const KEEP_ALIVE = 30000;
 const GAMES_BACKUP = process.env.TRIVABBLE_GAMES_BACKUP || "games.backup.json";
+const DEFAULT_BOARD_LANG = process.env.TRIVABBLE_DEFAULT_BOARD_LANG || "fr";
 
 const VERSION = 202005070100;
 
@@ -47,10 +48,10 @@ if (DEV_ENABLE_SERVING_FILES) {
 
 const debuglog = DEBUG_LOG ? console.log.bind(console) : () => null;
 
-const http = require("http");
-const fs   = require("fs");
+const http   = require("http");
+const path   = require("path");
+const fs     = require("fs");
 const crypto = require("crypto");
-const path = require("path");
 
 const REQUEST_TYPE_LONG_POLLING = 1;
 const REQUEST_TYPE_SSE          = 2;
@@ -59,16 +60,14 @@ const REQUEST_TYPE_WEBSOCKET    = 3;
 /* eslint no-sync: ["error", { allowAtRootLevel: true }] */
 
 /* Manage multi language board */
-const boardPieces = {};
-const langName = {};
-const langFiles = fs.readdirSync(path.join(__dirname, "lang"));
-for (let i = 0; i < langFiles.length; i++) {
-    console.log(path.join(__dirname, "lang", langFiles[i]));
-    const data = require(path.join(__dirname, "lang", langFiles[i])); /* eslint-disable-line global-require */
-    boardPieces[data.code] = data;
-    langName[data.code] = data.name;
+const boardTilesPerLang = {};
+const availableBoardLangs = {};
+
+for (const lang of fs.readdirSync(path.join(__dirname, "lang"))) {
+    const data = require(path.join(__dirname, "lang", lang)); // eslint-disable-line global-require
+    boardTilesPerLang[data.code] = data;
+    availableBoardLangs[data.code] = data.name;
 }
-const defaultLang = "fr";
 
 const games = {};
 
@@ -214,10 +213,9 @@ function newBoard() {
 
 Game.prototype.init = function (lang) {
     this.board = newBoard();
-    this.lang = lang || defaultLang;
-    this.langName = langName;
-    this.bag = boardPieces[this.lang].bag.slice();
-    this.letterValues = boardPieces[this.lang].letterValues;
+    this.lang = lang || DEFAULT_BOARD_LANG;
+    this.bag = boardTilesPerLang[this.lang].bag.slice();
+    this.letterValues = boardTilesPerLang[this.lang].letterValues;
     this.racks = {};
     this.scores = {};
     this.lastUpdated = new Date();
@@ -230,7 +228,6 @@ Game.prototype.toJSON = function () {
     return {
         board:            this.board,
         lang:             this.lang,
-        langName:         this.langName,
         bag:              this.bag,
         letterValues:     this.letterValues,
         racks:            this.racks,
@@ -243,10 +240,9 @@ Game.prototype.toJSON = function () {
 Game.fromJSON = function (obj) {
     const game = new Game();
     game.board            = obj.board  || newBoard();
-    game.lang             = obj.lang   || defaultLang;
-    game.langName         = obj.langName;
-    game.bag              = boardPieces[game.lang].bag.slice();
-    game.letterValues     = boardPieces[game.lang].letterValues;
+    game.lang             = obj.lang   || DEFAULT_BOARD_LANG;
+    game.bag              = boardTilesPerLang[game.lang].bag.slice();
+    game.letterValues     = boardTilesPerLang[game.lang].letterValues;
     game.racks            = obj.racks  || {};
     game.scores           = obj.scores || {};
     game.lastUpdated      = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date();
@@ -293,15 +289,13 @@ Game.prototype.playerJoined = function (playerName) {
 
     const players = [];
 
-    for (let player in this.racks) {
-        if (Object.prototype.hasOwnProperty.call(this.racks, player)) {
-            player = player.slice(1); // '#'
-            players.push({
-                player: player,
-                score: this.getPlayerScore(player),
-                rackCount: countTiles(this.getPlayerRack(player))
-            });
-        }
+    for (let player of Object.keys(this.racks)) {
+        player = player.slice(1); // '#'
+        players.push({
+            player: player,
+            score: this.getPlayerScore(player),
+            rackCount: countTiles(this.getPlayerRack(player))
+        });
     }
 
     this.pendingEvents.push({players: players});
@@ -315,7 +309,7 @@ Game.prototype.addListeningPlayer = function (playerName, responseAndType) {
 
     let closed = false;
 
-    function close () {
+    function close() {
         if (closed) {
             return;
         }
@@ -481,7 +475,7 @@ function handleCommand(cmdNumber, message, response) {
                 gameNumber: gameNumber,
                 playerName: playerName,
                 boardLang: game.lang,
-                langName: langName,
+                availableBoardLangs: availableBoardLangs,
                 currentPlayer: game.currentPlayer,
                 rack: game.getPlayerRack(playerName),
                 board: game.board,
@@ -672,7 +666,7 @@ function handleCommand(cmdNumber, message, response) {
         }
 
         case "changeBoard": {
-            game.lang = cmd.lang || defaultLang;
+            game.lang = cmd.lang || DEFAULT_BOARD_LANG;
             game.reset();
             reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, letterValues: game.letterValues});
             break;
@@ -707,16 +701,16 @@ function handleCommands(message, responseAndType) {
 
         writeMessage(responseAndType,
             JSON.stringify({
-                playerName:       message.playerName,
-                currentPlayer:    game.currentPlayer,
-                gameNumber:       gameNumber,
-                boardLang:        game.lang,
-                langName:         langName,
-                letterValues:     game.letterValues,
-                rack:             game.getPlayerRack(message.playerName),
-                board:            game.board,
-                remainingLetters: game.bag.length,
-                version:          VERSION
+                playerName:          message.playerName,
+                currentPlayer:       game.currentPlayer,
+                gameNumber:          gameNumber,
+                boardLang:           game.lang,
+                availableBoardLangs: availableBoardLangs,
+                letterValues:        game.letterValues,
+                rack:                game.getPlayerRack(message.playerName),
+                board:               game.board,
+                remainingLetters:    game.bag.length,
+                version:             VERSION
             })
         );
 
@@ -949,9 +943,10 @@ function handleRequest(request, response) {
 
             debuglog("Serving " + request.url);
 
-            fs.exists("../public/" + request.url, function (exists) {
+            const requestedPath = path.join(__dirname, "..", "public", request.url);
+            fs.exists(requestedPath, function (exists) {
                 if (exists) {
-                    fs.readFile("../public/" + request.url, function(err, contents) {
+                    fs.readFile(requestedPath, function (err, contents) {
                         if (err) {
                             response.statusCode = 500;
                             response.setHeader("Content-Type", "text/plain; charset=utf-8");
@@ -1025,10 +1020,8 @@ fs.readFile(GAMES_BACKUP, function (err, data) {
         } else {
             const backup = JSON.parse(data);
 
-            for (const gameNumber in backup) {
-                if (Object.prototype.hasOwnProperty.call(backup, gameNumber)) {
-                    games[gameNumber] = Game.fromJSON(backup[gameNumber]);
-                }
+            for (const gameNumber of Object.keys(backup)) {
+                games[gameNumber] = Game.fromJSON(backup[gameNumber]);
             }
         }
     } catch (e) {
@@ -1158,7 +1151,7 @@ fs.readFile(GAMES_BACKUP, function (err, data) {
         console.error("An error happened in the HTTP server", error);
     });
 
-    server.listen(port, function() {
+    server.listen(port, function () {
         console.log("Server listening on: http://localhost:%s", port);
     });
 });