From 725da57f8e2983d744629b524f9084516a43cbac Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 20 Feb 2020 18:21:47 +0100 Subject: [PATCH] Modal to accept/refuse challenge + diagrams preview when creating challenge --- client/src/components/ContactForm.vue | 17 ++- client/src/components/Language.vue | 13 +-- client/src/components/UpsertUser.vue | 2 +- client/src/main.js | 2 +- client/src/translations/en.js | 3 + client/src/translations/es.js | 3 + client/src/translations/fr.js | 3 + client/src/views/Analyse.vue | 12 +- client/src/views/Game.vue | 52 +++++---- client/src/views/Hall.vue | 157 +++++++++++++++++++++----- 10 files changed, 189 insertions(+), 75 deletions(-) diff --git a/client/src/components/ContactForm.vue b/client/src/components/ContactForm.vue index 9046e0a7..cadbf73d 100644 --- a/client/src/components/ContactForm.vue +++ b/client/src/components/ContactForm.vue @@ -10,15 +10,14 @@ div ) .card label.modal-close(for="modalContact") - form(@submit.prevent="trySendMessage()" @keyup.enter="trySendMessage()") - fieldset - label(for="userEmail") {{ st.tr["Email"] }} - input#userEmail(type="email") - fieldset - label(for="mailSubject") {{ st.tr["Subject"] }} - input#mailSubject(type="text") - fieldset - textarea#mailContent(:placeholder="st.tr['Your message']") + fieldset + label(for="userEmail") {{ st.tr["Email"] }} + input#userEmail(type="email") + fieldset + label(for="mailSubject") {{ st.tr["Subject"] }} + input#mailSubject(type="text") + fieldset + textarea#mailContent(:placeholder="st.tr['Your message']") button(@click="trySendMessage()") {{ st.tr["Send"] }} #dialog.text-center {{ st.tr[infoMsg] }} </template> diff --git a/client/src/components/Language.vue b/client/src/components/Language.vue index 633f89dd..f49a592e 100644 --- a/client/src/components/Language.vue +++ b/client/src/components/Language.vue @@ -13,13 +13,12 @@ div ) .card label.modal-close(for="modalLang") - form(@change="setLanguage($event)") - fieldset - label(for="langSelect") {{ st.tr["Language"] }} - select#langSelect - each language,langCode in langName - option(value=langCode) - =language + fieldset(@change="setLanguage($event)") + label(for="langSelect") {{ st.tr["Language"] }} + select#langSelect + each language,langCode in langName + option(value=langCode) + =language </template> <script> diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue index eb8adffc..b5c6fbe4 100644 --- a/client/src/components/UpsertUser.vue +++ b/client/src/components/UpsertUser.vue @@ -11,7 +11,7 @@ div .card label.modal-close(for="modalUser") h3.section {{ st.tr[stage] }} - form(@submit.prevent="onSubmit()" @keyup.enter="onSubmit()") + div(@keyup.enter="onSubmit()") div(v-show="stage!='Login'") fieldset label(for="username") {{ st.tr["User name"] }} diff --git a/client/src/main.js b/client/src/main.js index db70c3e1..70b7d78a 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -18,7 +18,7 @@ new Vue({ if (e.code === "Escape") { let modalBoxes = document.querySelectorAll("[id^='modal']"); modalBoxes.forEach(m => { - if (m.checked && m.id != "modalWelcome") m.checked = false; + if (m.checked && m.id != "modalAccept") m.checked = false; }); } }); diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 70f8969d..ef9f291e 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -2,6 +2,7 @@ export const translations = { Abort: "Abort", About: "About", "Accept draw?": "Accept draw?", + "Accept challenge?": "Accept challenge?", All: "All", Analyse: "Analyse", "Any player": "Any player", @@ -81,6 +82,7 @@ export const translations = { "Processing... Please wait": "Processing... Please wait", Problems: "Problems", "participant(s):": "participant(s):", + Refuse: "Refuse", Register: "Register", "Registration complete! Please check your emails": "Registration complete! Please check your emails", "Remove game?": "Remove game?", @@ -112,6 +114,7 @@ export const translations = { "White win": "White win", "Who's there?": "Who's there?", With: "With", + with: "with", "Write news": "Write news", "Wrong time control": "Wrong time control", "Your message": "Your message", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 3c79b660..e51720a4 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -2,6 +2,7 @@ export const translations = { Abort: "Terminar", About: "Acerca de", "Accept draw?": "¿Acceptar tablas?", + "Accept challenge?": "¿Acceptar el desafÃo?", All: "Todos", Analyse: "Analizar", "Any player": "Cualquier jugador", @@ -81,6 +82,7 @@ export const translations = { "Processing... Please wait": "Procesando... por favor espere", Problems: "Problemas", "participant(s):": "participante(s):", + Refuse: "Rechazar", Register: "Registrarse", "Registration complete! Please check your emails": "¡Registro completo! Por favor revise sus correos electrónicos", "Remove game?": "¿Eliminar la partida?", @@ -112,6 +114,7 @@ export const translations = { "White win": "Las blancas gagnan", "Who's there?": "¿Quién está ahÃ?", With: "Con", + with: "con", "Write news": "Escribir una news", "Wrong time control": "Cadencia errónea", "Your message": "Tu mensaje", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 33503d30..d3533fcb 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -2,6 +2,7 @@ export const translations = { Abort: "Arrêter", About: "à propos", "Accept draw?": "Accepter la nulle ?", + "Accept challenge?": "Accepter le défi ?", All: "Tous", Analyse: "Analyser", "Any player": "N'importe qui", @@ -81,6 +82,7 @@ export const translations = { "Processing... Please wait": "Traitement en cours... Attendez SVP", Problems: "Problèmes", "participant(s):": "participant(s) :", + Refuse: "Refuser", Register: "S'enregistrer", "Registration complete! Please check your emails": "Enregistrement terminé ! Allez voir vos emails", "Remove game?": "Supprimer la partie ?", @@ -112,6 +114,7 @@ export const translations = { "White win": "Les blancs gagnent", "Who's there?": "Qui est là ?", With: "Avec", + with: "avec", "Write news": "Ãcrire une news", "Wrong time control": "Cadence erronée", "Your message": "Votre message", diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index d1c6d593..23515c67 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -5,9 +5,8 @@ main .text-center input#fen( v-model="curFen" - @input="adjustFenSize()" + @input="adjustFenSize(); tryGotoFen()" ) - button(@click="gotoFen()") {{ st.tr["Go"] }} BaseGame( :game="game" :vr="vr" @@ -73,9 +72,12 @@ export default { let fenInput = document.getElementById("fen"); fenInput.style.width = this.curFen.length + "ch"; }, - gotoFen: function() { - this.gameRef.fen = this.curFen; - this.loadGame(); + tryGotoFen: function() { + if (V.IsGoodFen(this.curFen)) + { + this.gameRef.fen = this.curFen; + this.loadGame(); + } } } }; diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 90a9a9fa..1294d50d 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -513,6 +513,29 @@ export default { } } } + this.repeat = {}; //reset: scan past moves' FEN: + let repIdx = 0; + // NOTE: vr_tmp to obtain FEN strings is redundant with BaseGame + let vr_tmp = new V(game.fenStart); + let movesCount = -1; + let curTurn = "n"; + game.moves.forEach(m => { + if (vr_tmp.turn != curTurn) + { + movesCount++; + curTurn = vr_tmp.turn; + } + vr_tmp.play(m); + const fenObj = V.ParseFen(vr_tmp.getFen()); + repIdx = fenObj.position + "_" + fenObj.turn; + if (fenObj.flags) repIdx += "_" + fenObj.flags; + this.repeat[repIdx] = this.repeat[repIdx] + ? this.repeat[repIdx] + 1 + : 1; + }); + if (vr_tmp.turn != curTurn) + movesCount++; + if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; this.game = Object.assign( {}, game, @@ -524,7 +547,8 @@ export default { // opponent sid not strictly required (or available), but easier // at least oppsid or oppid is available anyway: oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, - oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].uid + oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].uid, + movesCount: movesCount, } ); this.re_setClocks(); @@ -533,20 +557,6 @@ export default { // Did lastate arrive before game was rendered? if (this.lastate) this.processLastate(); }); - this.repeat = {}; //reset: scan past moves' FEN: - let repIdx = 0; - // NOTE: vr_tmp to obtain FEN strings is redundant with BaseGame - let vr_tmp = new V(game.fenStart); - game.moves.forEach(m => { - vr_tmp.play(m); - const fenObj = V.ParseFen(vr_tmp.getFen()); - repIdx = fenObj.position + "_" + fenObj.turn; - if (fenObj.flags) repIdx += "_" + fenObj.flags; - this.repeat[repIdx] = this.repeat[repIdx] - ? this.repeat[repIdx] + 1 - : 1; - }); - if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; if (callback) callback(); }; if (game) { @@ -563,7 +573,7 @@ export default { } }, re_setClocks: function() { - if (this.game.moves.length < 2 || this.game.score != "*") { + if (this.game.movesCount < 2 || this.game.score != "*") { // 1st move not completed yet, or game over: freeze time this.virtualClocks = this.game.clocks.map(s => ppt(s)); return; @@ -632,10 +642,9 @@ export default { let addTime = 0; if (move.color == this.game.mycolor) { if (this.drawOffer == "received") - //I refuse draw + // I refuse draw this.drawOffer = ""; - if (this.game.moves.length >= 2) { - //after first move + if (this.game.movesCount >= 2) { const elapsed = Date.now() - this.game.initime[colorIdx]; // elapsed time is measured in milliseconds addTime = this.game.increment - elapsed / 1000; @@ -649,13 +658,14 @@ export default { move.addTime = addTime; } else addTime = move.addTime; //supposed transmitted // Update current game object: + if (nextIdx != colorIdx) + this.game.movesCount++; this.game.moves.push(move); this.game.fen = move.fen; this.game.clocks[colorIdx] += addTime; // move.initime is set only when I receive a "lastate" move from opponent this.game.initime[nextIdx] = move.initime || Date.now(); - //if (colorIdx != nextIdx) - this.re_setClocks(); + this.re_setClocks(); // If repetition detected, consider that a draw offer was received: const fenObj = V.ParseFen(move.fen); let repIdx = fenObj.position + "_" + fenObj.turn; diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 82bd769b..a39cd548 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -8,6 +8,17 @@ main .card.text-center label.modal-close(for="modalInfo") p(v-html="infoMessage") + input#modalAccept.modal(type="checkbox") + div#acceptDiv(role="dialog") + .card.text-center + p + span.variantName {{ curChallToAccept.vname }} + span {{ curChallToAccept.cadence }} + span {{ st.tr["with"] + " " + curChallToAccept.from.name }} + .diagram(v-html="tchallDiag") + .button-group#buttonsTchall + button.acceptBtn(@click="decisionChallenge(true)") {{ st.tr["Accept challenge?"] }} + button.refuseBtn(@click="decisionChallenge(false)") {{ st.tr["Refuse"] }} input#modalNewgame.modal(type="checkbox") div#newgameDiv( role="dialog" @@ -15,10 +26,13 @@ main ) .card label#closeNewgame.modal-close(for="modalNewgame") - form(@submit.prevent="newChallenge()" @keyup.enter="newChallenge()") + div(@keyup.enter="newChallenge()") fieldset label(for="selectVariant") {{ st.tr["Variant"] }} * - select#selectVariant(v-model="newchallenge.vid") + select#selectVariant( + @change="loadNewchallVariant(trySetNewchallDiag)" + v-model="newchallenge.vid" + ) option( v-for="v in st.variants" :value="v.id" @@ -43,11 +57,13 @@ main v-model="newchallenge.to" ) fieldset(v-if="st.user.id > 0 && newchallenge.to.length > 0") - label(for="inputFen") FEN input#inputFen( + placeholder="FEN" + @input="trySetNewchallDiag()" type="text" v-model="newchallenge.fen" ) + .diagram(v-html="newchallenge.diag") button(@click="newChallenge()") {{ st.tr["Send challenge"] }} input#modalPeople.modal( type="checkbox" @@ -131,6 +147,7 @@ import { ArrayFun } from "@/utils/array"; import { ajax } from "@/utils/ajax"; import params from "@/parameters"; import { getRandString, shuffle } from "@/utils/alea"; +import { getDiagram } from "@/utils/printDiagram"; import Chat from "@/components/Chat.vue"; import GameList from "@/components/GameList.vue"; import ChallengeList from "@/components/ChallengeList.vue"; @@ -154,10 +171,17 @@ export default { infoMessage: "", newchallenge: { fen: "", - vid: localStorage.getItem("vid") || "", + vid: parseInt(localStorage.getItem("vid")) || 0, to: "", //name of challenged player (if any) - cadence: localStorage.getItem("cadence") || "" + cadence: localStorage.getItem("cadence") || "", + // VariantRules object, stored to not interfere with + // diagrams of targetted challenges: + V: null, + vname: "", + diag: "" //visualizing FEN }, + tchallDiag: "", + curChallToAccept: {from: {}}, newChat: "", conn: null, connexionString: "", @@ -173,6 +197,8 @@ export default { this.challenges.concat(this.games).forEach(o => { if (o.vname == "") o.vname = this.getVname(o.vid); }); + if (!this.newchallenge.V && this.newchallenge.vid > 0) + this.loadNewchallVariant(); } }, computed: { @@ -185,6 +211,8 @@ export default { } }, created: function() { + if (this.st.variants.length > 0 && this.newchallenge.vid > 0) + this.loadNewchallVariant(); const my = this.st.user; this.$set(this.people, my.sid, { id: my.id, name: my.name, pages: ["/"] }); // Ask server for current corr games (all but mines) @@ -630,6 +658,33 @@ export default { this.conn.addEventListener("close", this.socketCloseListener); }, // Challenge lifecycle: + loadNewchallVariant: async function(cb) { + const vname = this.getVname(this.newchallenge.vid); + const vModule = await import("@/variants/" + vname + ".js"); + this.newchallenge.V = vModule.VariantRules; + this.newchallenge.vname = vname; + if (cb) + cb(); + }, + trySetNewchallDiag: function() { + if (!this.newchallenge.fen) { + this.newchallenge.diag = ""; + return; + } + // If vid > 0 then the variant is loaded (function above): + window.V = this.newchallenge.V; + if ( + this.newchallenge.vid > 0 && + this.newchallenge.fen && + V.IsGoodFen(this.newchallenge.fen) + ) { + const parsedFen = V.ParseFen(this.newchallenge.fen); + this.newchallenge.diag = getDiagram({ + position: parsedFen.position, + orientation: V.GetOppCol(parsedFen.turn) + }); + } + }, newChallenge: async function() { if (this.newchallenge.cadence.match(/^[0-9]+$/)) this.newchallenge.cadence += "+0"; //assume minutes, no increment @@ -653,9 +708,7 @@ export default { alert(error); return; } - const vname = this.getVname(this.newchallenge.vid); - const vModule = await import("@/variants/" + vname + ".js"); - window.V = vModule.VariantRules; + window.V = this.newchallenge.V; error = checkChallenge(this.newchallenge); if (error) { alert(error); @@ -684,7 +737,7 @@ export default { }); // Add new challenge: chall.from = { - //decompose to avoid revealing email + // Decompose to avoid revealing email sid: this.st.user.sid, id: this.st.user.id, name: this.st.user.name @@ -692,7 +745,7 @@ export default { chall.added = Date.now(); // NOTE: vname and type are redundant (can be deduced from cadence + vid) chall.type = ctype; - chall.vname = vname; + chall.vname = this.newchallenge.vname; this.challenges.push(chall); // Remember cadence + vid for quicker further challenges: localStorage.setItem("cadence", chall.cadence); @@ -709,7 +762,28 @@ export default { }); } }, - clickChallenge: function(c) { + // Callback function after a diagram was showed to accept + // or refuse targetted challenge: + decisionChallenge: function(accepted) { + this.curChallToAccept.accepted = accepted; + this.finishProcessingChallenge(this.curChallToAccept); + document.getElementById("modalAccept").checked = false; + }, + finishProcessingChallenge: function(c) { + if (c.accepted) { + c.seat = { + // Again, avoid c.seat = st.user to not reveal email + sid: this.st.user.sid, + id: this.st.user.id, + name: this.st.user.name + }; + this.launchGame(c); + } else { + this.send("refusechallenge", { data: c.id, target: c.from.sid }); + } + this.send("deletechallenge", { data: c.id }); + }, + clickChallenge: async function(c) { const myChallenge = c.from.sid == this.st.user.sid || //live (this.st.user.id > 0 && c.from.id == this.st.user.id); //corr @@ -719,25 +793,30 @@ export default { return; } c.accepted = true; + const vModule = await import("@/variants/" + c.vname + ".js"); + window.V = vModule.VariantRules; if (c.to) { - //c.to == this.st.user.name (connected) - // TODO: if special FEN, show diagram after loading variant - c.accepted = confirm("Accept challenge?"); - } - if (c.accepted) { - c.seat = { - //again, avoid c.seat = st.user to not reveal email - sid: this.st.user.sid, - id: this.st.user.id, - name: this.st.user.name - }; - this.launchGame(c); - } else { - this.send("refusechallenge", { data: c.id, target: c.from.sid }); + // c.to == this.st.user.name (connected) + if (c.fen) { + const parsedFen = V.ParseFen(c.fen); + this.tchallDiag = getDiagram({ + position: parsedFen.position, + orientation: V.GetOppCol(parsedFen.turn) + }); + this.curChallToAccept = c; + document.getElementById("modalAccept").checked = true; + } + else { + if (!confirm(this.st.tr["Accept challenge?"])) + c.accepted = false; + this.finishProcessingChallenge(c); + } } - this.send("deletechallenge", { data: c.id }); - } //my challenge + else + this.finishProcessingChallenge(c); + } else { + // My challenge if (c.type == "corr") { ajax("/challenges", "DELETE", { id: c.id }); } @@ -747,9 +826,7 @@ export default { ArrayFun.remove(this.challenges, ch => ch.id == c.id); }, // NOTE: when launching game, the challenge is already being deleted - launchGame: async function(c) { - const vModule = await import("@/variants/" + c.vname + ".js"); - window.V = vModule.VariantRules; + launchGame: function(c) { // These game informations will be shared let gameInfo = { id: getRandString(), @@ -822,7 +899,7 @@ export default { padding: 15px 0 max-width: 430px -#newgameDiv > .card +#newgameDiv > .card, #acceptDiv > .card max-width: 767px max-height: 100% @@ -875,6 +952,24 @@ button.player-action .tabbtn background-color: #f9faee +button.acceptBtn + background-color: lightgreen +button.refuseBtn + background-color: red + +#buttonsTchall + margin-top: 10px + +.variantName + font-weight: bold + +.diagram + margin: 0 auto + max-width: 400px + +#inputFen + width: 100% + #div2, #div3 margin-top: 15px @media screen and (max-width: 767px) -- 2.44.0