Merge branch 'develop' into laser_pointer
authorLaurent Mazet <mazet@softndesign.org>
Sun, 30 Aug 2020 19:24:02 +0000 (21:24 +0200)
committerLaurent Mazet <mazet@softndesign.org>
Sun, 30 Aug 2020 19:24:02 +0000 (21:24 +0200)
1  2 
l10n/po/fr/trivabble.po
l10n/pot/trivabble.pot
public/index.html
public/trivabble.css
public/trivabble.js

index 9abb6d6970f7c4575750fcf986b52526889f6769,752e8bb3ce0ac8b8d433c95f4e115c96adcf4c29..d990adffaab0d8e6b7f50a337f5cf52345f05488
@@@ -166,26 -166,32 +166,53 @@@ msgstr "Vous avez changé la langue du 
  msgid  "{0} changed the language of the board to {1}"
  msgstr "{0} a changé la langue du plateau en {1}"
  
- msgid  "Configuration"
- msgstr "Options de configuration"
+ msgid  "Check spelling for new words"
+ msgstr "Vérifier l'orthographe des mots nouveaux"
+ 
+ msgid  "Incorrect spelling for {0} from {1}"
+ msgstr "Orthographe incorrecte pour {0} parmi {1}"
+ 
+ msgid  "All words are correct: {0}"
+ msgstr "Tous les mots sont corrects : {0}"
+ 
+ msgid  "No new word found"
+ msgstr "Aucun nouveau mot trouvé"
+ 
+ msgid  "Incorrect dictionary file."
+ msgstr "Dictionnaire incorrect."
+ 
+ msgid  "Can't load dictionary file."
+ msgstr "Impossible de charger le dictionnaire"
+ 
+ msgid  "Enable spell checker"
+ msgstr "Activer le vérificateur orthographique"
+ 
+ msgid  "Spell checking requires Trivabble to download a dictionary. Do you confirm?"
+ msgstr "La vérification orthographique nécessite que Trivabble télécharge un dictionnaire. Confirmez-vous ?"
+ 
+ msgid  "Spell checking is based on:"
+ msgstr "La vérification orthographique est basée sur :"
+ 
+ msgid  "Settings"
 -msgstr "Paramètres"
++msgstr "Paramètres"
 +
 +msgid  "Configuration window"
 +msgstr "Fenêtre de configuration"
 +
 +msgid  "Look at:"
 +msgstr "Regardez ici :"
 +
 +msgid  "Double tap duration"
 +msgstr "Durée de la double tape"
 +
 +msgid  "(slow)"
 +msgstr "(lent)"
 +
 +msgid  "(fast)"
 +msgstr "(rapide)"
 +
 +msgid  "Flash light duration"
 +msgstr "Durée du flash laser"
 +
 +msgid  "Flash light color"
 +msgstr "Couleur du flash laser"
index 69695299f2424fa79a35871a15d4a0430ea2c539,e8300029947387ecbf87b95fc9d13bd8744c39e5..4904eb36e3fd1d72d4b2dd785a76dcde8c5096bc
@@@ -172,25 -172,32 +172,52 @@@ msgstr "
  msgid  "{0} changed the language of the board to {1}"
  msgstr ""
  
- msgid  "Configuration"
+ msgid  "Check spelling for new words"
+ msgstr ""
+ 
+ msgid  "Incorrect spelling for {0} from {1}"
+ msgstr ""
+ 
+ msgid  "All words are correct: {0}"
+ msgstr ""
+ 
+ msgid  "No new word found"
+ msgstr ""
+ 
+ msgid  "Incorrect dictionary file."
+ msgstr ""
+ 
+ msgid  "Can't load dictionary file."
+ msgstr ""
+ 
+ msgid  "Enable spell checker"
+ msgstr ""
+ 
+ msgid  "Spell checking requires Trivabble to download a dictionary. Do you confirm?"
+ msgstr ""
+ 
+ msgid  "Spell checking is based on:"
+ msgstr ""
+ 
+ msgid  "Settings"
 -msgstr ""
 +msgstr ""
 +
 +msgid  "Configuration window"
 +
 +msgid  "Look at:"
 +msgstr ""
 +
 +msgid  "Double tap duration"
 +msgstr ""
 +
 +msgid  "(slow)"
 +msgstr ""
 +
 +msgid  "(fast)"
 +msgstr ""
 +
 +msgid  "Flash light duration"
 +msgstr ""
 +
 +msgid  "Flash light color"
 +msgstr ""
