From 2f258c37c19c5be20ec68695ddfaec2c21f7f0ae Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 17 Feb 2020 00:15:13 +0100 Subject: [PATCH] Fixes, improvements --- client/src/components/BaseGame.vue | 2 +- client/src/components/ChallengeList.vue | 2 +- client/src/components/UpsertUser.vue | 4 +- client/src/translations/en.js | 3 +- client/src/translations/es.js | 3 +- client/src/translations/fr.js | 3 +- client/src/views/Game.vue | 15 +++-- client/src/views/Hall.vue | 79 +++++++++++-------------- client/src/views/MyGames.vue | 43 ++++++++++---- client/src/views/Problems.vue | 52 ++++++++++++---- server/models/Game.js | 34 +++++++---- 11 files changed, 151 insertions(+), 89 deletions(-) diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index c17ba1ea..f5eb9a67 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -121,7 +121,7 @@ export default { if (!boardSize) { boardSize = (window.innerWidth >= 768 - ? Math.min(600, 0.5*window.innerWidth) //heuristic... + ? 0.75 * Math.min(window.innerWidth, window.innerHeight) : window.innerWidth); } const movesWidth = (window.innerWidth >= 768 ? 280 : 0); diff --git a/client/src/components/ChallengeList.vue b/client/src/components/ChallengeList.vue index b3c43e2a..f6d3090e 100644 --- a/client/src/components/ChallengeList.vue +++ b/client/src/components/ChallengeList.vue @@ -30,7 +30,7 @@ export default { let maxAdded = 0 let augmentedChalls = this.challenges.map(c => { let priority = 0; - if (c.to == this.st.user.name) + if (!!c.to && c.to == this.st.user.name) priority = 1; else if (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) priority = 2; diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue index 3a0bb490..f5a90443 100644 --- a/client/src/components/UpsertUser.vue +++ b/client/src/components/UpsertUser.vue @@ -8,7 +8,7 @@ div form(@submit.prevent="onSubmit()" @keyup.enter="onSubmit()") div(v-show="stage!='Login'") fieldset - label(for="username") {{ st.tr["Name"] }} + label(for="username") {{ st.tr["User name"] }} input#username(type="text" v-model="st.user.name") fieldset label(for="useremail") {{ st.tr["Email"] }} @@ -18,7 +18,7 @@ div input#notifyNew(type="checkbox" v-model="st.user.notify") div(v-show="stage=='Login'") fieldset - label(for="nameOrEmail") {{ st.tr["Name or Email"] }} + label(for="nameOrEmail") {{ st.tr["User name or Email"] }} input#nameOrEmail(type="text" v-model="nameOrEmail") .button-group button(@click="onSubmit()") diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 82e6d073..c9653358 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -55,7 +55,6 @@ export const translations = "Mutual agreement": "Mutual agreement", "My games": "My games", "My problems": "My problems", - "Name": "Name", "Name or Email": "Name or Email", "New connexion detected: tab now offline": "New connexion detected: tab now offline", "New correspondance game:": "New correspondance game:", @@ -65,6 +64,7 @@ export const translations = "No subject. Send anyway?": "No subject. Send anyway?", "None": "None", "Notifications by email": "Notifications by email", + "Number": "Number", "Observe": "Observe", "Offer draw?": "Offer draw?", "Opponent action": "Opponent action", @@ -102,6 +102,7 @@ export const translations = "To": "To", "Unknown": "Unknown", "Update": "Update", + "User name": "User name", "Variant": "Variant", "Variants": "Variants", "Versus": "Versus", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 8157d2e4..78f85854 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -56,7 +56,6 @@ export const translations = "Mutual agreement": "Acuerdo mutuo", "My games": "Mis partidas", "My problems": "Mis problemas", - "Name": "Nombre", "Name or Email": "Nombre o Email", "New connexion detected: tab now offline": "Nueva conexión detectada: pestaña ahora desconectada", "New correspondance game:": "Nueva partida por correspondencia:", @@ -66,6 +65,7 @@ export const translations = "No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?", "None": "Ninguno", "Notifications by email": "Notificaciones por email", + "Number": "Número", "Offer draw?": "¿Ofrecer tablas?", "Observe": "Observar", "Opponent action": "Acción del adversario", @@ -103,6 +103,7 @@ export const translations = "To": "A", "Unknown": "Desconocido", "Update": "Actualización", + "User name": "Nombre de usuario", "Variant": "Variante", "Variants": "Variantes", "Versus": "Contra", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 41593b87..e5322eb7 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -56,7 +56,6 @@ export const translations = "Mutual agreement": "Accord mutuel", "My games": "Mes parties", "My problems": "Mes problèmes", - "Name": "Nom", "Name or Email": "Nom ou Email", "New connexion detected: tab now offline": "Nouvelle connexion détectée : onglet désormais hors ligne", "New correspondance game:": "Nouvelle partie par corespondance :", @@ -66,6 +65,7 @@ export const translations = "No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??", "None": "Aucun", "Notifications by email": "Notifications par email", + "Number": "Numéro", "Offer draw?": "Proposer nulle ?", "Observe": "Observer", "Opponent action": "Action de l'adversaire", @@ -103,6 +103,7 @@ export const translations = "To": "À", "Unknown": "Inconnu", "Update": "Mise à jour", + "User name": "Nom d'utilisateur", "Variant": "Variante", "Variants": "Variantes", "Versus": "Contre", diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index d6c8599d..e2215619 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -14,7 +14,8 @@ main :newChat="newChat" @mychat="processChat") .row #aboveBoard.col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2 - span.variant-info {{ game.vname }} + span.variant-cadence {{ game.cadence }} + span.variant-name {{ game.vname }} button#chatBtn(onClick="doClick('modalChat')") Chat #actions(v-if="game.score=='*'") button(@click="clickDraw()" :class="{['draw-' + drawOffer]: true}") @@ -345,7 +346,7 @@ export default { case "newchat": this.newChat = data.data; if (!document.getElementById("modalChat").checked) - document.getElementById("chatBtn").style.backgroundColor = "#c5fefe"; + document.getElementById("chatBtn").classList.add("somethingnew"); break; } }, @@ -667,7 +668,7 @@ export default { }, resetChatColor: function() { // TODO: this is called twice, once on opening an once on closing - document.getElementById("chatBtn").style.backgroundColor = "#e2e2e2"; + document.getElementById("chatBtn").classList.remove("somethingnew"); }, processChat: function(chat) { this.send("newchat", {data:chat}); @@ -730,7 +731,10 @@ export default { #aboveBoard margin-left: 30% -.variant-info +.variant-cadence + padding-right: 10px + +.variant-name font-weight: bold padding-right: 10px @@ -763,4 +767,7 @@ export default { .draw-threerep, .draw-threerep:hover background-color: #e4d1fc + +.somethingnew + background-color: #c5fefe diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index b3f5b690..366de187 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -52,21 +52,21 @@ main button(onClick="doClick('modalNewgame')") {{ st.tr["New game"] }} .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 - div + div#div2 .button-group - button#btnClive(@click="setDisplay('c','live',$event)" class="active") + button.tabbtn#btnClive(@click="setDisplay('c','live',$event)") | {{ st.tr["Live challenges"] }} - button#btnCcorr(@click="setDisplay('c','corr',$event)") + button.tabbtn#btnCcorr(@click="setDisplay('c','corr',$event)") | {{ st.tr["Correspondance challenges"] }} ChallengeList(v-show="cdisplay=='live'" :challenges="filterChallenges('live')" @click-challenge="clickChallenge") ChallengeList(v-show="cdisplay=='corr'" :challenges="filterChallenges('corr')" @click-challenge="clickChallenge") - div + div#div3 .button-group - button#btnGlive(@click="setDisplay('g','live',$event)" class="active") + button.tabbtn#btnGlive(@click="setDisplay('g','live',$event)") | {{ st.tr["Live games"] }} - button#btnGcorr(@click="setDisplay('g','corr',$event)") + button.tabbtn#btnGcorr(@click="setDisplay('g','corr',$event)") | {{ st.tr["Correspondance games"] }} GameList(v-show="gdisplay=='live'" :games="filterGames('live')" :showBoth="true" @show-game="showGame") @@ -142,17 +142,6 @@ export default { "GET", {uid: this.st.user.id, excluded: true}, response => { - // Show corr tab with timeout, to let enough time for (socket) polling - setTimeout( - () => { - if (response.games.length > 0 && - this.games.length == response.games.length) - { - this.setDisplay('g', "corr"); - } - }, - 1000 - ); this.games = this.games.concat(response.games.map(g => { const type = this.classifyObject(g); const vname = this.getVname(g.vid); @@ -166,16 +155,6 @@ export default { "GET", {uid: this.st.user.id}, response => { - setTimeout( - () => { - if (response.challenges.length > 0 && - this.challenges.length == response.challenges.length) - { - this.setDisplay('c', "corr"); - } - }, - 1000 - ); // Gather all senders names, and then retrieve full identity: // (TODO [perf]: some might be online...) let names = {}; @@ -242,6 +221,10 @@ export default { () => { this.newchallenge.cadence = b.innerHTML; } )} ); + const showCtype = localStorage.getItem("type-challenges") || "live"; + const showGtype = localStorage.getItem("type-games") || "live"; + this.setDisplay('c', showCtype); + this.setDisplay('g', showGtype); }, beforeDestroy: function() { this.send("disconnect"); @@ -275,14 +258,12 @@ export default { }, setDisplay: function(letter, type, e) { this[letter + "display"] = type; + localStorage.setItem("type-" + (letter == 'c' ? "challenges" : "games"), type); let elt = !!e ? e.target : document.getElementById("btn" + letter.toUpperCase() + type); - // WARNING: this method is called at created in a setTimeout: - // => the page could have changed and element no longer defined. - if (!elt) - return; elt.classList.add("active"); + elt.classList.remove("somethingnew"); //in case of if (!!elt.previousElementSibling) elt.previousElementSibling.classList.remove("active"); else @@ -327,7 +308,7 @@ export default { }, resetChatColor: function() { // TODO: this is called twice, once on opening an once on closing - document.getElementById("peopleBtn").style.backgroundColor = "#e2e2e2"; + document.getElementById("peopleBtn").classList.remove("somethingnew"); }, processChat: function(chat) { this.send("newchat", {data:chat}); @@ -513,11 +494,11 @@ export default { newChall.from = Object.assign({sid:chall.from}, fromValues); newChall.vname = this.getVname(newChall.vid); this.challenges.push(newChall); - // Adjust visual: - if (newChall.type == "live" && this.cdisplay == "corr" && !this.challenges.some(c => c.type == "corr")) - this.setDisplay('c', "live"); - else if (newChall.type == "corr" && this.cdisplay == "live" && !this.challenges.some(c => c.type == "live")) - this.setDisplay('c', "corr"); + if ((newChall.type == "live" && this.cdisplay == "corr") || + (newChall.type == "corr" && this.cdisplay == "live")) + { + document.getElementById("btnC" + newChall.type).classList.add("somethingnew"); + } } break; } @@ -551,11 +532,11 @@ export default { newGame.rids = [game.rid]; delete newGame["rid"]; this.games.push(newGame); - // Adjust visual: - if (newGame.type == "live" && this.gdisplay == "corr" && !this.games.some(g => g.type == "corr")) - this.setDisplay('g', "live"); - else if (newGame.type == "live" && this.gdisplay == "live" && !this.games.some(g => g.type == "live")) - this.setDisplay('g', "corr"); + if ((newGame.type == "live" && this.gdisplay == "corr") || + (newGame.type == "corr" && this.gdisplay == "live")) + { + document.getElementById("btnG" + newGame.type).classList.add("somethingnew"); + } } else { @@ -591,7 +572,7 @@ export default { case "newchat": this.newChat = data.data; if (!document.getElementById("modalPeople").checked) - document.getElementById("peopleBtn").style.backgroundColor = "#c5fefe"; + document.getElementById("peopleBtn").classList.add("somethingnew"); break; } }, @@ -830,4 +811,16 @@ div#peopleWrap > .card font-style: italic button.player-action margin-left: 32px + +.somethingnew + background-color: #c5fefe !important + +.tabbtn + background-color: white + +#div2, #div3 + margin-top: 15px +@media screen and (max-width: 767px) + #div2, #div3 + margin-top: 0 diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index c89b2d6f..830c5351 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -3,11 +3,11 @@ main .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 .button-group - button(@click="display='live'") {{ st.tr["Live games"] }} - button(@click="display='corr'") {{ st.tr["Correspondance games"] }} - GameList(v-show="display=='live'" :games="filterGames('live')" + button#liveGames(@click="setDisplay('live',$event)") {{ st.tr["Live games"] }} + button#corrGames(@click="setDisplay('corr',$event)") {{ st.tr["Correspondance games"] }} + GameList(v-show="display=='live'" :games="liveGames" @show-game="showGame") - GameList(v-show="display=='corr'" :games="filterGames('corr')" + GameList(v-show="display=='corr'" :games="corrGames" @show-game="showGame") @@ -25,35 +25,52 @@ export default { return { st: store.state, display: "live", - games: [], + liveGames: [], + corrGames: [], }; }, created: function() { GameStorage.getAll((localGames) => { localGames.forEach((g) => g.type = this.classifyObject(g)); - //Array.prototype.push.apply(this.games, localGames); //TODO: Vue 3 - this.games = this.games.concat(localGames); + this.liveGames = localGames; }); if (this.st.user.id > 0) { ajax("/games", "GET", {uid: this.st.user.id}, (res) => { res.games.forEach((g) => g.type = this.classifyObject(g)); - //Array.prototype.push.apply(this.games, res.games); //TODO: Vue 3 - this.games = this.games.concat(res.games); + this.corrGames = res.games; }); } }, + mounted: function() { + const showType = localStorage.getItem("type-myGames") || "live"; + this.setDisplay(showType); + }, methods: { - // TODO: classifyObject and filterGames are redundant (see Hall.vue) + setDisplay: function(type, e) { + this.display = type; + localStorage.setItem("type-myGames", type); + let elt = !!e + ? e.target + : document.getElementById(type + "Games"); + elt.classList.add("active"); + if (!!elt.previousElementSibling) + elt.previousElementSibling.classList.remove("active"); + else + elt.nextElementSibling.classList.remove("active"); + }, + // TODO: classifyObject is redundant (see Hall.vue) classifyObject: function(o) { return (o.cadence.indexOf('d') === -1 ? "live" : "corr"); }, - filterGames: function(type) { - return this.games.filter(g => g.type == type); - }, showGame: function(g) { this.$router.push("/game/" + g.id); }, }, }; + + diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index 97e8c8e9..d3dfdfd2 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -39,9 +39,10 @@ main button(@click="sendProblem()") {{ st.tr["Send"] }} #dialog.text-center {{ st.tr[infoMsg] }} .row(v-if="showOne") - .col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2 + .col-sm-12.col-md-10.col-md-offset-2 #topPage - span {{ curproblem.vname }} + span.vname {{ curproblem.vname }} + span.uname {{ "(" + curproblem.uname + ")" }} button.marginleft(@click="backToList()") {{ st.tr["Back to list"] }} button.nomargin( v-if="st.user.id == curproblem.uid" @@ -54,7 +55,7 @@ main ) | {{ st.tr["Delete"] }} p.clickable( - v-html="curproblem.uname + ' : ' + parseHtml(curproblem.instruction)" + v-html="parseHtml(curproblem.instruction)" @click="curproblem.showSolution=!curproblem.showSolution" ) | {{ st.tr["Show solution"] }} @@ -83,13 +84,15 @@ main tr th {{ st.tr["Variant"] }} th {{ st.tr["Instructions"] }} + th {{ st.tr["Number"] }} tr( v-for="p in problems" v-show="displayProblem(p)" @click="setHrefPid(p)" ) td {{ p.vname }} - td(v-html="p.instruction") + td {{ firstChars(p.instruction) }} + td {{ p.id }} BaseGame(v-if="showOne" :game="game" :vr="vr") @@ -146,10 +149,15 @@ export default { this.problems.forEach(p => { if (p.uid != this.st.user.id) names[p.uid] = ""; //unknwon for now - else { console.log("assign " + this.st.user.name); - p.uname = this.st.user.name; console.log(p); console.log(this.problems); } + else + p.uname = this.st.user.name; }); - if (Object.keys(name).length > 0) + const showOneIfPid = () => { + const pid = this.$route.query["id"]; + if (!!pid) + this.showProblem(this.problems.find(p => p.id == pid)); + }; + if (Object.keys(names).length > 0) { ajax("/users", "GET", @@ -157,12 +165,12 @@ export default { res2 => { res2.users.forEach(u => {names[u.id] = u.name}); this.problems.forEach(p => p.uname = names[p.uid]); + showOneIfPid(); } ); } - const pid = this.$route.query["id"]; - if (!!pid) - this.showProblem(this.problems.find(p => p.id == pid)); + else + showOneIfPid(); }); }, mounted: function() { @@ -175,7 +183,7 @@ export default { if (this.problems.length > 0 && this.problems[0].vname == "") this.problems.forEach(p => this.setVname(p)); }, - "$route": function(to, from) { console.log("ddddd"); + "$route": function(to, from) { const pid = to.query["id"]; if (!!pid) this.showProblem(this.problems.find(p => p.id == pid)); @@ -187,6 +195,19 @@ export default { setVname: function(prob) { prob.vname = this.st.variants.find(v => v.id == prob.vid).name; }, + firstChars: function(text) { + let preparedText = text + // Replace line jumps and
by spaces + .replace(/\n/g, " " ) + .replace(//g, " " ) + .replace(/<[^>]+>/g, "") //remove remaining HTML tags + .replace(/[ ]+/g, " ") //remove series of spaces by only one + .trim(); + const maxLength = 32; //arbitrary... + if (preparedText.length > maxLength) + return preparedText.substr(0,32) + "..."; + return preparedText; + }, copyProblem: function(p1, p2) { for (let key in p1) p2[key] = p1[key]; @@ -336,14 +357,21 @@ textarea text-align: center & > * margin: 0 + #topPage - span + span.vname font-weight: bold padding-left: var(--universal-margin) + span.uname + padding-left: var(--universal-margin) margin: 0 auto & > .nomargin margin: 0 & > .marginleft margin: 0 0 0 15px +@media screen and (max-width: 767px) + #topPage + text-align: center + diff --git a/server/models/Game.js b/server/models/Game.js index f1751644..b4f128b1 100644 --- a/server/models/Game.js +++ b/server/models/Game.js @@ -77,7 +77,7 @@ const GameModel = let query = // NOTE: g.scoreMsg can be NULL // (in this case score = "*" and no reason to look at it) - "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.score, " + + "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " + "g.scoreMsg, g.drawOffer, v.name AS vname " + "FROM Games g " + "JOIN Variants v " + @@ -137,24 +137,38 @@ const GameModel = getByUser: function(uid, excluded, cb) { db.serialize(function() { - const query = - "SELECT DISTINCT gid " + - "FROM Players " + - "WHERE uid " + (excluded ? "<>" : "=") + " " + uid; + let query = ""; + if (uid == 0) + { + // Special case anonymous user: show all games + query = + "SELECT id AS gid " + + "FROM Games"; + } + else + { + // Registered user: + query = + "SELECT gid " + + "FROM Players " + + "GROUP BY gid " + + "HAVING COUNT(uid = " + uid + " OR NULL) " + + (excluded ? " = 0" : " > 0"); + } db.all(query, (err,gameIds) => { - if (!!err) - return cb(err); - if (gameIds.length == 0) - return cb(null, []); + if (!!err || gameIds.length == 0) + return cb(err, []); let gameArray = []; + let kounter = 0; for (let i=0; i { if (!!err2) return cb(err2); gameArray.push(game); + kounter++; //TODO: let's hope this is atomic?! // Call callback function only when gameArray is complete: - if (i == gameIds.length - 1) + if (kounter == gameIds.length) return cb(null, gameArray); }); } -- 2.44.0