A few fixes, drop planned problems support (replaced by forum + mode analyze)
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 29 Dec 2019 22:21:43 +0000 (23:21 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 29 Dec 2019 22:21:43 +0000 (23:21 +0100)
13 files changed:
client/next_src/views/MyGames.vue [deleted file]
client/next_src/views/Problem.vue [new file with mode: 0644]
client/next_src/views/Problems.vue
client/src/App.vue
client/src/components/GameList.vue
client/src/router.js
client/src/stylesheets/variant.sass
client/src/translations/en.js
client/src/translations/fr.js
client/src/views/Game.vue
client/src/views/MyGames.vue
server/models/Game.js
server/routes/games.js

diff --git a/client/next_src/views/MyGames.vue b/client/next_src/views/MyGames.vue
deleted file mode 100644 (file)
index 5f183da..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>
-// "My" games: tabs my archived local games, my correspondance games
-// + my imported games (of any type).
-// TODO: later, also add possibility to upload a game (parse PGN).
-Vue.component("my-tab-games", {
-       props: ["settings"],
-       data: function() {
-               return {
-                       display: "",
-                       imported: [],
-                       local: [],
-                       corr: []
-               };
-       },
-       template: `
-               <div>
-                       <div class="button-group">
-                               <button @click="display='local'">Local games</button>
-                               <button @click="display='corr'">Correspondance games</button>
-                               <button @click="display='imported'">Imported games</button>
-                       </div>
-                       <my-game-list v-show="display=='local'" :games="local">
-                       </my-game-list>
-                       <my-game-list v-show="display=='corr'" :games="corr">
-                       </my-game-list>
-                       <my-game-list v-show="display=='imported'" :games="imported">
-                       </my-game-list>
-                       <button @click="update">Refresh</button>
-               </div>
-       `,
-       created: function() {
-               // TODO: fetch corr games, local and corr
-               // if any corr game where it's my turn, set display = "corr",
-               // else set display = "local" (if any) or imported (if any and no local)
-       },
-       methods: {
-               update: function() {
-                       // TODO: scan local + imported games, if any new then add it
-               },
-       },
-});
diff --git a/client/next_src/views/Problem.vue b/client/next_src/views/Problem.vue
new file mode 100644 (file)
index 0000000..b30a22e
--- /dev/null
@@ -0,0 +1,94 @@
+//TODO: new problem form + problem visualisation, like Game.vue (but simpler)
+// --> mode analyze, moves = [], "load problem"
+               <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
+                       <input type="checkbox" id="modal-newproblem" class="modal"/>
+                       <div role="dialog" aria-labelledby="modalProblemTxt">
+                               <div v-show="!modalProb.preview" class="card newproblem-form">
+                                       <label for="modal-newproblem" class="modal-close">
+                                       </label>
+                                       <h3 id="modalProblemTxt">{{ translate("Add a problem") }}</h3>
+                                       <form @submit.prevent="previewProblem()">
+                                               <fieldset>
+                                                       <label for="newpbFen">FEN</label>
+                                                       <input id="newpbFen" type="text" v-model="modalProb.fen"
+                                                               :placeholder='translate("Full FEN description")'/>
+                                               </fieldset>
+                                               <fieldset>
+                                                       <p class="emphasis">{{ translate("Safe HTML tags allowed") }}</p>
+                                                       <label for="newpbInstructions">{{ translate("Instructions") }}</label>
+                                                       <textarea id="newpbInstructions" v-model="modalProb.instructions"
+                                                               :placeholder='translate("Describe the problem goal")'>
+                                                       </textarea>
+                                                       <label for="newpbSolution">{{ translate("Solution") }}</label>
+                                                       <textarea id="newpbSolution" v-model="modalProb.solution"
+                                                               :placeholder='translate("How to solve the problem?")'>
+                                                       </textarea>
+                                                       <button class="center-btn">{{ translate("Preview") }}</button>
+                                               </fieldset>
+                                       </form>
+                               </div>
+                               <div v-show="modalProb.preview" class="card newproblem-preview">
+                                       <label for="modal-newproblem" class="modal-close"
+                                               @click="modalProb.preview=false">
+                                       </label>
+                                       <my-problem-summary :prob="modalProb" :userid="userId" :preview="true">
+                                       </my-problem-summary>
+                                       <div class="button-group">
+                                               <button @click="modalProb.preview=false">{{ translate("Cancel") }}</button>
+                                               <button @click="sendProblem()">{{ translate("Send") }}</button>
+                                       </div>
+                               </div>
+                       </div>
+               previewProblem: function() {
+                       if (!V.IsGoodFen(this.modalProb.fen))
+                               return alert(translations["Bad FEN description"]);
+                       if (this.modalProb.instructions.trim().length == 0)
+                               return alert(translations["Empty instructions"]);
+                       if (this.modalProb.solution.trim().length == 0)
+                               return alert(translations["Empty solution"]);
+                       Vue.set(this.modalProb, "preview", true);
+               },
+               editProblem: function(prob) {
+                       this.modalProb = prob;
+                       Vue.set(this.modalProb, "preview", false);
+                       document.getElementById("modal-newproblem").checked = true;
+               },
+               deleteProblem: function(pid) {
+                       ajax(
+                               "/problems/" + pid,
+                               "DELETE",
+                               response => {
+                                       // Delete problem from the list on client side
+                                       let problems = this.curProblems();
+                                       const pIdx = problems.findIndex(p => p.id == pid);
+                                       problems.splice(pIdx, 1);
+                               }
+                       );
+               },
+               sendProblem: function() {
+                       // Send it to the server and close modal
+                       ajax(
+                               "/problems/" + variant.id,
+                               (this.modalProb.id > 0 ? "PUT" : "POST"),
+                               this.modalProb,
+                               response => {
+                                       document.getElementById("modal-newproblem").checked = false;
+                                       Vue.set(this.modalProb, "preview", false);
+                                       if (this.modalProb.id == 0)
+                                       {
+                                               this.myProblems.unshift({
+                                                       added: Date.now(),
+                                                       id: response.id,
+                                                       uid: user.id,
+                                                       fen: this.modalProb.fen,
+                                                       instructions: this.modalProb.instructions,
+                                                       solution: this.modalProb.solution,
+                                               });
+                                               if (!this.curProb && this.display != "mine")
+                                                       this.display = "mine";
+                                       }
+                                       else
+                                               this.modalProb.id = 0;
+                               }
+                       );
+               },
index b7f217d..368e09a 100644 (file)
@@ -47,52 +47,6 @@ Vue.component('my-problems', {
        },
        // NOTE: always modals first, because otherwise "scroll to the end" undesirable effect
        template: `
-               <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
-                       <input type="checkbox" id="modal-newproblem" class="modal"/>
-                       <div role="dialog" aria-labelledby="modalProblemTxt">
-                               <div v-show="!modalProb.preview" class="card newproblem-form">
-                                       <label for="modal-newproblem" class="modal-close">
-                                       </label>
-                                       <h3 id="modalProblemTxt">{{ translate("Add a problem") }}</h3>
-                                       <form @submit.prevent="previewProblem()">
-                                               <fieldset>
-                                                       <label for="newpbFen">FEN</label>
-                                                       <input id="newpbFen" type="text" v-model="modalProb.fen"
-                                                               :placeholder='translate("Full FEN description")'/>
-                                               </fieldset>
-                                               <fieldset>
-                                                       <p class="emphasis">{{ translate("Safe HTML tags allowed") }}</p>
-                                                       <label for="newpbInstructions">{{ translate("Instructions") }}</label>
-                                                       <textarea id="newpbInstructions" v-model="modalProb.instructions"
-                                                               :placeholder='translate("Describe the problem goal")'>
-                                                       </textarea>
-                                                       <label for="newpbSolution">{{ translate("Solution") }}</label>
-                                                       <textarea id="newpbSolution" v-model="modalProb.solution"
-                                                               :placeholder='translate("How to solve the problem?")'>
-                                                       </textarea>
-                                                       <button class="center-btn">{{ translate("Preview") }}</button>
-                                               </fieldset>
-                                       </form>
-                               </div>
-                               <div v-show="modalProb.preview" class="card newproblem-preview">
-                                       <label for="modal-newproblem" class="modal-close"
-                                               @click="modalProb.preview=false">
-                                       </label>
-                                       <my-problem-summary :prob="modalProb" :userid="userId" :preview="true">
-                                       </my-problem-summary>
-                                       <div class="button-group">
-                                               <button @click="modalProb.preview=false">{{ translate("Cancel") }}</button>
-                                               <button @click="sendProblem()">{{ translate("Send") }}</button>
-                                       </div>
-                               </div>
-                       </div>
-                       <input id="modalNomore" type="checkbox" class="modal"/>
-                       <div role="dialog" aria-labelledby="nomoreMessage">
-                               <div class="card smallpad small-modal text-center">
-                                       <label for="modalNomore" class="modal-close"></label>
-                                       <h3 id="nomoreMessage" class="section">{{ nomoreMessage }}</h3>
-                               </div>
-                       </div>
                        <div id="problemControls" class="button-group">
                                <button :aria-label='translate("Previous problem(s)")' class="tooltip"
                                        @click="showNext('backward')"
@@ -309,58 +263,5 @@ Vue.component('my-problems', {
                                }
                        );
                },
-               previewProblem: function() {
-                       if (!V.IsGoodFen(this.modalProb.fen))
-                               return alert(translations["Bad FEN description"]);
-                       if (this.modalProb.instructions.trim().length == 0)
-                               return alert(translations["Empty instructions"]);
-                       if (this.modalProb.solution.trim().length == 0)
-                               return alert(translations["Empty solution"]);
-                       Vue.set(this.modalProb, "preview", true);
-               },
-               editProblem: function(prob) {
-                       this.modalProb = prob;
-                       Vue.set(this.modalProb, "preview", false);
-                       document.getElementById("modal-newproblem").checked = true;
-               },
-               deleteProblem: function(pid) {
-                       ajax(
-                               "/problems/" + pid,
-                               "DELETE",
-                               response => {
-                                       // Delete problem from the list on client side
-                                       let problems = this.curProblems();
-                                       const pIdx = problems.findIndex(p => p.id == pid);
-                                       problems.splice(pIdx, 1);
-                               }
-                       );
-               },
-               sendProblem: function() {
-                       // Send it to the server and close modal
-                       ajax(
-                               "/problems/" + variant.id,
-                               (this.modalProb.id > 0 ? "PUT" : "POST"),
-                               this.modalProb,
-                               response => {
-                                       document.getElementById("modal-newproblem").checked = false;
-                                       Vue.set(this.modalProb, "preview", false);
-                                       if (this.modalProb.id == 0)
-                                       {
-                                               this.myProblems.unshift({
-                                                       added: Date.now(),
-                                                       id: response.id,
-                                                       uid: user.id,
-                                                       fen: this.modalProb.fen,
-                                                       instructions: this.modalProb.instructions,
-                                                       solution: this.modalProb.solution,
-                                               });
-                                               if (!this.curProb && this.display != "mine")
-                                                       this.display = "mine";
-                                       }
-                                       else
-                                               this.modalProb.id = 0;
-                               }
-                       );
-               },
        },
 })
index a9c1d4c..e06a2fe 100644 (file)
@@ -8,7 +8,7 @@
     .row
       .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
         // Menu (top of page):
-        // Left: hall, variants, mygames, problems
+        // Left: hall, variants, mygames, forum (ext. link)
         // Right: usermenu, settings, flag
         nav
           label.drawer-toggle(for="drawerControl")
@@ -22,8 +22,9 @@
                 | {{ st.tr["Variants"] }}
               router-link(to="/mygames")
                 | {{ st.tr["My games"] }}
-              router-link(to="/problems")
-                | {{ st.tr["Problems"] }}
+              // TODO: parametric URL, "forumURL"
+              a(href="https://forum.vchess.club")
+                | {{ st.tr["Forum"] }}
             #rightMenu
               .clickable(onClick="doClick('modalUser')")
                 | {{ st.user.id > 0 ? "Update" : "Login" }}
index 8481048..2b7f657 100644 (file)
@@ -8,8 +8,8 @@ table
     th(v-if="showResult") Result
   tr(v-for="g in games" @click="$emit('show-game',g)")
     td {{ g.vname }}
-    td {{ g.players[0] }}
-    td {{ g.players[1] }}
+    td {{ g.players[0].name || "@nonymous" }}
+    td {{ g.players[1].name || "@nonymous" }}
     td {{ g.timeControl }}
     td(v-if="showResult") {{ g.score }}
 </template>
@@ -20,7 +20,7 @@ export default {
        props: ["games"],
        computed: {
                showResult: function() {
-                       return this.games.length > 0 && this.games[0].score != "*";
+                       return this.games.some(g => g.score != "*");
                },
        },
 };
index 49f777d..26bdee9 100644 (file)
@@ -55,17 +55,26 @@ const router = new Router({
       component: Hall,
       //redirect: "/", //problem: redirection before end of AJAX request
     },
+    {
+      path: "/mygames",
+      name: "mygames",
+      component: loadView("MyGames"),
+    },
     {
       path: "/game/:id",
       name: "game",
       component: loadView("Game"),
     },
+    {
+      path: "/analyze/:vname([a-zA-Z0-9]+)",
+      name: "analyze",
+      component: loadView("Game"),
+    },
     {
       path: "/about",
       name: "about",
       component: loadView("About"),
     },
-    // TODO: myGames, problemId: https://router.vuejs.org/guide/essentials/dynamic-matching.html
   ]
 });
 
index 4c00a62..a118159 100644 (file)
@@ -172,50 +172,4 @@ button.seek
   margin: 10px 0
 
 // Rules section:
-
-
-// Problems section:
-
-.newproblem-form input, .newproblem-form textarea
-  width: 100%
-
-.emphasis
-  font-style: italic
-
-#newpbInstructions
-  margin-bottom: var(--universal-margin);
-
-.center-btn
-  margin-left: 40%
-
-//TODO?
-.center-inline
-  text-align: center
-.center-block
-  margin-left: auto
-  margin-right: auto
-
-.mistake-newproblem
-  color: #663300
-
-#solution-div h3
-  background-color: lightgrey
-  padding: 3px 5px
-
-.newproblem-form, .newproblem-preview
-  max-width: 90%
-
-#problemControls
-  width: 75%
-  margin: 0 auto
-  @media screen and (max-width: 767px)
-    width: 100%
-    margin: 0
-
-.problem
-  margin: 10px 0
-
-.only-mine
-  background-color: yellow
-  &:hover
-    background-color: yellow
+// TODO
index 0e3b668..b4af338 100644 (file)
@@ -3,7 +3,7 @@ export const translations =
   "Hall": "Hall",
   "Variants": "Variants",
   "My games": "My games",
-  "Problems": "Problems",
+  "Forum": "Forum",
   "Contact form": "Contact form",
   "Source code": "Source code",
 
@@ -73,23 +73,7 @@ export const translations =
   "Type here": "Type here",
   "Send": "Send",
   "Download PGN": "Download PGN",
-  "Show solution": "Show solution",
-  "Load previous problems": "Load previous problems",
-  "Load next problems": "Load next problems",
-  "New": "New",
-  "Add a problem": "Add a problem",
-  "Full FEN description": "Full FEN description",
-  "Safe HTML tags allowed": "Safe HTML tags allowed",
-  "Instructions": "Instructions",
-  "Describe the problem goal": "Describe the problem goal",
-  "Solution": "Solution",
-  "How to solve the problem?": "How to solve the problem?",
-  "Preview": "Preview",
   "Cancel": "Cancel",
-  "Solve": "Solve",
-  "Bad FEN description": "Bad FEN description",
-  "Empty instructions": "Empty instructions",
-  "Empty solution": "Empty solution",
   "Already playing a game in this variant on another tab!":
     "Already playing a game in this variant on another tab!",
   "Finish your ": "Finish your ",
index 4fa7957..815c2aa 100644 (file)
@@ -3,7 +3,7 @@ export const translations =
   "Hall": "Hall",
   "Variants": "Variantes",
   "My games": "Mes parties",
-  "Problems": "Problèmes",
+  "Forum": "Forum",
   "Contact form": "Formulaire de contact",
   "Source code": "Code source",
 
@@ -67,23 +67,7 @@ export const translations =
   "Type here": "Écrivez ici",
   "Send": "Envoyer",
   "Download PGN": "Télécharger le PGN",
-  "Show solution": "Montrer la solution",
-  "Load previous problems": "Charger les problèmes précédents",
-  "Load next problems": "Charger les problèmes suivants",
-  "New": "Nouveau",
-  "Add a problem": "Ajouter un problème",
-  "Full FEN description": "Description FEN complète",
-  "Safe HTML tags allowed": "HTML 'sûr' autorisé",
-  "Instructions": "Instructions",
-  "Describe the problem goal": "Décrire le but du problème",
-  "Solution": "Solution",
-  "How to solve the problem?": "Comment résoudre le problème ?",
-  "Preview": "Prévisualiser",
   "Cancel": "Annuler",
-  "Solve": "Résoudre",
-  "Bad FEN string": "Mauvaise description FEN",
-  "Empty instructions": "Instructions vides",
-  "Empty solution": "Solution vide",
   "Already playing a game in this variant on another tab!":
     "Une partie est en cours sur cette variante dans un autre onglet !",
   "Finish your ": "Terminez votre ",
index b10be75..1cb9af5 100644 (file)
@@ -12,7 +12,7 @@
     BaseGame(:game="game" :vr="vr" ref="basegame"
       @newmove="processMove" @gameover="gameOver")
     div Names: {{ game.players[0].name }} - {{ game.players[1].name }}
-    div Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }}
+    div(v-if="game.score=='*'") Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }}
     .button-group(v-if="game.mode!='analyze' && game.score=='*'")
       button(@click="offerDraw") Draw
       button(@click="() => abortGame()") Abort
@@ -24,7 +24,6 @@
 // TODO: movelist dans basegame et chat ici
 // ==> après, implémenter/vérifier les passages de challenges + parties en cours
 // observer,
-// + problèmes, habiller et publier. (+ corr...)
 // when send to chat (or a move), reach only this group (send gid along)
 -->
 
@@ -111,6 +110,8 @@ export default {
       this.gameRef.rid = this.$route.query["rid"];
       this.loadGame();
     }
+    // TODO: mode analyse (/analyze/Atomic/rn
+    // ... fen = query[], vname=params[] ...
     // 0.1] Ask server for room composition:
     const funcPollClients = () => {
       this.st.conn.send(JSON.stringify({code:"pollclients"}));
index c630668..d601bf0 100644 (file)
@@ -1,5 +1,68 @@
-    // TODO: AJAX call get corr games (all variants)
-               // si dernier lastMove sur serveur n'est pas le mien et nextColor == moi, alors background orange
-               // ==> background orange si à moi de jouer par corr (sur main index)
-               // (helper: static fonction "GetNextCol()" dans base_rules.js)
-//use GameStorage.getAll()
+<template lang="pug">
+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'") Live games
+        button(@click="display='corr'") Correspondance games
+      GameList(v-show="display=='live'" :games="filterGames('live')"
+        @show-game="showGame")
+      GameList(v-show="display=='corr'" :games="filterGames('corr')"
+        @show-game="showGame")
+</template>
+
+<script>
+// TODO: background orange si à moi de jouer
+// (helper: static fonction "GetNextCol()" dans base_rules.js)
+// use GameStorage.getAll()
+
+import { store } from "@/store";
+import { GameStorage } from "@/utils/gameStorage";
+import { ajax } from "@/utils/ajax";
+import GameList from "@/components/GameList.vue";
+export default {
+  name: "my-games",
+  components: {
+    GameList,
+  },
+  data: function() {
+    return {
+      st: store.state,
+                       display: "live",
+      games: [],
+    };
+  },
+  created: function() {
+    GameStorage.getAll((localGames) => {
+      localGames.forEach((g) => g.type = this.classifyObject(g));
+      Array.prototype.push.apply(this.games, 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);
+      });
+    }
+  },
+  methods: {
+    // TODO: classifyObject and filterGames are redundant (see Hall.vue)
+    classifyObject: function(o) {
+      return (o.timeControl.indexOf('d') === -1 ? "live" : "corr");
+    },
+    filterGames: function(type) {
+      return this.games.filter(g => g.type == type);
+    },
+    showGame: function(g) {
+      // NOTE: we play in this game, since this is "MyGames" page
+      this.$router.push("/game/" + g.id);
+    },
+  },
+};
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="sass">
+/* TODO */
+</style>
index 786f1e7..0949fc9 100644 (file)
@@ -98,19 +98,22 @@ const GameModel =
                                "SELECT gid " +
                                "FROM Players " +
                                "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
-                       db.run(query, (err,gameIds) => {
+                       db.all(query, (err,gameIds) => {
                                if (!!err)
                                        return cb(err);
         gameIds = gameIds || []; //might be empty
                                let gameArray = [];
-                               gameIds.forEach(gidRow => {
-                                       GameModel.getOne(gidRow["gid"], (err2,game) => {
+                               for (let i=0; i<gameIds.length; i++)
+                               {
+                                       GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
                                                if (!!err2)
                                                        return cb(err2);
                                                gameArray.push(game);
+                                               // Call callback function only when gameArray is complete:
+                                               if (i == gameIds.length - 1)
+                                                       return cb(null, gameArray);
                                        });
-                               });
-                               return cb(null, gameArray);
+                               }
                        });
                });
        },
index 9bd9a30..a444acd 100644 (file)
@@ -44,7 +44,7 @@ router.get("/games", access.ajax, (req,res) => {
     const userId = req.query["uid"];
     const excluded = !!req.query["excluded"];
     GameModel.getByUser(userId, excluded, (err,games) => {
-                 if (!!err)
+                       if (!!err)
         return res.json({errmsg: err.errmsg || err.toString()});
                        res.json({games: games});
          });