index 721a741cb6fdcb81cc11a5cd2dde1463ad0d4555,a8096464bdce8a5f492c3b849f366c087e4e6f28..d97ccec761cb1b9595bac7a677f4c4e712be5220
              </div>
          </div>
  
-         <div class="modal" id="modal-config-dialog">
+         <div class="modal" id="modal-settings-dialog">
              <div class="modal-content">
                  <h2>
-                     <span data-l10n="text-content">Configuration window</span>
-                     <span class="modal-button" id="modal-close-btn">&#215;</span>
-               </h2>
+                     <span data-l10n="text-content">Settings</span>
+                     <span class="modal-button" id="btn-settings-close">&#215;</span>
+                       </h2>
 -                <div id="prefs-sound">
 +                <div id="prefs">
                      <p><label><input type="checkbox" id="tiles-sound" checked="checked" /><span data-l10n="text-content">Sound of the tiles</span></label></p>
                      <p><label><input type="checkbox" id="msg-sound" checked="checked" /><span data-l10n="text-content">Sound of messages</span></label></p>
-                 </div>
-               <hr/>
-               <div id="prefs">
-                   <p><label>
-                 <span data-l10n="text-content">(fast)</span><input type="range" min="0" max="0" id="double-tap-duration"/><span data-l10n="text-content">(slow)</span>
-                 <br/>
-                 <span data-l10n="text-content">Double tap duration</span>
-             </label></p>
-                   <p><label>
-                 <span data-l10n="text-content">(fast)</span><input type="range" min="0" max="0" id="flash-light-duration"/><span data-l10n="text-content">(slow)</span>
-                 <br/>
-                 <span data-l10n="text-content">Flash light duration</span>
-             </label></p>
-             <p><label>
-                 <input type="text" size="6" id="flash-light-color"/>
-                 <span data-l10n="text-content">Flash light color</span>
-             </label></p>
-               </div>
+                     <p id="enable-spell-checker-outer" style="display:none"><label><input type="checkbox" id="enable-spell-checker" /> <span data-l10n="text-content">Enable spell checker</span></label></p>
 -                </div>
++                          <p><label><span data-l10n="text-content">(fast)</span><input type="range" min="0" max="0" id="double-tap-duration"/><span data-l10n="text-content">(slow)</span><br/><span data-l10n="text-content">Double tap duration</span></label></p>
++                          <p><label><span data-l10n="text-content">(fast)</span><input type="range" min="0" max="0" id="flash-light-duration"/><span data-l10n="text-content">(slow)</span><br/><span data-l10n="text-content">Flash light duration</span></label></p>
++                    <p><label><input type="text" size="6" id="flash-light-color"/> <span data-l10n="text-content">Flash light color</span></label></p>
++                      </div>
              </div>
          </div>
-         
-       <div id="content">
+ 
 -      <div id="content">
++      <div id="content">
              <table id="board">
                  <tr><td class="corner"></td></tr>
              </table>
index 30423162bbd25539f298227cfdb5f281b1d83166,50ab0eed405824dd2eb42ec42dcc8ef8a5d61c1d..ed0b970599ccb92692b6fb6a2c4312c606af458d
@@@ -203,6 -212,6 +212,10 @@@ button[disabled=disabled], button:disab
      border: 1px solid black;
  }
  
++.highlight {
++    background-color: #E3E;
++}
++
  @keyframes highlight {
      0% {
          background:red;
@@@ -511,3 -523,30 +527,30 @@@ td.blink 
      transform: scale(1.0);
      transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s;
  }
 -}
+ 
+ .alert {
+     z-index: 2;
+ }
+ 
+ #btn-settings {
+     background:lightgray;
+     color:black;
+     font-size:small;
+     border-radius:3px;
+     border:none;
+     margin:0 0 0 auto;
+     min-height: 2em;
+ }
+ 
+ #btn-settings:before {
+     content: "⚙️ "
+ }
+ 
+ #info-spell-checking {
+     font-size:small;
+     color:gray;
+ }
+ 
+ #info-spell-checking a {
+     color:black;
++}
index 2ffcc8151a6bc2eaa60761238df26884047a4bf0,aa525183d655dcf7e3f80451e0390bdd10f67738..eee36ad958eff3cc114a7f12fcd05d1bbbd5f7a4
      setConf("ENABLE_EVENT_SOURCE",  true);
      setConf("MAX_WEBSOCKET_ERRORS", 1);
      setConf("APP_PATH",             "");
