From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 30 Jan 2020 10:35:36 +0000 (+0100) Subject: Smooth scrolling in moves, template to render moveList X-Git-Url: https://git.auder.net/variants/current/css/img/assets/mini-custom.min.css?a=commitdiff_plain;h=430a203855578f9bbf4c851165c6066a741ff1f8;p=vchess.git Smooth scrolling in moves, template to render moveList --- diff --git a/client/src/App.vue b/client/src/App.vue index 4fc3b48b..77fed7af 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -140,6 +140,10 @@ nav width: 36px height: 27px +@media screen and (max-width: 767px) + nav + border: none + [type="checkbox"].drawer+* right: -767px @@ -168,4 +172,8 @@ footer & > p display: inline-block margin: 0 0 0 10px + +@media screen and (max-width: 767px) + footer + border: none </style> diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 5c82b0b1..e04f5082 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -22,7 +22,7 @@ div#baseGame(tabindex=-1 @click="() => focusBg()" @keydown="handleKeys") a#download(href="#") button(@click="download") {{ st.tr["Download PGN"] }} .col-sm-12.col-md-3 - MoveList(v-if="showMoves" + MoveList(v-if="showMoves" :score="game.score" :message="game.scoreMsg" :moves="moves" :cursor="cursor" @goto-move="gotoMove") </template> @@ -51,7 +51,6 @@ export default { moves: [], cursor: -1, //index of the move just played lastMove: null, - gameHasEnded: false, //to avoid showing end message twice }; }, watch: { @@ -63,13 +62,6 @@ export default { "game.moveToPlay": function() { this.play(this.game.moveToPlay, "receive", this.game.vname=="Dark"); }, - "game.score": function(score) { - if (!this.gameHasEnded && score != "*") - { - // "false" says "don't bubble up": the parent already knows - this.endGame(score, this.game.scoreMsg, false); - } - }, }, computed: { showMoves: function() { @@ -77,10 +69,10 @@ export default { //return window.innerWidth >= 768; }, showFen: function() { - return this.game.vname != "Dark" || this.score != "*"; + return this.game.vname != "Dark" || this.game.score != "*"; }, analyze: function() { - return this.game.mode == "analyze" || this.score != "*"; + return this.game.mode == "analyze" || this.game.score != "*"; }, }, created: function() { @@ -117,8 +109,6 @@ export default { re_setVariables: function() { this.endgameMessage = ""; this.orientation = this.game.mycolor || "w"; //default orientation for observed games - this.score = this.game.score || "*"; //mutable (if initially "*") - this.gameHasEnded = (this.score != "*"); this.moves = JSON.parse(JSON.stringify(this.game.moves || [])); // Post-processing: decorate each move with color + current FEN: // (to be able to jump to any position quickly) @@ -142,8 +132,9 @@ export default { this.lastMove = (L > 0 ? this.moves[L-1] : null); }, gotoFenContent: function(event) { - this.$router.push("/analyze/" + this.game.vname + - "/?fen=" + event.target.innerText.replace(/ /g, "_")); + const newUrl = "#/analyze/" + this.game.vname + + "/?fen=" + event.target.innerText.replace(/ /g, "_"); + window.open(newUrl); //to open in a new tab }, download: function() { const content = this.getPgn(); @@ -161,7 +152,7 @@ export default { pgn += '[White "' + this.game.players[0].name + '"]\n'; pgn += '[Black "' + this.game.players[1].name + '"]\n'; pgn += '[Fen "' + this.game.fenStart + '"]\n'; - pgn += '[Result "' + this.score + '"]\n\n'; + pgn += '[Result "' + this.game.score + '"]\n\n'; let counter = 1; let i = 0; while (i < this.moves.length) @@ -203,15 +194,6 @@ export default { modalBox.checked = true; setTimeout(() => { modalBox.checked = false; }, 2000); }, - endGame: function(score, message, bubbleUp) { - this.gameHasEnded = true; - this.score = score; - if (!message) - message = this.getScoreMessage(score); - this.showEndgameMsg(score + " . " + message); - if (bubbleUp) - this.$emit("gameover", score); - }, animateMove: function(move) { let startSquare = document.getElementById(getSquareId(move.start)); let endSquare = document.getElementById(getSquareId(move.end)); @@ -275,7 +257,7 @@ export default { if (!navigate) { move.fen = this.vr.getFen(); - if (this.score == "*" || this.analyze) + if (this.game.score == "*" || this.analyze) { // Stack move on movesList at current cursor if (this.cursor == this.moves.length) @@ -291,14 +273,11 @@ export default { const score = this.vr.getCurrentScore(); if (score != "*") { + const message = this.getScoreMessage(score); if (!this.analyze) - this.endGame(score, undefined, true); - else - { - // Just show score on screen (allow undo) - const message = this.getScoreMessage(score); + this.$emit("gameover", score, message); + else //just show score on screen (allow undo) this.showEndgameMsg(score + " . " + message); - } } }, undo: function(move) { @@ -366,7 +345,7 @@ export default { width: 20% margin: 0 #boardContainer - margin-top: 5px + //margin-top: 5px >div margin-left: auto margin-right: auto diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index 941016b7..46b672bb 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -123,8 +123,9 @@ export default { this.playComputerMove(); } }, - gameOver: function(score) { + gameOver: function(score, scoreMsg) { this.game.score = score; + this.game.scoreMsg = scoreMsg; this.game.mode = "analyze"; this.$emit("game-over", score); //bubble up to Rules.vue }, diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue index a4e1ca41..70756cc3 100644 --- a/client/src/components/MoveList.vue +++ b/client/src/components/MoveList.vue @@ -1,9 +1,65 @@ +<template lang="pug"> +div + #scoreInfo(v-if="score!='*'") + p {{ score }} + p {{ message }} + table#movesList + tbody + tr(v-for="moveIdx in evenNumbers") + td {{ moveIdx / 2 + 1 }} + td(:class="{'highlight-lm': cursor == moveIdx}" + data-label="White move" @click="() => gotoMove(moveIdx)") + | {{ moves[moveIdx].notation }} + td(v-if="moveIdx < moves.length-1" + :class="{'highlight-lm': cursor == moveIdx+1}" + data-label="Black move" @click="() => gotoMove(moveIdx+1)") + | {{ moves[moveIdx+1].notation }} + // Else: just add an empty cell + td(v-else) +</template> + <script> // Component for moves list on the right export default { name: 'my-move-list', - props: ["moves","cursor"], - render(h) { + props: ["moves","cursor","score","message"], + watch: { + cursor: function(newValue) { + // $nextTick to wait for table > tr to be rendered + this.$nextTick( () => { + let rows = document.querySelectorAll('#movesList tr'); + if (rows.length > 0) + { + rows[Math.floor(newValue/2)].scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + }); + }, + }, + computed: { + evenNumbers: function() { + return [...Array(this.moves.length).keys()].filter(i => i%2==0); + }, + }, + methods: { + gotoMove: function(index) { + this.$emit("goto-move", index); + }, + }, +}; +</script> + +<style lang="sass" scoped> +.moves-list + min-width: 250px +td.highlight-lm + background-color: plum +</style> + +<!-- Old render method: + render(h) { if (this.moves.length == 0) return; let tableContent = []; @@ -65,32 +121,34 @@ export default { } tableRow.children = moveCells; tableContent.push(tableRow); + const scoreDiv = h("div", + { + id: "scoreInfo", + style: { + display: this.score!="*" ? "block" : "none", + }, + }, + [ + h("p", this.score), + h("p", this.message), + ] + ); const movesTable = h( "div", { }, - [h( - "table", - { - "class": { - "moves-list": true, + [ + scoreDiv, + h( + "table", + { + "class": { + "moves-list": true, + }, }, - }, - tableContent - )] + tableContent + ) + ] ); return movesTable; }, - methods: { - gotoMove: function(index) { - this.$emit("goto-move", index); - }, - }, -}; -</script> - -<style lang="sass" scoped> -.moves-list - min-width: 250px -td.highlight-lm - background-color: plum -</style> +--> diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 00414d91..1ffcda41 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -1,14 +1,11 @@ <template lang="pug"> main .row - .col-sm-12.col-md-3 + #chat.col-sm-12.col-md-4.col-md-offset-4 Chat(:players="game.players") - .col-sm-12.col-md-9 - BaseGame(:game="game" :vr="vr" ref="basegame" - @newmove="processMove" @gameover="gameOver") .row - .col-sm-12.col-md-9.col-md-offset-3 - .button-group(v-if="game.mode!='analyze' && game.score=='*'") + .col-sm-12 + #actions(v-if="game.mode!='analyze' && game.score=='*'") button(@click="offerDraw") Draw button(@click="abortGame") Abort button(@click="resign") Resign @@ -16,6 +13,8 @@ main div(v-if="game.score=='*'") Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }} div(v-if="game.type=='corr'") {{ game.corrMsg }} textarea(v-if="game.score=='*'" v-model="corrMsg") + BaseGame(:game="game" :vr="vr" ref="basegame" + @newmove="processMove" @gameover="gameOver") </template> <script> @@ -79,7 +78,7 @@ export default { { clearInterval(clockUpdate); if (countdown < 0) - this.setScore(this.vr.turn=="w" ? "0-1" : "1-0", "Time"); + this.gameOver(this.vr.turn=="w" ? "0-1" : "1-0", "Time"); } else { @@ -211,13 +210,13 @@ export default { break; } case "resign": - this.setScore(data.side=="b" ? "1-0" : "0-1", "Resign"); + this.gameOver(data.side=="b" ? "1-0" : "0-1", "Resign"); break; case "abort": - this.setScore("?", "Abort"); + this.gameOver("?", "Abort"); break; case "draw": - this.setScore("1/2", "Mutual agreement"); + this.gameOver("1/2", "Mutual agreement"); break; case "drawoffer": this.drawOffer = "received"; //TODO: observers don't know who offered draw @@ -253,17 +252,13 @@ export default { { // Opponent resigned or aborted game, or accepted draw offer // (this is not a stalemate or checkmate) - this.setScore(data.score, "Opponent action"); + this.gameOver(data.score, "Opponent action"); } this.game.clocks = data.clocks; //TODO: check this? if (!!data.lastMove.draw) this.drawOffer = "received"; } }, - setScore: function(score, message) { - this.game.scoreMsg = message; - this.$set(this.game, "score", score); //TODO: Vue3... - }, offerDraw: function() { if (this.drawOffer == "received") { @@ -273,7 +268,7 @@ export default { if (p.sid != this.st.user.sid) this.st.conn.send(JSON.stringify({code:"draw", target:p.sid})); }); - this.setScore("1/2", "Mutual agreement"); + this.gameOver("1/2", "Mutual agreement"); } else if (this.drawOffer == "sent") { @@ -297,7 +292,7 @@ export default { abortGame: function() { if (!confirm(this.st.tr["Terminate game?"])) return; - this.setScore("?", "Abort"); + this.gameOver("?", "Abort"); this.people.forEach(p => { if (p.sid != this.st.user.sid) { @@ -318,7 +313,7 @@ export default { side:this.game.mycolor, target:p.sid})); } }); - this.setScore(this.game.mycolor=="w" ? "0-1" : "1-0", "Resign"); + this.gameOver(this.game.mycolor=="w" ? "0-1" : "1-0", "Resign"); }, // 3 cases for loading a game: // - from indexedDB (running or completed live game I play) @@ -518,10 +513,10 @@ export default { if (this.repeat[repIdx] >= 3) this.drawOffer = "received"; //TODO: will print "mutual agreement"... }, - gameOver: function(score) { + gameOver: function(score, scoreMsg) { this.game.mode = "analyze"; - this.game.score = score; //until Vue3, this property change isn't seen - //by child (and doesn't need to be) + this.game.score = score; + this.game.scoreMsg = scoreMsg; const myIdx = this.game.players.findIndex(p => { return p.sid == this.st.user.sid || p.uid == this.st.user.id; }); @@ -535,13 +530,29 @@ export default { <style lang="sass"> .connected background-color: green - .disconnected background-color: red -.white-turn - background-color: white +@media screen and (min-width: 768px) + #actions + width: 300px +@media screen and (max-width: 767px) + .game + width: 100% -.black-turn - background-color: black +#actions + margin-top: 10px + margin-left: auto + margin-right: auto + button + display: inline-block + width: 33% + margin: 0 +#chat + margin-top: 5px + margin-bottom: 5px + >.card + max-width: 100% + margin: 0; + border: none; </style>