From: Laurent Mazet Date: Sun, 30 Aug 2020 19:24:02 +0000 (+0200) Subject: Merge branch 'develop' into laser_pointer X-Git-Url: https://secure.softndesign.org/git/?a=commitdiff_plain;h=344bae18a2b3275782bf57582eb4b553850c7f6d;p=trivabble.git Merge branch 'develop' into laser_pointer --- 344bae18a2b3275782bf57582eb4b553850c7f6d diff --cc l10n/po/fr/trivabble.po index 9abb6d6,752e8bb..d990adf --- a/l10n/po/fr/trivabble.po +++ b/l10n/po/fr/trivabble.po @@@ -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" diff --cc l10n/pot/trivabble.pot index 6969529,e830002..4904eb3 --- a/l10n/pot/trivabble.pot +++ b/l10n/pot/trivabble.pot @@@ -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 "" diff --cc public/index.html index 721a741,a809646..d97ccec --- a/public/index.html +++ b/public/index.html @@@ -44,37 -44,21 +44,24 @@@ - - -
+ -
++
diff --cc public/trivabble.css index 3042316,50ab0ee..ed0b970 --- a/public/trivabble.css +++ b/public/trivabble.css @@@ -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; -} ++} diff --cc public/trivabble.js index 2ffcc81,aa52518..eee36ad --- a/public/trivabble.js +++ b/public/trivabble.js @@@ -42,9 -42,7 +42,10 @@@ 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; @@@ -683,14 -750,7 +753,12 @@@ ? 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); + } } } @@@ -1443,66 -1507,181 +1515,241 @@@ } + 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"); @@@ -1583,65 -1762,24 +1830,83 @@@ } } + 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(); @@@ -1707,9 -1845,9 +1972,9 @@@ 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() { @@@ -1861,7 -1997,7 +2127,8 @@@ initChat(); initGame(); initSound(); + initSpellChecker(); + initLaserPointer(); }; trivabble.l10nError = trivabble.run;