+     setConf("ENABLE_SPELL_CHECKER", false);
 +    setConf("DOUBLE_TAP_DURATIONS", [650, 1100, 1800, 3000, 5000]);
 +    setConf("FLASH_LIGHT_DURATIONS", [800, 1600, 3200]);
 +    setConf("FLASH_LIGHT_COLOR", "#E3E");
  
      const _ = (window.libD && libD.l10n) ? libD.l10n() : function (s) {
          return s;
                              ? format(_("You changed the language of the board to {0}"), newLang)
                              : format(_("{0} changed the language of the board to {1}"), msg.sender, newLang)
                      );
- 
-                     return;
                  }
 +
 +                case "highlightTile": {
 +                    localHighlightTile(msg.specialMsg.tile);
 +                    infoMessage(msg.msg);
 +                }
              }
          }
  
  
      }
  
+     function getLetterFromBoard(row, col) {
+         if ((row < 0) || (row >= 15) || (col < 0) || (col >= 15)) {
+             return "";
+         }
+         try {
+             const index = (row * 15) + col;
+             const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
+             const tile = tilePlaceholder.getElementsByClassName("tile")[0];
+             return tile.firstChild.textContent;
+         } catch (e) {
+             return "";
+         }
+     }
+ 
+     function searchWordInLine(row, col, incRow, incCol) {
+         const initLetter = getLetterFromBoard(row, col);
+ 
+         /* Look for first letter in line */
+         while (getLetterFromBoard(row - incRow, col - incCol) !== "") {
+             row -= incRow;
+             col -= incCol;
+         }
+ 
+         /* Look for last letter in line */
+         const word = {letters: "", row: row, col: col, incRow: incRow, incCol: incCol};
+         let letter;
+         while ((letter = getLetterFromBoard(row, col)) !== "") {
+             word.letters += letter;
+             row += incRow;
+             col += incCol;
+         }
+         return (word.letters === initLetter) ? null : word;
+     }
+ 
+     function isNewWord(newWord, words) {
+         if (newWord === null) {
+             return false;
+         }
+         for (const k of Object.keys(words)) {
+             if ((words[k].letters === newWord.letters) &&
+                 (words[k].row === newWord.row) &&
+                 (words[k].col === newWord.col) &&
+                 (words[k].incRow === newWord.incRow) &&
+                 (words[k].incCol === newWord.incCol)) {
+                 return false;
+             }
+         }
+         return true;
+     }
+ 
+     function searchNewWords() {
+ 
+         const words = [];
+         for (const i of Object.keys(currentTilePlayed)) {
+ 
+             /* Get board position */
+             const row = Math.floor(i / 15);
+             const col = i % 15;
+ 
+             /* Look for word in column */
+             const newWordInCol = searchWordInLine(row, col, 1, 0);
+             if (isNewWord(newWordInCol, words)) {
+                 words.push(newWordInCol);
+             }
+ 
+             /* Look for word in row */
+             const newWordInRow = searchWordInLine(row, col, 0, 1);
+             if (isNewWord(newWordInRow, words)) {
+                 words.push(newWordInRow);
+             }
+ 
+             /* Should we look for word in diag? */
+ 
+             /* If nothing found, it's a one letter word */
+             if ((newWordInCol === null) && (newWordInRow === null)) {
+                 words.push({letters: getLetterFromBoard(row, col), row: row, col: col, incRow: 0, incCol: 0});
+             }
+         }
+ 
+         return words;
+     }
+ 
+     function modifyTileClass(word, value, action) {
+ 
+         /* action: true for add, false for remove */
+ 
+         if (action) {
+ 
+             /* only for new words */
+             for (let l = 0; l < word.letters.length; l++) {
+                 const index = ((word.row + (l * word.incRow)) * 15) + word.col + (l * word.incCol);
+                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
+                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
+ 
+                 tile.classList.add(value);
+             }
+ 
+         } else {
+ 
+             /* tiles on board */
+             for (let index = 0; index < 15 * 15; index++) {
+                 const tilePlaceholder = boardCells[index].getElementsByClassName("tile-placeholder")[0];
+                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
+                 if (typeof tile !== "undefined") {
+                     tile.classList.remove(value);
+                 }
+             }
+ 
+             /* tiles on rack */
+             for (const tilePlaceholder of rack.getElementsByClassName("tile-placeholder")) {
+                 const tile = tilePlaceholder.getElementsByClassName("tile")[0];
+                 if (typeof tile !== "undefined") {
+                     tile.classList.remove(value);
+                 }
+             }
+ 
+             /* tile on the move */
+             if (movingTile !== null) {
+                 movingTile.classList.remove(value);
+             }
+         }
+     }
+ 
+     function checkSpellingClicked() {
+         if (localStorage.spellCheckerEnabled === "false") {
+             return;
+         }
+ 
+         getDictionary(localStorage.trivabbleBoardLang, checkSpelling);
+     }
+ 
+     function checkSpelling(dictionary) {
+         const newWords = searchNewWords();
+         if (newWords.length === 0) {
+             myAlert(_("No new word found"));
+             return;
+         }
+ 
+         /* Check if words are in dictionary */
+         let words = [];
+         let badWords = [];
+         for (const k of Object.keys(newWords)) {
+             const currentWord = newWords[k].letters.replace(/ /gu, "?");
+             if (words.indexOf(currentWord) === -1) {
+                 words.push(currentWord);
+             }
+             modifyTileClass(newWords[k], "tile-spelling-ok", true);
+             const pattern = "^" + newWords[k].letters.replace(/ /gu, ".") + "$";
+             const find = dictionary.find(function (word) {
+                 return word.match(pattern);
+             });
+             if (!find) {
+                 if (badWords.indexOf(currentWord) === -1) {
+                     badWords.push(currentWord);
+                 }
+                 modifyTileClass(newWords[k], "tile-spelling-nok", true);
+             }
+         }
+         setTimeout(
+             function () {
+                 modifyTileClass(null, "tile-spelling-ok", false);
+                 modifyTileClass(null, "tile-spelling-nok", false);
+             }, 5000);
+         words = words.join(", ").trim();
+         badWords = badWords.join(", ").trim();
+ 
+         /* Advice on erroneous spelling */
+         if (badWords.length > 0) {
+             const msg = chatMessage("", format(_("Incorrect spelling for {0} from {1}"), badWords, words));
+             msg.classList.add("error");
+         } else {
+             chatMessage("", format(_("All words are correct: {0}"), words));
+         }
+     }
+ 
 +    function highlightTile(e) {
 +        let id = this.id;
 +        if (id === "") {
 +            return;
 +        } else if (id === "center-cell") {
 +            id = 112;
 +        }
 +        if (e.type === "touchstart") {
 +            if ((highlightTile.id !== id) || (Date.now() - highlightTile.date > localStorage.doubleTapDuration)) {
 +                highlightTile.id = id;
 +                highlightTile.date = Date.now();
 +                return;
 +            }
 +        }
 +
 +        if (e.type === "dblclick") {
 +            const tilePlaceholder = boardCells[id].getElementsByClassName("tile-placeholder")[0];
 +            if (tilePlaceholder.getElementsByClassName("tile")[0]) {
 +                return;
 +            }
 +        }
 +
 +        const letters = "ABCDEFGHIJKLMNO";
 +        const col = (id % 15) + 1;
 +        const row = letters[Math.floor(id / 15)];
 +        sendCmds([{
 +            cmd: "msg",
 +            msg: _("Look at:") + " " + row + col,
 +            specialMsg: {
 +                type: "highlightTile",
 +                tile: id
 +            }
 +        }]);
 +    }
 +
 +    function localHighlightTile(id) {
 +        const placeholder = boardCells[id].getElementsByClassName("tile-placeholder")[0];
 +        placeholder.classList.add("highlight");
 +        setTimeout(
 +            function (x) {
 +                x.classList.remove("highlight");
 +            }, Number(localStorage.flashLightDuration) * 1 / 3, placeholder);
 +        setTimeout(
 +            function (x) {
 +                x.classList.add("highlight");
 +            }, Number(localStorage.flashLightDuration) * 1 / 2, placeholder);
 +        setTimeout(
 +            function (x) {
 +                x.classList.remove("highlight");
 +            }, Number(localStorage.flashLightDuration) * 2 / 3, placeholder);
 +        setTimeout(
 +            function (x) {
 +                x.classList.add("highlight");
 +            }, Number(localStorage.flashLightDuration) * 5 / 6, placeholder);
 +        setTimeout(
 +            function (x) {
 +                x.classList.remove("highlight");
 +            }, Number(localStorage.flashLightDuration) * 1, placeholder);
 +    }
 +
      function initChat() {
          const btn = document.getElementById("chat-btn");
  
          }
      }
  
