Refactor models (merge Players in Games), add cursor to correspondance games. Finishe...
[vchess.git] / client / src / views / Hall.vue
index 829e5b7..b9262d1 100644 (file)
@@ -185,12 +185,17 @@ main
           :showBoth="true"
           @show-game="showGame"
         )
-        GameList(
-          v-show="gdisplay=='corr'"
-          :games="filterGames('corr')"
-          :showBoth="true"
-          @show-game="showGame"
-        )
+        div(v-show="gdisplay=='corr'")
+          GameList(
+            :games="filterGames('corr')"
+            :showBoth="true"
+            @show-game="showGame"
+          )
+          button#loadMoreBtn(
+            v-if="hasMore"
+            @click="loadMore()"
+          )
+            | {{ st.tr["Load more"] }}
 </template>
 
 <script>
@@ -218,6 +223,10 @@ export default {
       st: store.state,
       cdisplay: "live", //or corr
       gdisplay: "live",
+      // timestamp of last showed (oldest) corr game:
+      cursor: Number.MAX_SAFE_INTEGER,
+      // hasMore == TRUE: a priori there could be more games to load
+      hasMore: true,
       games: [],
       challenges: [],
       people: {},
@@ -227,7 +236,7 @@ export default {
         vid: parseInt(localStorage.getItem("vid")) || 0,
         to: "", //name of challenged player (if any)
         cadence: localStorage.getItem("cadence") || "",
-        randomness: parseInt(localStorage.getItem("randomness")) || 2,
+        randomness: parseInt(localStorage.getItem("challRandomness")) || 2,
         // VariantRules object, stored to not interfere with
         // diagrams of targetted challenges:
         V: null,
@@ -280,22 +289,72 @@ export default {
         pages: [{ path: "/", focus: true }]
       }
     );
+    const connectAndPoll = () => {
+      this.send("connect");
+      this.send("pollclientsandgamers");
+    };
+    // Initialize connection
+    this.connexionString =
+      params.socketUrl +
+      "/?sid=" +
+      this.st.user.sid +
+      "&id=" +
+      this.st.user.id +
+      "&tmpId=" +
+      getRandString() +
+      "&page=" +
+      // Hall: path is "/" (could be hard-coded as well)
+      encodeURIComponent(this.$route.path);
+    this.conn = new WebSocket(this.connexionString);
+    this.conn.onopen = connectAndPoll;
+    this.conn.onmessage = this.socketMessageListener;
+    this.conn.onclose = this.socketCloseListener;
+  },
+  mounted: function() {
+    document.addEventListener('visibilitychange', this.visibilityChange);
+    ["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => {
+      document.getElementById(eltName)
+        .addEventListener("click", processModalClick);
+    });
+    document.querySelectorAll("#predefinedCadences > button").forEach(b => {
+      b.addEventListener("click", () => {
+        this.newchallenge.cadence = b.innerHTML;
+      });
+    });
+    const dispCorr = this.$route.query["disp"];
+    const showCtype =
+      dispCorr || localStorage.getItem("type-challenges") || "live";
+    const showGtype =
+      dispCorr || localStorage.getItem("type-games") || "live";
+    this.setDisplay('c', showCtype);
+    this.setDisplay('g', showGtype);
     // Ask server for current corr games (all but mines)
     ajax(
-      "/games",
+      "/observedgames",
       "GET",
       {
-        data: { uid: this.st.user.id, excluded: true },
+        data: {
+          uid: this.st.user.id,
+          cursor: this.cursor
+        },
         success: (response) => {
+          if (
+            response.games.length > 0 &&
+            this.games.length == 0 &&
+            this.gdisplay == "live"
+          ) {
+            document
+              .getElementById("btnGcorr")
+              .classList.add("somethingnew");
+          }
           this.games = this.games.concat(
             response.games.map(g => {
-              const type = this.classifyObject(g);
               const vname = this.getVname(g.vid);
               return Object.assign(
                 {},
                 g,
                 {
-                  type: type,
+                  type: "corr",
                   vname: vname
                 }
               );
@@ -311,6 +370,15 @@ export default {
       {
         data: { uid: this.st.user.id },
         success: (response) => {
+          if (
+            response.challenges.length > 0 &&
+            this.challenges.length == 0 &&
+            this.cdisplay == "live"
+          ) {
+            document
+              .getElementById("btnCcorr")
+              .classList.add("somethingnew");
+          }
           // Gather all senders names, and then retrieve full identity:
           // (TODO [perf]: some might be online...)
           let names = {};
@@ -357,45 +425,6 @@ export default {
         }
       }
     );
-    const connectAndPoll = () => {
-      this.send("connect");
-      this.send("pollclientsandgamers");
-    };
-    // Initialize connection
-    this.connexionString =
-      params.socketUrl +
-      "/?sid=" +
-      this.st.user.sid +
-      "&id=" +
-      this.st.user.id +
-      "&tmpId=" +
-      getRandString() +
-      "&page=" +
-      // Hall: path is "/" (could be hard-coded as well)
-      encodeURIComponent(this.$route.path);
-    this.conn = new WebSocket(this.connexionString);
-    this.conn.onopen = connectAndPoll;
-    this.conn.onmessage = this.socketMessageListener;
-    this.conn.onclose = this.socketCloseListener;
-  },
-  mounted: function() {
-    document.addEventListener('visibilitychange', this.visibilityChange);
-    ["peopleWrap", "infoDiv", "newgameDiv"].forEach(eltName => {
-      document.getElementById(eltName)
-        .addEventListener("click", processModalClick);
-    });
-    document.querySelectorAll("#predefinedCadences > button").forEach(b => {
-      b.addEventListener("click", () => {
-        this.newchallenge.cadence = b.innerHTML;
-      });
-    });
-    const dispCorr = this.$route.query["disp"];
-    const showCtype =
-      dispCorr || localStorage.getItem("type-challenges") || "live";
-    const showGtype =
-      dispCorr || localStorage.getItem("type-games") || "live";
-    this.setDisplay("c", showCtype);
-    this.setDisplay("g", showGtype);
   },
   beforeDestroy: function() {
     document.removeEventListener('visibilitychange', this.visibilityChange);
@@ -619,15 +648,14 @@ export default {
           } else {
             // Remove the matching live game if now unreachable
             const gid = data.page.match(/[a-zA-Z0-9]+$/)[0];
-            const gidx = this.games.findIndex(g => g.id == gid);
-            if (gidx >= 0) {
-              const game = this.games[gidx];
-              if (
-                game.type == "live" &&
-                game.rids.length == 1 &&
-                game.rids[0] == data.from
-              ) {
-                this.games.splice(gidx, 1);
+            // Corr games are always reachable:
+            if (!gid.match(/^[0-9]+$/)) {
+              const gidx = this.games.findIndex(g => g.id == gid);
+              // NOTE: gidx should always be >= 0 (TODO?)
+              if (gidx >= 0) {
+                const game = this.games[gidx];
+                ArrayFun.remove(game.rids, rid => rid == data.from);
+                if (game.rids.length == 0) this.games.splice(gidx, 1);
               }
             }
           }
@@ -658,7 +686,7 @@ export default {
           alert(this.st.tr["New connexion detected: tab now offline"]);
           break;
         case "askidentity": {
-          // Request for identification (TODO: anonymous shouldn't need to reply)
+          // Request for identification
           const me = {
             // Decompose to avoid revealing email
             name: this.st.user.name,
@@ -744,12 +772,12 @@ export default {
           }
           break;
         }
-        case "game": //individual request
-        case "newgame": {
+        case "game": {
+          // Individual request
           const game = data.data;
           // Ignore games where I play (will go in MyGames page)
           if (game.players.every(p =>
-            p.sid != this.st.user.sid || p.uid != this.st.user.id))
+            p.sid != this.st.user.sid && p.id != this.st.user.id))
           {
             let locGame = this.games.find(g => g.id == game.id);
             if (!locGame) {
@@ -813,6 +841,36 @@ export default {
       this.conn.addEventListener("message", this.socketMessageListener);
       this.conn.addEventListener("close", this.socketCloseListener);
     },
+    loadMore: function() {
+      ajax(
+        "/observedgames",
+        "GET",
+        {
+          data: {
+            uid: this.st.user.id,
+            cursor: this.cursor
+          },
+          success: (res) => {
+            if (res.games.length > 0) {
+              const L = res.games.length;
+              this.cursor = res.games[L - 1].created;
+              let moreGames = res.games.map(g => {
+                const vname = this.getVname(g.vid);
+                return Object.assign(
+                  {},
+                  g,
+                  {
+                    type: "corr",
+                    vname: vname
+                  }
+                );
+              });
+              this.games = this.games.concat(moreGames);
+            } else this.hasMore = false;
+          }
+        }
+      );
+    },
     // Challenge lifecycle:
     addChallenge: function(chall) {
       // NOTE about next condition: see "askchallenges" case.
@@ -845,8 +903,7 @@ export default {
       const vModule = await import("@/variants/" + vname + ".js");
       this.newchallenge.V = vModule.VariantRules;
       this.newchallenge.vname = vname;
-      if (!!cb)
-        cb();
+      if (!!cb) cb();
     },
     trySetNewchallDiag: function() {
       if (!this.newchallenge.fen) {
@@ -872,7 +929,7 @@ export default {
       this.newchallenge.vid = pchall.vid;
       this.newchallenge.cadence = pchall.cadence;
       this.newchallenge.randomness = pchall.randomness;
-      this.issueNewChallenge();
+      this.loadNewchallVariant(this.issueNewChallenge);
     },
     issueNewChallenge: async function() {
       if (!!(this.newchallenge.cadence.match(/^[0-9]+$/)))
@@ -890,8 +947,9 @@ export default {
         else if (
           ctype == "live" &&
           Object.values(this.people).every(p => p.name != this.newchallenge.to)
-        )
+        ) {
           error = this.newchallenge.to + " " + this.st.tr["is not online"];
+        }
       }
       if (error) {
         alert(error);
@@ -970,7 +1028,7 @@ export default {
         // Remember cadence  + vid for quicker further challenges:
         localStorage.setItem("cadence", chall.cadence);
         localStorage.setItem("vid", chall.vid);
-        localStorage.setItem("randomness", chall.randomness);
+        localStorage.setItem("challRandomness", chall.randomness);
         document.getElementById("modalNewgame").checked = false;
         // Show the challenge if not on current display
         if (
@@ -1081,16 +1139,11 @@ export default {
         !!c.mycolor
           ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
           : shuffle([c.from, c.seat]);
-      // Convention for players IDs in stored games is 'uid'
-      players.forEach(p => {
-        let pWithUid = p;
-        pWithUid["uid"] = p.id;
-        delete pWithUid["id"];
-      });
       // These game informations will be shared
       let gameInfo = {
         id: getRandString(),
         fen: c.fen || V.GenRandInitFen(c.randomness),
+        randomness: c.randomness, //for rematch
         // White player index 0, black player index 1:
         players: c.mycolor
           ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
@@ -1103,19 +1156,18 @@ export default {
         if (!!oppsid)
           // Opponent is online
           this.send("startgame", { data: gameInfo, target: oppsid });
-        // Send game info (only if live) to everyone except me and opponent
-        // TODO: this double message send could be avoided.
-        this.send("newgame", { data: gameInfo, oppsid: oppsid });
-        // Also to MyGames page:
+        // Notify MyGames page:
         this.send(
           "notifynewgame",
           {
             data: gameInfo,
             targets: gameInfo.players.map(p => {
-              return { sid: p.sid, uid: p.uid };
+              return { sid: p.sid, id: p.id };
             })
           }
         );
+        // NOTE: no need to send the game to the room, since I'll connect
+        // on game just after, the main Hall will be notified.
       };
       if (c.type == "live") {
         notifyNewgame();
@@ -1158,13 +1210,12 @@ export default {
         () => {
           GameStorage.add(game, (err) => {
             // If an error occurred, game is not added: a tab already
-            // added the game and (if focused) is redirected toward it.
-            // If no error and the tab is hidden: do not show anything.
-            if (!err && !document.hidden) {
-              if (this.st.settings.sound)
-                new Audio("/sounds/newgame.flac").play().catch(() => {});
-              this.$router.push("/game/" + gameInfo.id);
-            }
+            // added the game. Maybe a focused one, maybe not.
+            // We know for sure that it emitted the gong start sound.
+            // ==> Do not play it again.
+            if (!err && this.st.settings.sound)
+              new Audio("/sounds/newgame.flac").play().catch(() => {});
+            this.$router.push("/game/" + gameInfo.id);
           });
         },
         document.hidden ? 500 + 1000 * Math.random() : 0
@@ -1276,6 +1327,10 @@ tr > td
   h4
     margin: 5px 0
 
+button#loadMoreBtn
+  margin-top: 0
+  margin-bottom: 0
+
 td.remove-preset
   background-color: lightgrey
   text-align: center