Fixes, improvements
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 16 Feb 2020 23:15:13 +0000 (00:15 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 16 Feb 2020 23:15:13 +0000 (00:15 +0100)
client/src/components/BaseGame.vue
client/src/components/ChallengeList.vue
client/src/components/UpsertUser.vue
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/views/Game.vue
client/src/views/Hall.vue
client/src/views/MyGames.vue
client/src/views/Problems.vue
server/models/Game.js

index c17ba1e..f5eb9a6 100644 (file)
@@ -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);
index b3c43e2..f6d3090 100644 (file)
@@ -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;
index 3a0bb49..f5a9044 100644 (file)
@@ -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()")
index 82e6d07..c965335 100644 (file)
@@ -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",
index 8157d2e..78f8585 100644 (file)
@@ -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",
index 41593b8..e5322eb 100644 (file)
@@ -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",
index d6c8599..e221561 100644 (file)
@@ -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
 </style>
index b3f5b69..366de18 100644 (file)
@@ -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
 </style>
index c89b2d6..830c535 100644 (file)
@@ -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")
 </template>
 
@@ -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);
     },
   },
 };
 </script>
+
+<style lang="sass" scoped>
+.active
+  color: #42a983
+</style>
index 97e8c8e..d3dfdfd 100644 (file)
@@ -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")
 </template>
 
@@ -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 <br> by spaces
+        .replace(/\n/g, " " )
+        .replace(/<br\/?>/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
+
 </style>
index f175164..b4f128b 100644 (file)
@@ -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<gameIds.length; i++)
         {
           GameModel.getOne(gameIds[i]["gid"], true, (err2,game) => {
             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);
           });
         }