+     function initSpellChecker() {
+         if (Conf.ENABLE_SPELL_CHECKER) {
+             document.getElementById("enable-spell-checker-outer").style.display = "";
+         }
+ 
+         document.getElementById("enable-spell-checker").onclick = spellCheckerSettingChecked;
+ 
+         if (!Conf.ENABLE_SPELL_CHECKER || localStorage.spellCheckerEnabled === "false") {
+             document.getElementById("check-spelling").style.display = "none";
+             document.getElementById("enable-spell-checker").checked = false;
+             document.getElementById("info-spell-checking").style.display = "none";
+         } else {
+             document.getElementById("check-spelling").style.display = "";
+             document.getElementById("info-spell-checking").style.display = "";
+             document.getElementById("enable-spell-checker").checked = true;
+         }
+     }
+ 
 +    function translateDuration(x, values) {
 +        if (x < 0) {
 +            return null;
 +        } else if (x < values.length) {
 +            return values[Math.floor(x)];
 +        } else {
 +            for (let i = 0; i < values.length; i++) {
 +                if (x <= values[i]) {
 +                    return i;
 +                }
 +            }
 +            return values.length - 1;
 +        }
 +    }
 +
 +    function initLaserPointer() {
 +        if (!Object.prototype.hasOwnProperty.call(localStorage, "doubleTapDuration")) {
 +            localStorage.doubleTapDuration = Conf.DOUBLE_TAP_DURATIONS[Math.floor(Conf.DOUBLE_TAP_DURATIONS.length / 2)];
 +        }
 +        const doubleTapDuration = document.getElementById("double-tap-duration");
 +        if (doubleTapDuration) {
 +            doubleTapDuration.max = Conf.DOUBLE_TAP_DURATIONS.length - 1;
 +            doubleTapDuration.value = translateDuration(localStorage.doubleTapDuration, Conf.DOUBLE_TAP_DURATIONS);
 +            doubleTapDuration.onchange = function () {
 +                localStorage.doubleTapDuration =
 +                    translateDuration(document.getElementById("double-tap-duration").value, Conf.DOUBLE_TAP_DURATIONS);
 +            };
 +        }
 +
 +        if (!Object.prototype.hasOwnProperty.call(localStorage, "flashLightDuration")) {
 +            localStorage.flashLightDuration = Conf.FLASH_LIGHT_DURATIONS[Math.floor(Conf.FLASH_LIGHT_DURATIONS.length / 2)];
 +        }
 +        const flashLightDuration = document.getElementById("flash-light-duration");
 +        if (flashLightDuration) {
 +            flashLightDuration.max = Conf.FLASH_LIGHT_DURATIONS.length - 1;
 +            flashLightDuration.value = translateDuration(localStorage.flashLightDuration, Conf.FLASH_LIGHT_DURATIONS);
 +            flashLightDuration.onchange = function () {
 +                localStorage.flashLightDuration =
 +                    translateDuration(document.getElementById("flash-light-duration").value, Conf.FLASH_LIGHT_DURATIONS);
 +            };
 +        }
 +
 +        if (!Object.prototype.hasOwnProperty.call(localStorage, "flashLightColor")) {
 +            localStorage.flashLightColor = Conf.FLASH_LIGHT_COLOR;
 +        }
 +        const flashLightColor = document.getElementById("flash-light-color");
 +        if (flashLightColor) {
 +            flashLightColor.value = localStorage.flashLightColor;
 +            flashLightColor.onchange = function () {
 +                localStorage.flashLightColor =
 +                    document.getElementById("flash-light-color").value;
 +                const style = document.createElement("style");
 +                style.innerHTML = ".highlight {background-color: " + localStorage.flashLightColor + ";}";
 +                document.body.appendChild(style);
 +            };
 +            flashLightColor.onchange();
 +        }
 +    }
 +
      function repromptName(f) {
          if (localStorage.trivabblePlayerName && localStorage.trivabblePlayerName.trim()) {
              f();
          location.reload();
      }
  
-     function showConfig() {
-         const modal = document.querySelector(".modal");
-         modal.classList.toggle("show-modal");
+     function showSettings() {
 -      const modal = document.querySelector(".modal");
 -      modal.classList.toggle("show-modal");
++          const modal = document.querySelector(".modal");
++          modal.classList.toggle("show-modal");
      }
  
      function initGlobals() {
          initChat();
          initGame();
          initSound();
+         initSpellChecker();
 +        initLaserPointer();
      };
  
      trivabble.l10nError = trivabble.run;