X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FGame.vue;h=fe55f7439c69353a403ad21641999ac7305ee67c;hb=2c5d7b20742b802d9c47916915c1114bcfc9a9c3;hp=f9bc8430b4878faf2e64a888ddb887f9acad6f54;hpb=c7550017d88ff6eb32e2c76a271a7f9edc4a3bf6;p=vchess.git diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index f9bc8430..fe55f743 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -131,6 +131,7 @@ import Chat from "@/components/Chat.vue"; import { store } from "@/store"; import { GameStorage } from "@/utils/gameStorage"; import { ppt } from "@/utils/datetime"; +import { notify } from "@/utils/notifications"; import { ajax } from "@/utils/ajax"; import { extractTime } from "@/utils/timeControl"; import { getRandString } from "@/utils/alea"; @@ -154,6 +155,7 @@ export default { gameRef: "", nextIds: [], game: {}, //passed to BaseGame + focus: !document.hidden, //will not always work... TODO // virtualClocks will be initialized from true game.clocks virtualClocks: [], vr: null, //"variant rules" object initialized from FEN @@ -229,6 +231,8 @@ export default { cleanBeforeDestroy: function() { clearInterval(this.socketCloseListener); document.removeEventListener('visibilitychange', this.visibilityChange); + window.removeEventListener('focus', this.onFocus); + window.removeEventListener('blur', this.onBlur); if (!!this.askLastate) clearInterval(this.askLastate); if (!!this.retrySendmove) clearInterval(this.retrySendmove); if (!!this.clockUpdate) clearInterval(this.clockUpdate); @@ -238,11 +242,25 @@ export default { }, visibilityChange: function() { // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27 - this.send( - document.visibilityState == "visible" - ? "getfocus" - : "losefocus" - ); + this.focus = (document.visibilityState == "visible"); + if (!this.focus && !!this.rematchOffer) { + this.rematchOffer = ""; + this.send("rematchoffer", { data: false }); + // Do not remove rematch offer from (local) storage + } + this.send(this.focus ? "getfocus" : "losefocus"); + }, + onFocus: function() { + this.focus = true; + this.send("getfocus"); + }, + onBlur: function() { + this.focus = false; + if (!!this.rematchOffer) { + this.rematchOffer = ""; + this.send("rematchoffer", { data: false }); + } + this.send("losefocus"); }, participateInChat: function(p) { return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name; @@ -256,6 +274,8 @@ export default { }, atCreation: function() { document.addEventListener('visibilitychange', this.visibilityChange); + window.addEventListener('focus', this.onFocus); + window.addEventListener('blur', this.onBlur); // 0] (Re)Set variables this.gameRef = this.$route.params["id"]; // next = next corr games IDs to navigate faster (if applicable) @@ -312,7 +332,8 @@ export default { this.socketCloseListener = setInterval( () => { if (this.conn.readyState == 3) { - this.conn.removeEventListener("message", this.socketMessageListener); + this.conn.removeEventListener( + "message", this.socketMessageListener); this.conn = new WebSocket(this.connexionString); this.conn.addEventListener("message", this.socketMessageListener); } @@ -334,7 +355,7 @@ export default { this.loadVariantThenGame(game, () => socketInit(this.roomInit)); else // Live game stored remotely: need socket to retrieve it - // NOTE: the callback "roomInit" will be lost, so we don't provide it. + // NOTE: the callback "roomInit" will be lost, so it's not provided. // --> It will be given when receiving "fullgame" socket event. socketInit(() => { this.send("askfullgame"); }); }); @@ -408,16 +429,22 @@ export default { // NOTE: anonymous chats in corr games are not stored on server (TODO?) if (this.game.type == "corr" && this.st.user.id > 0) this.updateCorrGame({ chat: chat }); + else if (this.game.type == "live") { + chat.added = Date.now(); + GameStorage.update(this.gameRef, { chat: chat }); + } }, clearChat: function() { - // Nothing more to do if game is live (chats not recorded) - if (this.game.type == "corr") { - if (!!this.game.mycolor) { + if (!!this.game.mycolor) { + if (this.game.type == "corr") { ajax( "/chats", "DELETE", { data: { gid: this.game.id } } ); + } else { + // Live game + GameStorage.update(this.gameRef, { delchat: true }); } this.$set(this.game, "chats", []); } @@ -640,7 +667,7 @@ export default { case "asklastate": // Sending informative last state if I played a move or score != "*" // If the game or moves aren't loaded yet, delay the sending: - // TODO: since socket init after game load, the game is supposedly ready + // TODO: socket init after game load, so the game is supposedly ready if (!this.game || !this.game.moves) this.lastateAsked = true; else this.sendLastate(data.from); break; @@ -674,9 +701,25 @@ export default { } else { this.gotMoveIdx = movePlus.index; const receiveMyMove = (movePlus.color == this.game.mycolor); - if (!receiveMyMove && !!this.game.mycolor) + const moveColIdx = ["w", "b"].indexOf(movePlus.color); + if (!receiveMyMove && !!this.game.mycolor) { // Notify opponent that I got the move: - this.send("gotmove", {data: movePlus.index, target: data.from}); + this.send( + "gotmove", + { data: movePlus.index, target: data.from } + ); + // And myself if I'm elsewhere: + if (!this.focus) { + notify( + "New move", + { + body: + (this.game.players[moveColIdx].name || "@nonymous") + + " just played." + } + ); + } + } if (movePlus.cancelDrawOffer) { // Opponent refuses draw this.drawOffer = ""; @@ -689,8 +732,8 @@ export default { GameStorage.update(this.gameRef, { drawOffer: "" }); } } - this.$refs["basegame"].play(movePlus.move, "received", null, true); - const moveColIdx = ["w", "b"].indexOf(movePlus.color); + this.$refs["basegame"].play( + movePlus.move, "received", null, true); this.game.clocks[moveColIdx] = movePlus.clock; this.processMove( movePlus.move, @@ -761,11 +804,17 @@ export default { } break; } - case "newchat": - this.$refs["chatcomp"].newChat(data.data); + case "newchat": { + let chat = data.data; + this.$refs["chatcomp"].newChat(chat); + if (this.game.type == "live") { + chat.added = Date.now(); + GameStorage.update(this.gameRef, { chat: chat }); + } if (!document.getElementById("modalChat").checked) document.getElementById("chatBtn").classList.add("somethingnew"); break; + } } }, updateCorrGame: function(obj, callback) { @@ -938,7 +987,8 @@ export default { } }, abortGame: function() { - if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) return; + if (!this.game.mycolor || !confirm(this.st.tr["Terminate game?"])) + return; this.gameOver("?", "Stop"); this.send("abort"); }, @@ -957,8 +1007,12 @@ export default { const myIdx = game.players.findIndex(p => { return p.sid == this.st.user.sid || p.id == this.st.user.id; }); - const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers - if (!game.chats) game.chats = []; //live games don't have chat history + // "mycolor" is undefined for observers + const mycolor = [undefined, "w", "b"][myIdx + 1]; + // Live games before 26/03/2020 don't have chat history: + if (!game.chats) game.chats = []; //TODO: remove line + // Sort chat messages from newest to oldest + game.chats.sort((c1, c2) => c2.added - c1.added); if (gtype == "corr") { // NOTE: clocks in seconds game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of @@ -971,10 +1025,6 @@ export default { (Date.now() - game.moves[L-1].played) / 1000; } } - // Sort chat messages from newest to oldest - game.chats.sort((c1, c2) => { - return c2.added - c1.added; - }); if (myIdx >= 0 && game.score == "*" && game.chats.length > 0) { // Did a chat message arrive after my last move? let dtLastMove = 0; @@ -996,6 +1046,12 @@ export default { game.moves = game.moves.map(m => m.squares); } if (gtype == "live") { + if ( + game.chats.length > 0 && + (!game.initime || game.initime < game.chats[0].added) + ) { + document.getElementById("chatBtn").classList.add("somethingnew"); + } if (game.clocks[0] < 0) { // Game is unstarted. clock is ignored until move 2 game.clocks = [tc.mainTime, tc.mainTime]; @@ -1278,7 +1334,7 @@ export default { }); }; // The active tab can update storage immediately - if (!document.hidden) updateStorage(); + if (this.focus) updateStorage(); // Small random delay otherwise else setTimeout(updateStorage, 500 + 1000 * Math.random()); } @@ -1288,7 +1344,8 @@ export default { let sendMove = { move: filtered_move, index: origMovescount, - // color is required to check if this is my move (if several tabs opened) + // color is required to check if this is my move + // (if several tabs opened) color: moveCol, cancelDrawOffer: this.drawOffer == "" }; @@ -1409,10 +1466,18 @@ export default { }; if (this.game.type == "live") { GameStorage.update(this.gameRef, scoreObj); + // Notify myself locally if I'm elsewhere: + if (!this.focus) { + notify( + "Game over", + { body: score + " : " + scoreMsg } + ); + } if (!!callback) callback(); } else this.updateCorrGame(scoreObj, callback); - // Notify the score to main Hall. TODO: only one player (currently double send) + // Notify the score to main Hall. + // TODO: only one player (currently double send) this.send("result", { gid: this.game.id, score: score }); // Also to MyGames page (TODO: doubled as well...) this.notifyMyGames(