Linting and refactor

This commit is contained in:
Raphaël Jakse 2020-05-16 10:02:18 +02:00
parent d44a225a65
commit 3a20d898ab
9 changed files with 423 additions and 383 deletions

View File

@ -298,12 +298,16 @@
"sort-keys": "off", "sort-keys": "off",
"sort-vars": "off", "sort-vars": "off",
"space-before-blocks": "error", "space-before-blocks": "error",
"space-before-function-paren": "off", "space-before-function-paren": ["error", {
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": [ "space-in-parens": [
"error", "error",
"never" "never"
], ],
"space-infix-ops": "off", "space-infix-ops": ["error", { "int32Hint": false }],
"space-unary-ops": "error", "space-unary-ops": "error",
"spaced-comment": "off", "spaced-comment": "off",
"strict": [ "strict": [

View File

@ -1,6 +1,10 @@
# -*- Makefile -*- # -*- Makefile -*-
PORT = 3000 ESLINT?=eslint
ifeq (, $(shell which $(firstword ${ESLINT})))
ESLINT?=npx eslint
endif
help: help:
@echo make lang: build translation files @echo make lang: build translation files
@ -11,11 +15,7 @@ lang:
cd l10n; make cd l10n; make
eslint: eslint:
-npx eslint server/trivabble-server.js -${ESLINT} **/*.js
-npx eslint public/alert.js
-npx eslint public/config.js
-npx eslint public/l10n.js
-npx eslint public/trivabble.js
start-dev-server: start-dev-server:
cd server && make start-dev-server cd server && make start-dev-server

View File

@ -1,104 +1,117 @@
#!/usr/bin/env node #!/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"); const fs = require("fs");
var po, i, len;
let po;
let i;
let len;
function skipLine() { function skipLine() {
while (i < len && po[i] !== '\n') { while (i < len && po[i] !== "\n") {
++i; ++i;
} }
++i; ++i;
} }
function skipSpaces() { function skipSpaces() {
while (i < len && !po[i].trim()) { while (i < len && !po[i].trim()) {
++i; ++i;
} }
} }
function parseString() { function parseString() {
skipSpaces(); skipSpaces();
if (po[i] !== '"') { if (po[i] !== '"') {
return ""; return "";
} }
++i; ++i;
var deb = i, end; const deb = i;
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; while (i < len) {
} if (po[i] === "\\") {
++i; ++i;
} } else if (po[i] === '"') {
throw new Error("not ended string at character " + deb); const str1 = po.substring(deb, i++);
} const end = i;
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];
var translationFunction = fs.readFileSync("pot/" + poFile + 't', {encoding:'utf-8'})
.match(/\#TranslationFunction[\s]+([\S]+)/)[1];
fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
po = fs.readFileSync("po/" + lang + '/' + poFile, {encoding:'utf-8'});
i = 0; len = po.length;
while (i < len) {
skipSpaces();
if (po[i] === '#') {
skipLine();
continue;
}
if (po.substr(i, 5) === "msgid") {
if (po[i+5].trim() && po[i+5] !== '"') {
skipLine(); // don't understand this line
continue;
}
i+=5;
skipSpaces(); skipSpaces();
msgid = parseString(); const ndeb = i;
} else if (po.substr(i, 6) === "msgstr") { const str2 = parseString();
if (po[i+6].trim() && po[i+6] !== '"') {
skipLine(); // don't understand this line
continue;
}
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();}})();"); if (i === ndeb) { // we did not parse anything
fs.close(jsFile, function (e) { i = end;
if (e) { return str1;
console.error(e); }
}
}); return str1 + str2;
}
++i;
}
throw new Error("not ended string at character " + deb);
}
let msgid;
let msgstr;
for (const lang of fs.readdirSync("po")) {
const jsFile = fs.openSync(ROOT + "js/" + lang + ".js", "w");
fs.writeSync(jsFile, "(function(){var ");
let translationFunction = "translationFunction";
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;
fs.writeSync(jsFile, "_=" + translationFunction + ".l10n;");
po = fs.readFileSync("po/" + lang + "/" + poFile, {encoding: "utf-8"});
i = 0;
len = po.length;
while (i < len) {
skipSpaces();
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, "") + '");'
);
}
}
// if po[i] === "#", ignore
skipLine();
}
}
fs.writeSync(
jsFile,
"if(" + translationFunction + ".applyL10n){" + translationFunction + ".applyL10n();}})();"
);
fs.close(jsFile, function (e) {
if (e) {
console.error(e);
}
});
} }

View File

@ -14,7 +14,9 @@
let alertInput; let alertInput;
let divAlertContent; 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() { function promptOK() {
divAlert.style.display = "none"; divAlert.style.display = "none";

View File

@ -38,7 +38,7 @@
</select> </select>
</label> </label>
</div> </div>
<div id="board-lang-selection"> <div id="board-lang-selection" style="display:none">
<label> <label>
<span data-l10n="text-content">Board language: </span> <span data-l10n="text-content">Board language: </span>
<select id="board-lang"> <select id="board-lang">

View File

@ -6,7 +6,7 @@ window.libD = {
const t = []; const t = [];
function f (lang, orig, translated) { function f(lang, orig, translated) {
if (!orig) { if (!orig) {
return ( return (
(libD.lang && t[libD.lang] && t[libD.lang][lang]) (libD.lang && t[libD.lang] && t[libD.lang][lang])
@ -20,7 +20,7 @@ window.libD = {
} }
t[lang][orig] = translated; t[lang][orig] = translated;
}; }
return f; return f;
}, },

View File

@ -1,58 +1,70 @@
//thx http://stackoverflow.com/questions/1517924/javascript-mapping-touch-events-to-mouse-events#1781750 //thx http://stackoverflow.com/questions/1517924/javascript-mapping-touch-events-to-mouse-events#1781750
/*global jQuery*/
function touchHandler(event) { 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") { if (nn === "input" || nn === "select" || nn === "button" || nn === "textarea") {
return; return;
} }
var touches = event.changedTouches, const touches = event.changedTouches;
first = touches ? touches[0] : event, const first = touches ? touches[0] : event;
type = ""; let type = "";
switch (event.type) { switch (event.type) {
case "touchstart": type = "mousedown"; break; case "touchstart":
case "touchmove": type = "mousemove"; break; type = "mousedown";
case "touchend": type = "mouseup"; break; break;
case "tap": type = "click"; break; case "touchmove":
case "dbltap": type = "dblclick"; break; type = "mousemove";
default: return; break;
case "touchend":
type = "mouseup";
break;
case "tap":
type = "click";
break;
case "dbltap":
type = "dblclick";
break;
default:
return;
} }
//initMouseEvent(type, canBubble, cancelable, view, clickCount, //initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey, // screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget); // altKey, shiftKey, metaKey, button, relatedTarget);
var simulatedEvent = document.createEvent("MouseEvent"); const simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent(type, true, true, window, 1, simulatedEvent.initMouseEvent(type, true, true, window, 1,
first.screenX, first.screenY, first.screenX, first.screenY,
first.clientX, first.clientY, false, first.clientX, first.clientY, false,
false, false, false, 0/*left*/, null); false, false, false, 0/*left*/, null);
first.target.dispatchEvent(simulatedEvent); first.target.dispatchEvent(simulatedEvent);
event.preventDefault(); event.preventDefault();
} }
document.addEventListener('touchend', (function(speed, distance) { document.addEventListener("touchend", (function (speed, distance) {
/* // Copyright (c)2012 Stephen M. McKamey.
* Copyright (c)2012 Stephen M. McKamey. // Licensed under The MIT License.
* Licensed under The MIT License. // src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
* src: https://raw.github.com/mckamey/doubleTap.js/master/doubleTap.js
*/
"use strict"; "use strict";
// default dblclick speed to half sec (default for Windows & Mac OS X) // 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 // default dblclick distance to within 40x40 pixel area
distance = Math.abs(+distance) || 40;//px distance = Math.abs(Number(distance)) || 40;//px
// Date.now() polyfill // Date.now() polyfill
var now = Date.now || function() { const now = Date.now || function () {
return +new Date(); return Number(new Date());
}; };
var cancelEvent = function(e) { function cancelEvent(e) {
e = (e || window.event); e = (e || window.event);
if (e) { if (e) {
@ -69,24 +81,24 @@ document.addEventListener('touchend', (function(speed, distance) {
} }
} }
return false; return false;
}; }
var taps = 0, let taps = 0;
last = 0, let last = 0;
// NaN will always test false // NaN will always test false
x = NaN, let x = NaN;
y = NaN; let y = NaN;
return function (e) { return function (e) {
e = (e || window.event); e = (e || window.event);
var time = now(), const time = now();
touch = e.changedTouches ? e.changedTouches[0] : e, const touch = e.changedTouches ? e.changedTouches[0] : e;
nextX = +touch.clientX, const nextX = Number(touch.clientX);
nextY = +touch.clientY, const nextY = Number(touch.clientY);
target = e.target || e.srcElement, const target = e.target || e.srcElement;
e2, let e2;
parent; let parent;
if ((last + speed) > time && if ((last + speed) > time &&
Math.abs(nextX - x) < distance && Math.abs(nextX - x) < distance &&
@ -106,9 +118,9 @@ document.addEventListener('touchend', (function(speed, distance) {
// fire tap event // fire tap event
if (document.createEvent) { if (document.createEvent) {
e2 = document.createEvent('MouseEvents'); e2 = document.createEvent("MouseEvents");
e2.initMouseEvent( e2.initMouseEvent(
'tap', "tap",
true, // click bubbles true, // click bubbles
true, // click cancelable true, // click cancelable
e.view, // copy view e.view, // copy view
@ -145,18 +157,18 @@ document.addEventListener('touchend', (function(speed, distance) {
// DOM Level 0, IE // DOM Level 0, IE
parent.ontap(e); parent.ontap(e);
} else if (typeof jQuery !== 'undefined') { } else if (typeof jQuery !== "undefined") {
// cop out and patch IE6-8 with jQuery // 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) { if (taps === 2) {
// fire dbltap event only for 2nd click // fire dbltap event only for 2nd click
if (document.createEvent) { if (document.createEvent) {
e2 = document.createEvent('MouseEvents'); e2 = document.createEvent("MouseEvents");
e2.initMouseEvent( e2.initMouseEvent(
'dbltap', "dbltap",
true, // dblclick bubbles true, // dblclick bubbles
true, // dblclick cancelable true, // dblclick cancelable
e.view, // copy view e.view, // copy view
@ -193,9 +205,9 @@ document.addEventListener('touchend', (function(speed, distance) {
// DOM Level 0, IE // DOM Level 0, IE
parent.ondbltap(e); parent.ondbltap(e);
} else if (typeof jQuery !== 'undefined') { } else if (typeof jQuery !== "undefined") {
// cop out and patch IE6-8 with jQuery // cop out and patch IE6-8 with jQuery
jQuery(this).trigger('dbltap', e); jQuery(this).trigger("dbltap", e); /* eslint-disable-line no-invalid-this */
} }
} }
} }

View File

@ -56,9 +56,9 @@
let board; let board;
let rack; let rack;
const boardCells = []; const boardCells = [];
let langName;
let scoreOf; let scoreOf;
let bag; let bag;
let boardLangSelect;
const playerLetters = []; const playerLetters = [];
let currentPlayer = ""; let currentPlayer = "";
@ -689,6 +689,167 @@
refreshCurrentPlayer(); 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) { function handleReceivedData(data) {
if (Array.isArray(data)) { if (Array.isArray(data)) {
data.forEach(handleReceivedData); data.forEach(handleReceivedData);
@ -733,127 +894,7 @@
} }
if (data.players) { if (data.players) {
if (participantPlaceholder) { setPlayers(data.players);
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();
} }
if (data.playerName) { if (data.playerName) {
@ -864,13 +905,13 @@
set("gameNumber", data.gameNumber); set("gameNumber", data.gameNumber);
} }
if (data.boardLang) { if (data.availableBoardLangs) {
set("boardLang", data.boardLang); setAvailableBoardLangs(data.availableBoardLangs);
} }
if (data.langName) { if (data.boardLang) {
langName = data.langName; set("boardLang", data.boardLang);
createLangBoardSelect(langName); (boardLangSelect || {}).value = data.boardLang;
} }
if (data.letterValues) { if (data.letterValues) {
@ -897,43 +938,8 @@
setRack(data.rack); setRack(data.rack);
} }
if (!data.action) { if (data.action) {
return; 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);
} }
} }
@ -1339,20 +1345,20 @@
); );
} }
function changeBoardLang(lang) { function onChangeBoardLang() {
const code = document.getElementById("board-lang").value;
const lang = document.getElementById("board-lang").textContent;
myConfirm( 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 () { function () {
sendCmds([{cmd: "changeBoard", lang: lang}]); sendCmds([{cmd: "changeBoard", lang: code}]);
}, },
function () { function () {
document.getElementById("board-lang").value = localStorage.trivabbleBoardLang; document.getElementById("board-lang").value = localStorage.trivabbleBoardLang;
} }
); );
} }
function onChangeBoardLang() {
changeBoardLang(document.getElementById("board-lang").value);
}
function clearRack() { function clearRack() {
myConfirm( myConfirm(
@ -1523,22 +1529,32 @@
trivabble.run(); trivabble.run();
}; };
function createLangBoardSelect(langName) { function setAvailableBoardLangs(availableBoardLangs) {
if (!boardLangSelect) {
const select = document.getElementById("board-lang"); const boardLangSelection = document.getElementById("board-lang-selection");
boardLangSelect = boardLangSelection.querySelector("select");
for (let i = select.options.length - 1; i >=0; i--) { boardLangSelection.style.display = "";
select.remove(i);
} }
for (const key in langName) {/* eslint-disable-line guard-for-in */ boardLangSelect.textContent = "";
select.add(new Option(_(langName[key]), key));
for (const key of Object.keys(availableBoardLangs)) {
boardLangSelect.add(new Option(_(availableBoardLangs[key]), key));
} }
Array.prototype.sort.call(select.options, Array.prototype.sort.call(boardLangSelect.options, function (a, b) {
function(a, b) {return (a.textContent > b.textContent) ? 1 : ((b.textContent > a.textContent) ? -1 : 0);}); return (
(a.textContent > b.textContent)
? 1
: (
(b.textContent > a.textContent)
? -1
: 0
)
);
});
select.value = localStorage.trivabbleBoardLang; boardLangSelect.value = localStorage.trivabbleBoardLang;
} }
function langSelectionChange(e) { function langSelectionChange(e) {
@ -1638,7 +1654,7 @@
specialCell("doubleWord", board.lastChild.lastChild); specialCell("doubleWord", board.lastChild.lastChild);
} else if ((i % 4 === 1) && (j % 4 === 1)) { } else if ((i % 4 === 1) && (j % 4 === 1)) {
specialCell("tripleLetter", board.lastChild.lastChild); 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); specialCell("doubleLetter", board.lastChild.lastChild);
} }
} }

View File

@ -31,6 +31,7 @@ const port = parseInt(process.env.TRIVABBLE_PORT || "3000");
const SAVE_TIMEOUT = 5000; const SAVE_TIMEOUT = 5000;
const KEEP_ALIVE = 30000; const KEEP_ALIVE = 30000;
const GAMES_BACKUP = process.env.TRIVABBLE_GAMES_BACKUP || "games.backup.json"; 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; const VERSION = 202005070100;
@ -47,10 +48,10 @@ if (DEV_ENABLE_SERVING_FILES) {
const debuglog = DEBUG_LOG ? console.log.bind(console) : () => null; const debuglog = DEBUG_LOG ? console.log.bind(console) : () => null;
const http = require("http"); const http = require("http");
const fs = require("fs"); const path = require("path");
const fs = require("fs");
const crypto = require("crypto"); const crypto = require("crypto");
const path = require("path");
const REQUEST_TYPE_LONG_POLLING = 1; const REQUEST_TYPE_LONG_POLLING = 1;
const REQUEST_TYPE_SSE = 2; const REQUEST_TYPE_SSE = 2;
@ -59,16 +60,14 @@ const REQUEST_TYPE_WEBSOCKET = 3;
/* eslint no-sync: ["error", { allowAtRootLevel: true }] */ /* eslint no-sync: ["error", { allowAtRootLevel: true }] */
/* Manage multi language board */ /* Manage multi language board */
const boardPieces = {}; const boardTilesPerLang = {};
const langName = {}; const availableBoardLangs = {};
const langFiles = fs.readdirSync(path.join(__dirname, "lang"));
for (let i = 0; i < langFiles.length; i++) { for (const lang of fs.readdirSync(path.join(__dirname, "lang"))) {
console.log(path.join(__dirname, "lang", langFiles[i])); const data = require(path.join(__dirname, "lang", lang)); // eslint-disable-line global-require
const data = require(path.join(__dirname, "lang", langFiles[i])); /* eslint-disable-line global-require */ boardTilesPerLang[data.code] = data;
boardPieces[data.code] = data; availableBoardLangs[data.code] = data.name;
langName[data.code] = data.name;
} }
const defaultLang = "fr";
const games = {}; const games = {};
@ -214,10 +213,9 @@ function newBoard() {
Game.prototype.init = function (lang) { Game.prototype.init = function (lang) {
this.board = newBoard(); this.board = newBoard();
this.lang = lang || defaultLang; this.lang = lang || DEFAULT_BOARD_LANG;
this.langName = langName; this.bag = boardTilesPerLang[this.lang].bag.slice();
this.bag = boardPieces[this.lang].bag.slice(); this.letterValues = boardTilesPerLang[this.lang].letterValues;
this.letterValues = boardPieces[this.lang].letterValues;
this.racks = {}; this.racks = {};
this.scores = {}; this.scores = {};
this.lastUpdated = new Date(); this.lastUpdated = new Date();
@ -230,7 +228,6 @@ Game.prototype.toJSON = function () {
return { return {
board: this.board, board: this.board,
lang: this.lang, lang: this.lang,
langName: this.langName,
bag: this.bag, bag: this.bag,
letterValues: this.letterValues, letterValues: this.letterValues,
racks: this.racks, racks: this.racks,
@ -243,10 +240,9 @@ Game.prototype.toJSON = function () {
Game.fromJSON = function (obj) { Game.fromJSON = function (obj) {
const game = new Game(); const game = new Game();
game.board = obj.board || newBoard(); game.board = obj.board || newBoard();
game.lang = obj.lang || defaultLang; game.lang = obj.lang || DEFAULT_BOARD_LANG;
game.langName = obj.langName; game.bag = boardTilesPerLang[game.lang].bag.slice();
game.bag = boardPieces[game.lang].bag.slice(); game.letterValues = boardTilesPerLang[game.lang].letterValues;
game.letterValues = boardPieces[game.lang].letterValues;
game.racks = obj.racks || {}; game.racks = obj.racks || {};
game.scores = obj.scores || {}; game.scores = obj.scores || {};
game.lastUpdated = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date(); game.lastUpdated = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date();
@ -293,15 +289,13 @@ Game.prototype.playerJoined = function (playerName) {
const players = []; const players = [];
for (let player in this.racks) { for (let player of Object.keys(this.racks)) {
if (Object.prototype.hasOwnProperty.call(this.racks, player)) { player = player.slice(1); // '#'
player = player.slice(1); // '#' players.push({
players.push({ player: player,
player: player, score: this.getPlayerScore(player),
score: this.getPlayerScore(player), rackCount: countTiles(this.getPlayerRack(player))
rackCount: countTiles(this.getPlayerRack(player)) });
});
}
} }
this.pendingEvents.push({players: players}); this.pendingEvents.push({players: players});
@ -315,7 +309,7 @@ Game.prototype.addListeningPlayer = function (playerName, responseAndType) {
let closed = false; let closed = false;
function close () { function close() {
if (closed) { if (closed) {
return; return;
} }
@ -481,7 +475,7 @@ function handleCommand(cmdNumber, message, response) {
gameNumber: gameNumber, gameNumber: gameNumber,
playerName: playerName, playerName: playerName,
boardLang: game.lang, boardLang: game.lang,
langName: langName, availableBoardLangs: availableBoardLangs,
currentPlayer: game.currentPlayer, currentPlayer: game.currentPlayer,
rack: game.getPlayerRack(playerName), rack: game.getPlayerRack(playerName),
board: game.board, board: game.board,
@ -672,7 +666,7 @@ function handleCommand(cmdNumber, message, response) {
} }
case "changeBoard": { case "changeBoard": {
game.lang = cmd.lang || defaultLang; game.lang = cmd.lang || DEFAULT_BOARD_LANG;
game.reset(); game.reset();
reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, letterValues: game.letterValues}); reply(message, response, cmdNumber, {error: 0, boardLang: game.lang, letterValues: game.letterValues});
break; break;
@ -707,16 +701,16 @@ function handleCommands(message, responseAndType) {
writeMessage(responseAndType, writeMessage(responseAndType,
JSON.stringify({ JSON.stringify({
playerName: message.playerName, playerName: message.playerName,
currentPlayer: game.currentPlayer, currentPlayer: game.currentPlayer,
gameNumber: gameNumber, gameNumber: gameNumber,
boardLang: game.lang, boardLang: game.lang,
langName: langName, availableBoardLangs: availableBoardLangs,
letterValues: game.letterValues, letterValues: game.letterValues,
rack: game.getPlayerRack(message.playerName), rack: game.getPlayerRack(message.playerName),
board: game.board, board: game.board,
remainingLetters: game.bag.length, remainingLetters: game.bag.length,
version: VERSION version: VERSION
}) })
); );
@ -949,9 +943,10 @@ function handleRequest(request, response) {
debuglog("Serving " + request.url); 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) { if (exists) {
fs.readFile("../public/" + request.url, function(err, contents) { fs.readFile(requestedPath, function (err, contents) {
if (err) { if (err) {
response.statusCode = 500; response.statusCode = 500;
response.setHeader("Content-Type", "text/plain; charset=utf-8"); response.setHeader("Content-Type", "text/plain; charset=utf-8");
@ -1025,10 +1020,8 @@ fs.readFile(GAMES_BACKUP, function (err, data) {
} else { } else {
const backup = JSON.parse(data); const backup = JSON.parse(data);
for (const gameNumber in backup) { for (const gameNumber of Object.keys(backup)) {
if (Object.prototype.hasOwnProperty.call(backup, gameNumber)) { games[gameNumber] = Game.fromJSON(backup[gameNumber]);
games[gameNumber] = Game.fromJSON(backup[gameNumber]);
}
} }
} }
} catch (e) { } catch (e) {
@ -1158,7 +1151,7 @@ fs.readFile(GAMES_BACKUP, function (err, data) {
console.error("An error happened in the HTTP server", error); 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); console.log("Server listening on: http://localhost:%s", port);
}); });
}); });