X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FHall.vue;h=e60d982fe916cc04c61913e890c9b0b08d7ab7fb;hb=7ebc0408a76b4a966273190a2ade49e0f97099be;hp=eb8464ae4f15339c22863cd424aa64ec81eed592;hpb=68e19a449db7a12e0a168e99cd750d985c983ba1;p=vchess.git diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index eb8464ae..e60d982f 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -73,10 +73,14 @@ main ) fieldset(v-if="st.user.id > 0") label(for="selectPlayers") {{ st.tr["Play with"] }} - select#selectPlayersInList(v-model="newchallenge.to") + select#selectPlayersInList( + v-model="newchallenge.to" + @change="changeChallTarget()" + ) option(value="") option( v-for="p in Object.values(people)" + v-if="!!p.name" :value="p.name" ) | {{ p.name }} @@ -120,10 +124,10 @@ main @click="challenge(sid)" ) | {{ st.tr["Challenge"] }} - p.anonymous @nonymous ({{ anonymousCount }}) + p.anonymous @nonymous ({{ anonymousCount() }}) #chat Chat( - :newChat="newChat" + ref="chatcomp" @mychat="processChat" :pastChats="[]" ) @@ -131,7 +135,7 @@ main .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 .button-group - button#peopleBtn(onClick="window.doClick('modalPeople')") + button#peopleBtn(@click="openModalPeople()") | {{ st.tr["Who's there?"] }} button(@click="showNewchallengeForm()") | {{ st.tr["New game"] }} @@ -193,7 +197,7 @@ main ) button#loadMoreBtn( v-if="hasMore" - @click="loadMore()" + @click="loadMoreCorr()" ) | {{ st.tr["Load more"] }} @@ -204,7 +208,7 @@ import { checkChallenge } from "@/data/challengeCheck"; import { ArrayFun } from "@/utils/array"; import { ajax } from "@/utils/ajax"; import params from "@/parameters"; -import { getRandString, shuffle } from "@/utils/alea"; +import { getRandString, shuffle, randInt } from "@/utils/alea"; import { getDiagram } from "@/utils/printDiagram"; import Chat from "@/components/Chat.vue"; import GameList from "@/components/GameList.vue"; @@ -247,7 +251,6 @@ export default { tchallDiag: "", curChallToAccept: {from: {}}, presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"), - newChat: "", conn: null, connexionString: "", // Related to (killing of) self multi-connects: @@ -264,29 +267,27 @@ export default { }); if (!this.newchallenge.V && this.newchallenge.vid > 0) this.loadNewchallVariant(); - } - }, - computed: { - anonymousCount: function() { - let count = 0; - Object.values(this.people).forEach(p => { - // Do not cound people who did not send their identity yet: - count += (!p.name && p.id === 0) ? 1 : 0; - }); - return count; + }, + $route: function(to, from) { + if (to.path != "/") this.cleanBeforeDestroy(); } }, created: function() { + document.addEventListener('visibilitychange', this.visibilityChange); + window.addEventListener("beforeunload", this.cleanBeforeDestroy); if (this.st.variants.length > 0 && this.newchallenge.vid > 0) this.loadNewchallVariant(); const my = this.st.user; + const tmpId = getRandString(); this.$set( this.people, my.sid, { id: my.id, name: my.name, - pages: [{ path: "/", focus: true }] + tmpIds: { + tmpId: { page: "/", focus: true } + } } ); const connectAndPoll = () => { @@ -296,22 +297,18 @@ export default { // Initialize connection this.connexionString = params.socketUrl + - "/?sid=" + - this.st.user.sid + - "&id=" + - this.st.user.id + - "&tmpId=" + - getRandString() + + "/?sid=" + this.st.user.sid + + "&id=" + this.st.user.id + + "&tmpId=" + tmpId + "&page=" + - // Hall: path is "/" (could be hard-coded as well) + // Hall: path is "/" (TODO: could be hard-coded as well) encodeURIComponent(this.$route.path); this.conn = new WebSocket(this.connexionString); this.conn.onopen = connectAndPoll; - this.conn.onmessage = this.socketMessageListener; - this.conn.onclose = this.socketCloseListener; + this.conn.addEventListener("message", this.socketMessageListener); + this.conn.addEventListener("close", this.socketCloseListener); }, mounted: function() { - document.addEventListener('visibilitychange', this.visibilityChange); ["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => { document.getElementById(eltName) .addEventListener("click", processModalClick); @@ -329,41 +326,9 @@ export default { this.setDisplay('c', showCtype); this.setDisplay('g', showGtype); // Ask server for current corr games (all but mines) - ajax( - "/observedgames", - "GET", - { - data: { - uid: this.st.user.id, - cursor: this.cursor - }, - success: (response) => { - const L = response.games.length; - if (L > 0) { - this.cursor = response.games[L - 1].created; - if (this.games.length == 0 && this.gdisplay == "live") { - document - .getElementById("btnGcorr") - .classList.add("somethingnew"); - } - } - this.games = this.games.concat( - response.games.map(g => { - const vname = this.getVname(g.vid); - return Object.assign( - {}, - g, - { - type: "corr", - vname: vname - } - ); - }) - ); - } - } - ); + this.loadMoreCorr(); // Also ask for corr challenges (open + sent by/to me) + // List them all, because they are not supposed to be that many (TODO?) ajax( "/challenges", "GET", @@ -427,15 +392,34 @@ export default { ); }, beforeDestroy: function() { - document.removeEventListener('visibilitychange', this.visibilityChange); - this.send("disconnect"); + this.cleanBeforeDestroy(); }, methods: { + cleanBeforeDestroy: function() { + document.removeEventListener('visibilitychange', this.visibilityChange); + window.removeEventListener("beforeunload", this.cleanBeforeDestroy); + this.conn.removeEventListener("message", this.socketMessageListener); + this.conn.removeEventListener("close", this.socketCloseListener); + this.send("disconnect"); + this.conn = null; + }, getRandomnessClass: function(pc) { return { ["random-" + pc.randomness]: true }; }, + openModalPeople: function() { + window.doClick("modalPeople"); + document.getElementById("inputChat").focus(); + }, + anonymousCount: function() { + let count = 0; + Object.values(this.people).forEach(p => { + // Do not cound people who did not send their identity yet: + count += (!p.name && p.id === 0) ? 1 : 0; + }); + return count; + }, visibilityChange: function() { // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27 this.send( @@ -484,6 +468,13 @@ export default { if (!!this.curChallToAccept.fen) return { "margin-top": "10px" }; return {}; }, + changeChallTarget: function() { + if (!this.newchallenge.to) { + // Reset potential FEN + diagram + this.newchallenge.fen = ""; + this.newchallenge.diag = ""; + } + }, cadenceFocusIfOpened: function() { if (event.target.checked) document.getElementById("cadence").focus(); @@ -524,15 +515,15 @@ export default { else elt.nextElementSibling.classList.remove("active"); }, isGamer: function(sid) { - return this.people[sid].pages - .some(p => p.focus && p.path.indexOf("/game/") >= 0); + return Object.values(this.people[sid].tmpIds) + .some(v => v.focus && v.page.indexOf("/game/") >= 0); }, isFocusedOnHall: function(sid) { return ( // This is meant to challenge people, thus the next 2 conditions: this.st.user.id > 0 && sid != this.st.user.sid && - this.people[sid].pages.some(p => p.path == "/" && p.focus) + Object.values(this.people[sid].tmpIds).some(v => v.focus && v.page == "/") ); }, challenge: function(sid) { @@ -546,9 +537,9 @@ export default { watchGame: function(sid) { // In some game, maybe playing maybe not: show a random one let gids = []; - this.people[sid].pages.forEach(p => { - if (p.focus) { - const matchGid = p.path.match(/[a-zA-Z0-9]+$/); + Object.values(this.people[sid].tmpIds).forEach(v => { + if (v.focus) { + const matchGid = v.page.match(/[a-zA-Z0-9]+$/); if (!!matchGid) gids.push(matchGid[0]); } }); @@ -560,10 +551,7 @@ export default { showGame: function(g) { // NOTE: we are an observer, since only games I don't play are shown here // ==> Moves sent by connected remote player(s) if live game - let url = "/game/" + g.id; - if (g.type == "live") - url += "?rid=" + g.rids[Math.floor(Math.random() * g.rids.length)]; - this.$router.push(url); + this.$router.push("/game/" + g.id); }, resetSocialColor: function() { // TODO: this is called twice, once on opening an once on closing @@ -587,50 +575,69 @@ export default { const data = JSON.parse(msg.data); switch (data.code) { case "pollclientsandgamers": { - // Since people can be both in Hall and Game, - // need to track "askIdentity" requests: - let identityAsked = {}; - data.sockIds.forEach(s => { - const page = s.page || "/"; - if (s.sid != this.st.user.sid && !identityAsked[s.sid]) { - this.send("askidentity", { target: s.sid, page: page }); - identityAsked[s.sid] = true; + // TODO: shuffling and random filtering on server, + // if the room is really crowded. + Object.keys(data.sockIds).forEach(sid => { + // TODO: test sid != user.sid was already done on server + if (sid != this.st.user.sid) { + // Pick a target tmpId (+page) at random: + const pt = Object.values(data.sockIds[sid]); + const randPage = pt[randInt(pt.length)].page; + this.send( + "askidentity", + { + target: sid, + page: randPage + } + ); } - if (!this.people[s.sid]) { + if (!this.people[sid]) // Do not set name or id: identity unknown yet - this.people[s.sid] = { pages: [{path: page, focus: true}] }; - } - else if (!(this.people[s.sid].pages.find(p => p.path == page))) - this.people[s.sid].pages.push({ path: page, focus: true }); - if (!s.page) + this.people[sid] = { tmpIds: data.sockIds[sid] }; + else + Object.assign(this.people[s.sid].tmpIds, data.sockIds[sid]); + if (Object.values(data.sockIds[sid]).some(v => v.page == "/")) // Peer is in Hall - this.send("askchallenges", { target: s.sid }); - // Peer is in Game: ask only if live game - else if (!page.match(/\/[0-9]+$/)) - this.send("askgame", { target: s.sid, page: page }); + this.send("askchallenges", { target: sid }); + Object.values(data.sockIds[sid]).forEach(v => { + if ( + v.page.indexOf("/game/") >= 0 && + !v.page.match(/\/[0-9]+$/) + ) { + // Peer is in Game: ask only if live game + this.send("askgame", { target: sid, page: v.page }); + } + }); }); break; } case "connect": case "gconnect": { const page = data.page || "/"; - // Only ask game / challenges if first connexion: - if (!this.people[data.from]) { - this.people[data.from] = { pages: [{ path: page, focus: true }] }; - if (data.code == "connect") - this.send("askchallenges", { target: data.from }); - // Ask game only if live: - else if (!page.match(/\/[0-9]+$/)) - this.send("askgame", { target: data.from, page: page }); - } else { - // Append page if not already in list - if (!(this.people[data.from].pages.find(p => p.path == page))) - this.people[data.from].pages.push({ path: page, focus: true }); + if (data.code == "connect") { + // Ask challenges only on first connexion: + if (!this.people[data.from]) + this.send("askchallenges", { target: data.from[0] }); } - if (!this.people[data.from].name && this.people[data.from].id !== 0) { - // Identity not known yet + // Ask game only if live: + else if (!page.match(/\/[0-9]+$/)) + this.send("askgame", { target: data.from[0], page: page }); + if (!this.people[data.from]) { + this.$set( + this.people, + data.from[0], + { + tmpIds: { + [data.from[1]]: { page: page, focus: true } + } + } + ); this.newConnect[data.from] = true; //for self multi-connects tests - this.send("askidentity", { target: data.from, page: page }); + this.send("askidentity", { target: data.from[0], page: page }); + } else { + this.people[data.from[0]].tmpIds[data.from[1]] = + { page: page, focus: true }; + this.$forceUpdate(); //TODO: shouldn't be required } break; } @@ -639,13 +646,16 @@ export default { // If the user reloads the page twice very quickly (experienced with Firefox), // the first reload won't have time to connect but will trigger a "close" event anyway. // ==> Next check is required. - if (!this.people[data.from]) return; - // Disconnect means no more tmpIds: + if (!this.people[data.from[0]]) return; + delete this.people[data.from[0]].tmpIds[data.from[1]]; + if (Object.keys(this.people[data.from[0]].tmpIds).length == 0) + this.$delete(this.people, data.from[0]); + else this.$forceUpdate(); //TODO: shouldn't be required if (data.code == "disconnect") { // Remove the live challenges sent by this player: ArrayFun.remove( this.challenges, - c => c.type == "live" && c.from.sid == data.from, + c => c.type == "live" && c.from.sid == data.from[0], "all" ); } else { @@ -653,38 +663,37 @@ export default { const gid = data.page.match(/[a-zA-Z0-9]+$/)[0]; // Corr games are always reachable: if (!gid.match(/^[0-9]+$/)) { - const gidx = this.games.findIndex(g => g.id == gid); - // NOTE: gidx should always be >= 0 (TODO?) - if (gidx >= 0) { - const game = this.games[gidx]; - ArrayFun.remove(game.rids, rid => rid == data.from); - if (game.rids.length == 0) this.games.splice(gidx, 1); + // Live games are reachable as long as someone is on the game page + if (Object.values(this.people).every(p => + Object.values(p.tmpIds).every(v => v.page != data.page)) + ) { + ArrayFun.remove(this.games, g => g.id == gid); } } } - const page = data.page || "/"; - ArrayFun.remove(this.people[data.from].pages, p => p.path == page); - if (this.people[data.from].pages.length == 0) - this.$delete(this.people, data.from); break; } - case "getfocus": + case "getfocus": { + let player = this.people[data.from[0]]; // If user reload a page, focus may arrive earlier than connect - if (!!this.people[data.from]) { - this.people[data.from].pages - .find(p => p.path == data.page).focus = true; + if (!!player) { + player.tmpIds[data.from[1]].focus = true; this.$forceUpdate(); //TODO: shouldn't be required } break; - case "losefocus": - if (!!this.people[data.from]) { - this.people[data.from].pages - .find(p => p.path == data.page).focus = false; + } + case "losefocus": { + let player = this.people[data.from[0]]; + if (!!player) { + player.tmpIds[data.from[1]].focus = false; this.$forceUpdate(); //TODO: shouldn't be required } break; + } case "killed": // I logged in elsewhere: + this.conn.removeEventListener("message", this.socketMessageListener); + this.conn.removeEventListener("close", this.socketCloseListener); this.conn = null; alert(this.st.tr["New connexion detected: tab now offline"]); break; @@ -702,7 +711,7 @@ export default { case "identity": { const user = data.data; let player = this.people[user.sid]; - // player.pages is already set + // player.tmpIds is already set player.id = user.id; player.name = user.name; // TODO: this.$set(people, ...) fails. So forceUpdate. @@ -778,32 +787,27 @@ export default { case "game": // Individual request case "newgame": { const game = data.data; - // Ignore games where I play (will go in MyGames page) - if (game.players.every(p => - p.sid != this.st.user.sid && 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) - // 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); + // Ignore games where I play (will go in MyGames page), + // and also games that I already received. + if ( + game.players.every(p => + p.sid != this.st.user.sid && p.id != this.st.user.id) && + this.games.findIndex(g => g.id == game.id) == -1 + ) { + let newGame = game; + newGame.type = this.classifyObject(game); + newGame.vname = this.getVname(game.vid); + if (!game.score) + // New game from Hall + newGame.score = "*"; + 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"); } } break; @@ -820,19 +824,15 @@ export default { this.startNewGame(gameInfo); else { this.infoMessage = - this.st.tr["New correspondance game:"] + - " " + - "#/game/" + - gameInfo.id + - ""; + this.st.tr["New correspondance game:"] + " " + + "" + + "#/game/" + gameInfo.id + ""; document.getElementById("modalInfo").checked = true; } break; } case "newchat": - this.newChat = data.data; + this.$refs["chatcomp"].newChat(data.data); if (!document.getElementById("modalPeople").checked) document.getElementById("peopleBtn").classList.add("somethingnew"); break; @@ -844,7 +844,7 @@ export default { this.conn.addEventListener("message", this.socketMessageListener); this.conn.addEventListener("close", this.socketCloseListener); }, - loadMore: function() { + loadMoreCorr: function() { ajax( "/observedgames", "GET", @@ -856,6 +856,16 @@ export default { success: (res) => { const L = res.games.length; if (L > 0) { + if ( + this.cursor == Number.MAX_SAFE_INTEGER && + this.games.length == 0 && + this.gdisplay == "live" + ) { + // First loading: show indicators + document + .getElementById("btnGcorr") + .classList.add("somethingnew"); + } this.cursor = res.games[L - 1].created; let moreGames = res.games.map(g => { const vname = this.getVname(g.vid); @@ -903,10 +913,13 @@ export default { }, 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(); + await import("@/variants/" + vname + ".js") + .then((vModule) => { + window.V = vModule[vname + "Rules"]; + this.newchallenge.V = window.V; + this.newchallenge.vname = vname; + if (!!cb) cb(); + }); }, trySetNewchallDiag: function() { if (!this.newchallenge.fen) { @@ -925,7 +938,7 @@ export default { position: parsedFen.position, orientation: parsedFen.turn }); - } + } else this.newchallenge.diag = ""; }, newChallFromPreset(pchall) { this.partialResetNewchallenge(); @@ -1103,22 +1116,24 @@ 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) - if (!!c.fen) { - const parsedFen = V.ParseFen(c.fen); - c.mycolor = V.GetOppCol(parsedFen.turn); - this.tchallDiag = getDiagram({ - position: parsedFen.position, - orientation: c.mycolor - }); + await import("@/variants/" + c.vname + ".js") + .then((vModule) => { + window.V = vModule[c.vname + "Rules"]; + if (!!c.to) { + // 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; } - this.curChallToAccept = c; - document.getElementById("modalAccept").checked = true; - } - else this.finishProcessingChallenge(c); + else this.finishProcessingChallenge(c); + }); } else { // My challenge @@ -1212,7 +1227,6 @@ export default { // Game state (including FEN): will be updated moves: [], clocks: [-1, -1], //-1 = unstarted - initime: [0, 0], //initialized later score: "*" } );