X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FHall.vue;h=1031e49c75b2cc07c806f52d5ba9e319a3df6172;hb=efdfb4c70f4a4391b8571726d924cdf58baba41c;hp=6cc58e978f5c301d2e289508931bd66e5a9d11b7;hpb=8477e53d8e78606e4c4e4bf91c77b1011aab583c;p=vchess.git diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 6cc58e97..1031e49c 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -8,17 +8,34 @@ main .card.text-center label.modal-close(for="modalInfo") p(v-html="infoMessage") - input#modalNewgame.modal(type="checkbox") + 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" + @change="cadenceFocusIfOpened($event)" + ) div#newgameDiv( role="dialog" data-checkbox="modalNewgame" ) .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" @@ -28,14 +45,21 @@ main fieldset label(for="cadence") {{ st.tr["Cadence"] }} * div#predefinedCadences - button(type="button") 3+2 - button(type="button") 5+3 button(type="button") 15+5 + button(type="button") 45+30 + button(type="button") 3d + button(type="button") 7d input#cadence( type="text" v-model="newchallenge.cadence" - placeholder="5+0, 1h+30s, 7d+1d ..." + placeholder="5+0, 1h+30s, 5d ..." ) + fieldset + label(for="selectRandomLevel") {{ st.tr["Randomness"] }} + select#selectRandomLevel(v-model="newchallenge.randomness") + option(value="0") {{ st.tr["Deterministic"] }} + option(value="1") {{ st.tr["Symmetric random"] }} + option(value="2") {{ st.tr["Asymmetric random"] }} fieldset(v-if="st.user.id > 0") label(for="selectPlayers") {{ st.tr["Play with?"] }} input#selectPlayers( @@ -43,15 +67,17 @@ 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" - @click="resetChatColor()" + @click="resetSocialColor()" ) div#peopleWrap( role="dialog" @@ -63,7 +89,7 @@ main #players p( v-for="sid in Object.keys(people)" - v-if="!!people[sid].name" + v-if="people[sid].name" ) span {{ people[sid].name }} button.player-action( @@ -131,6 +157,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 +181,18 @@ 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") || "", + randomness: parseInt(localStorage.getItem("randomness")) || 2, + // 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,18 +208,23 @@ 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: { anonymousCount: function() { let count = 0; Object.values(this.people).forEach(p => { - count += !p.name ? 1 : 0; + // Do not cound people who did not send their identity yet: + count += (!p.name && p.id === 0) ? 1 : 0; }); return count; } }, 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) @@ -209,7 +249,6 @@ export default { let names = {}; response.challenges.forEach(c => { if (c.uid != this.st.user.id) names[c.uid] = ""; - //unknwon for now else if (!!c.target && c.target != this.st.user.id) names[c.target] = ""; }); @@ -288,6 +327,10 @@ export default { }, methods: { // Helpers: + cadenceFocusIfOpened: function() { + if (event.target.checked) + document.getElementById("cadence").focus(); + }, send: function(code, obj) { if (this.conn) { this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); @@ -358,7 +401,7 @@ export default { url += "?rid=" + g.rids[Math.floor(Math.random() * g.rids.length)]; this.$router.push(url); }, - resetChatColor: function() { + resetSocialColor: function() { // TODO: this is called twice, once on opening an once on closing document.getElementById("peopleBtn").classList.remove("somethingnew"); }, @@ -377,17 +420,18 @@ export default { data.sockIds.forEach(s => { const page = s.page || "/"; if (s.sid != this.st.user.sid && !identityAsked[s.sid]) { - identityAsked[s.sid] = true; this.send("askidentity", { target: s.sid, page: page }); + identityAsked[s.sid] = true; } if (!this.people[s.sid]) - this.$set(this.people, s.sid, { id: 0, name: "", pages: [page] }); + // Do not set name or id: identity unknown yet + this.$set(this.people, s.sid, { pages: [page] }); else if (this.people[s.sid].pages.indexOf(page) < 0) this.people[s.sid].pages.push(page); if (!s.page) - //peer is in Hall + // Peer is in Hall this.send("askchallenge", { target: s.sid }); - //peer is in Game + // Peer is in Game else this.send("askgame", { target: s.sid, page: page }); }); break; @@ -399,20 +443,17 @@ export default { // So it's a good idea to ask identity if he was anonymous. // But only ask game / challenge if currently disconnected. if (!this.people[data.from]) { - this.$set(this.people, data.from, { - name: "", - id: 0, - pages: [page] - }); + this.$set(this.people, data.from, { pages: [page] }); if (data.code == "connect") this.send("askchallenge", { target: data.from }); else this.send("askgame", { target: data.from, page: page }); } else { - // append page if not already in list + // Append page if not already in list if (this.people[data.from].pages.indexOf(page) < 0) this.people[data.from].pages.push(page); } - if (this.people[data.from].id == 0) { + if (!this.people[data.from].name && this.people[data.from].id !== 0) { + // Identity not known yet this.newConnect[data.from] = true; //for self multi-connects tests this.send("askidentity", { target: data.from, page: page }); } @@ -451,12 +492,8 @@ export default { } case "killed": // I logged in elsewhere: - alert(this.st.tr["New connexion detected: tab now offline"]); - // TODO: this fails. See https://github.com/websockets/ws/issues/489 - //this.conn.removeEventListener("message", this.socketMessageListener); - //this.conn.removeEventListener("close", this.socketCloseListener); - //this.conn.close(); this.conn = null; + alert(this.st.tr["New connexion detected: tab now offline"]); break; case "askidentity": { // Request for identification (TODO: anonymous shouldn't need to reply) @@ -471,6 +508,11 @@ export default { } case "identity": { const user = data.data; + this.$set(this.people, user.sid, { + id: user.id, + name: user.name, + pages: this.people[user.sid].pages + }); if (user.name) { // If I multi-connect, kill current connexion if no mark (I'm older) if ( @@ -484,14 +526,6 @@ export default { this.killed[this.st.user.sid] = true; } } - if (user.sid != this.st.user.sid) { - //I already know my identity... - this.$set(this.people, user.sid, { - id: user.id, - name: user.name, - pages: this.people[user.sid].pages - }); - } } delete this.newConnect[user.sid]; break; @@ -510,6 +544,7 @@ export default { id: c.id, from: this.st.user.sid, to: c.to, + randomness: c.randomness, fen: c.fen, vid: c.vid, cadence: c.cadence, @@ -530,6 +565,7 @@ export default { ) { let newChall = Object.assign({}, chall); newChall.type = this.classifyObject(chall); + newChall.randomness = chall.randomness; newChall.added = Date.now(); let fromValues = Object.assign({}, this.people[chall.from]); delete fromValues["pages"]; //irrelevant in this context @@ -563,28 +599,32 @@ export default { case "newgame": { // NOTE: it may be live or correspondance const game = data.data; - let locGame = this.games.find(g => g.id == game.id); - if (!locGame) { - let newGame = game; - newGame.type = this.classifyObject(game); - newGame.vname = this.getVname(game.vid); - if (!game.score) - //if new game from Hall - newGame.score = "*"; - newGame.rids = [game.rid]; - delete newGame["rid"]; - this.games.push(newGame); - if ( - (newGame.type == "live" && this.gdisplay == "corr") || - (newGame.type == "corr" && this.gdisplay == "live") - ) { - document - .getElementById("btnG" + newGame.type) - .classList.add("somethingnew"); + // Ignore games where I play (corr games) + if (game.players.every(p => p.id != this.st.user.id)) + { + let locGame = this.games.find(g => g.id == game.id); + if (!locGame) { + let newGame = game; + newGame.type = this.classifyObject(game); + newGame.vname = this.getVname(game.vid); + if (!game.score) + //if new game from Hall + newGame.score = "*"; + newGame.rids = [game.rid]; + delete newGame["rid"]; + this.games.push(newGame); + if ( + (newGame.type == "live" && this.gdisplay == "corr") || + (newGame.type == "corr" && this.gdisplay == "live") + ) { + document + .getElementById("btnG" + newGame.type) + .classList.add("somethingnew"); + } + } else { + // Append rid (if not already in list) + if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid); } - } else { - // Append rid (if not already in list) - if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid); } break; } @@ -626,6 +666,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 @@ -649,9 +716,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); @@ -659,6 +724,8 @@ export default { } // NOTE: "from" information is not required here let chall = Object.assign({}, this.newchallenge); + delete chall["V"]; + delete chall["diag"]; const finishAddChallenge = cid => { chall.id = cid || "c" + getRandString(); // Remove old challenge if any (only one at a time of a given type): @@ -680,7 +747,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 @@ -688,12 +755,20 @@ 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); localStorage.setItem("vid", chall.vid); + localStorage.setItem("randomness", chall.randomness); document.getElementById("modalNewgame").checked = false; + // Show the challenge if not on current display + if ( + (ctype == "live" && this.cdisplay == "corr") || + (ctype == "corr" && this.cdisplay == "live") + ) { + this.setDisplay('c', ctype); + } }; if (ctype == "live") { // Live challenges have a random ID @@ -705,7 +780,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 @@ -715,25 +811,31 @@ 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); + c.mycolor = V.GetOppCol(parsedFen.turn); + this.tchallDiag = getDiagram({ + position: parsedFen.position, + orientation: c.mycolor + }); + 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 }); } @@ -743,14 +845,15 @@ 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(), - fen: c.fen || V.GenRandInitFen(), - players: shuffle([c.from, c.seat]), //white then black + fen: c.fen || V.GenRandInitFen(c.randomness), + // White player index 0, black player index 1: + players: c.mycolor + ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat]) + : shuffle([c.from, c.seat]), vid: c.vid, cadence: c.cadence }; @@ -800,8 +903,8 @@ export default { GameStorage.add(game, (err) => { // If an error occurred, game is not added: abort if (!err) { - if (this.st.settings.sound >= 1) - new Audio("/sounds/newgame.mp3").play().catch(() => {}); + if (this.st.settings.sound) + new Audio("/sounds/newgame.flac").play().catch(() => {}); this.$router.push("/game/" + gameInfo.id); } }); @@ -818,7 +921,7 @@ export default { padding: 15px 0 max-width: 430px -#newgameDiv > .card +#newgameDiv > .card, #acceptDiv > .card max-width: 767px max-height: 100% @@ -871,6 +974,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)