Fix online indicators between Hall and Game pages
[vchess.git] / client / src / views / Hall.vue
index dadb597..851201f 100644 (file)
@@ -5,7 +5,7 @@ main
     .card.smallpad.small-modal.text-center
       label.modal-close(for="modalInfo")
       h3#infoMessage.section
-        p New game started: #[a(href="/game/" + {{ newGameId }})]
+        p(v-html="infoMessage")
   input#modalNewgame.modal(type="checkbox")
   div(role="dialog" aria-labelledby="titleFenedit")
     .card.smallpad
@@ -90,8 +90,8 @@ export default {
       gdisplay: "live",
       games: [],
       challenges: [],
-      people: [], //(all) online players
-      newGameId: 0,
+      people: [], //people in main hall
+      infoMessage: "",
       newchallenge: {
         fen: "",
         vid: 0,
@@ -110,24 +110,28 @@ export default {
       });
       this.games.forEach(g => {
         if (g.vname == "")
-          g.vname = this.getVname(g.vid)
+          g.vname = this.getVname(g.vid);
       });
     },
   },
   computed: {
     uniquePlayers: function() {
       // Show e.g. "@nonymous (5)", and do nothing on click on anonymous
-      let anonymous = {id:0, name:"@nonymous", count:0};
-      let playerList = [];
+      let anonymous = {name:"@nonymous", count:0};
+      let playerList = {};
       this.people.forEach(p => {
         if (p.id > 0)
-          playerList.push(p);
+        {
+          // We don't count registered users connections: either they are here or not.
+          if (!playerList[p.id])
+            playerList[p.id] = {name: p.name, count: 0};
+        }
         else
           anonymous.count++;
       });
       if (anonymous.count > 0)
-        playerList.push(anonymous);
-      return playerList;
+        playerList[0] = anonymous;
+      return Object.values(playerList);
     },
   },
   created: function() {
@@ -143,50 +147,47 @@ export default {
       else
         localStorage.removeItem("challenge");
     }
-    if (this.st.user.id > 0)
-    {
-      // Ask server for current corr games (all but mines)
-      ajax(
-        "/games",
-        "GET",
-        {uid: this.st.user.id, excluded: true},
-        response => {
-          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, vname: vname});
-          }));
-        }
-      );
-      // Also ask for corr challenges (open + sent to me)
-      ajax(
-        "/challenges",
-        "GET",
-        {uid: this.st.user.id},
-        response => {
-          // Gather all senders names, and then retrieve full identity:
-          // (TODO [perf]: some might be online...)
-          const uids = response.challenges.map(c => { return c.uid });
-          ajax("/users",
-            "GET",
-            { ids: uids.join(",") },
-            response2 => {
-              let names = {};
-              response2.users.forEach(u => {names[u.id] = u.name});
-              this.challenges = this.challenges.concat(
-                response.challenges.map(c => {
-                  // (just players names in fact)
-                  const from = {name: names[c.uid], id: c.uid};
-                  const type = this.classifyObject(c);
-                  const vname = this.getVname(c.vid);
-                  return Object.assign({}, c, {type: type, vname: vname, from: from});
-                })
-              )
-            }
-          );
-        }
-      );
-    }
+    // Ask server for current corr games (all but mines)
+    ajax(
+      "/games",
+      "GET",
+      {uid: this.st.user.id, excluded: true},
+      response => {
+        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, vname: vname});
+        }));
+      }
+    );
+    // Also ask for corr challenges (open + sent to me)
+    ajax(
+      "/challenges",
+      "GET",
+      {uid: this.st.user.id},
+      response => {
+        // Gather all senders names, and then retrieve full identity:
+        // (TODO [perf]: some might be online...)
+        const uids = response.challenges.map(c => { return c.uid });
+        ajax("/users",
+          "GET",
+          { ids: uids.join(",") },
+          response2 => {
+            let names = {};
+            response2.users.forEach(u => {names[u.id] = u.name});
+            this.challenges = this.challenges.concat(
+              response.challenges.map(c => {
+                // (just players names in fact)
+                const from = {name: names[c.uid], id: c.uid};
+                const type = this.classifyObject(c);
+                const vname = this.getVname(c.vid);
+                return Object.assign({}, c, {type: type, vname: vname, from: from});
+              })
+            )
+          }
+        );
+      }
+    );
     // 0.1] Ask server for room composition:
     const funcPollClients = () => {
       this.st.conn.send(JSON.stringify({code:"pollclients"}));
@@ -227,10 +228,10 @@ export default {
       }
       this.$router.push(url);
     },
-    // TODO: ...filter(...)[0].name, one-line, just remove this function
     getVname: function(vid) {
-      const vIdx = this.st.variants.findIndex(v => v.id == vid);
-      return vIdx >= 0 ? this.st.variants[vIdx].name : "";
+      const variant = this.st.variants.find(v => v.id == vid);
+      // this.st.variants might be uninitialized (variant == null)
+      return (!!variant ? variant.name : "");
     },
     getSid: function(pname) {
       const pIdx = this.people.findIndex(pl => pl.name == pname);
@@ -320,26 +321,6 @@ export default {
           }
           break;
         }
-        case "askgame":
-        {
-          // Send my current live game (if any)
-          GameStorage.getCurrent((game) => {
-            if (!!game)
-            {
-              const myGame =
-              {
-                // Minimal game informations:
-                id: game.id,
-                players: game.players.map(p => p.name),
-                vid: game.vid,
-                timeControl: game.timeControl,
-              };
-              this.st.conn.send(JSON.stringify({code:"game",
-                game:myGame, target:data.from}));
-            }
-          });
-          break;
-        }
         case "identity":
         {
           const pIdx = this.people.findIndex(p => p.sid == data.user.sid);
@@ -383,10 +364,12 @@ export default {
             this.startNewGame(data.gameInfo);
           else
           {
-            this.newGameId = data.gameInfo.gameId;
+            this.infoMessage = "New game started: " +
+              "<a href='#/game/" + data.gameInfo.id + "'>" +
+              "#/game/" + data.gameInfo.id + "</a>";
             let modalBox = document.getElementById("modalInfo");
             modalBox.checked = true;
-            setTimeout(() => { modalBox.checked = false; }, 2500);
+            setTimeout(() => { modalBox.checked = false; }, 3000);
           }
           break;
         }
@@ -405,20 +388,20 @@ export default {
         }
         case "connect":
         {
-          this.people.push({name:"", id:0, sid:data.sid});
-          this.st.conn.send(JSON.stringify({code:"askidentity", target:data.sid}));
-          this.st.conn.send(JSON.stringify({code:"askchallenge", target:data.sid}));
-          this.st.conn.send(JSON.stringify({code:"askgame", target:data.sid}));
+          this.people.push({name:"", id:0, sid:data.from});
+          this.st.conn.send(JSON.stringify({code:"askidentity", target:data.from}));
+          this.st.conn.send(JSON.stringify({code:"askchallenge", target:data.from}));
+          this.st.conn.send(JSON.stringify({code:"askgame", target:data.from}));
           break;
         }
         case "disconnect":
         {
-          ArrayFun.remove(this.people, p => p.sid == data.sid);
+          ArrayFun.remove(this.people, p => p.sid == data.from);
           // Also remove all challenges sent by this player:
-          ArrayFun.remove(this.challenges, c => c.from.sid == data.sid);
+          ArrayFun.remove(this.challenges, c => c.from.sid == data.from);
           // And all live games where he plays and no other opponent is online
           ArrayFun.remove(this.games, g =>
-            g.type == "live" && (g.players.every(p => p.sid == data.sid
+            g.type == "live" && (g.players.every(p => p.sid == data.from
               || !this.people.some(pl => pl.sid == p.sid))), "all");
           break;
         }
@@ -453,7 +436,8 @@ export default {
         chall.vname = vname;
         chall.from = this.people[0]; //avoid sending email
         this.challenges.push(chall);
-        localStorage.setItem("challenge", JSON.stringify(chall));
+        if (ctype == "live")
+          localStorage.setItem("challenge", JSON.stringify(chall));
         document.getElementById("modalNewgame").checked = false;
       };
       const cIdx = this.challenges.findIndex(
@@ -490,14 +474,12 @@ export default {
       }
     },
     clickChallenge: function(c) {
-      // In all cases, the challenge is consumed:
-      ArrayFun.remove(this.challenges, ch => ch.id == c.id);
-      // NOTE: deletechallenge event might be redundant (but it's easier this way)
-      this.sendSomethingTo((!!c.to ? c.from : null), "deletechallenge", {cid:c.id});
       const myChallenge = (c.from.sid == this.st.user.sid //live
         || (this.st.user.id > 0 && c.from.id == this.st.user.id)); //corr
       if (!myChallenge)
       {
+        if (c.type == "corr" && this.st.user.id <= 0)
+          return alert("Please log in to accept corr challenges");
         c.accepted = true;
         if (!!c.to) //c.to == this.st.user.name (connected)
         {
@@ -518,7 +500,6 @@ export default {
       }
       else //my challenge
       {
-        localStorage.removeItem("challenge");
         if (c.type == "corr")
         {
           ajax(
@@ -527,7 +508,13 @@ export default {
             {id: c.id}
           );
         }
+        else //live
+          localStorage.removeItem("challenge");
       }
+      // In (almost) all cases, the challenge is consumed:
+      ArrayFun.remove(this.challenges, ch => ch.id == c.id);
+      // NOTE: deletechallenge event might be redundant (but it's easier this way)
+      this.sendSomethingTo((!!c.to ? c.from : null), "deletechallenge", {cid:c.id});
     },
     // NOTE: when launching game, the challenge is already deleted
     launchGame: async function(c) {
@@ -536,10 +523,11 @@ export default {
       // These game informations will be sent to other players
       const gameInfo =
       {
-        gameId: getRandString(),
+        id: getRandString(),
         fen: c.fen || V.GenRandInitFen(),
         players: shuffle([c.from, c.seat]), //white then black
         vid: c.vid,
+        vname: c.vname, //theoretically vid is enough, but much easier with vname
         timeControl: c.timeControl,
       };
       let target = c.from.sid; //may not be defined if corr + offline opp
@@ -549,29 +537,47 @@ export default {
         if (!!opponent)
           target = opponent.sid
       }
-      if (!!target) //opponent is online
-      {
-        this.st.conn.send(JSON.stringify({code:"newgame",
-          gameInfo:gameInfo, target:target, cid:c.id}));
-      }
+      const tryNotifyOpponent = () => {
+        if (!!target) //opponent is online
+        {
+          this.st.conn.send(JSON.stringify({code:"newgame",
+            gameInfo:gameInfo, target:target, cid:c.id}));
+        }
+      };
       if (c.type == "live")
+      {
+        tryNotifyOpponent();
         this.startNewGame(gameInfo);
+      }
       else //corr: game only on server
       {
         ajax(
           "/games",
           "POST",
           {gameInfo: gameInfo, cid: c.id}, //cid useful to delete challenge
-          response => { this.$router.push("/game/" + response.gameId); }
+          response => {
+            gameInfo.id = response.gameId;
+            tryNotifyOpponent();
+            this.$router.push("/game/" + response.gameId);
+          }
         );
       }
+      // Send game info to everyone except opponent (and me)
+      this.st.conn.send(JSON.stringify({code:"game",
+        game: { //minimal game info:
+          id: gameInfo.id,
+          players: gameInfo.players.map(p => p.name),
+          vid: gameInfo.vid,
+          timeControl: gameInfo.timeControl,
+        },
+        oppsid: target}));
     },
     // NOTE: for live games only (corr games start on the server)
     startNewGame: function(gameInfo) {
       const game = Object.assign({}, gameInfo, {
         // (other) Game infos: constant
         fenStart: gameInfo.fen,
-        created: Date.now(),
+        added: Date.now(),
         // Game state (including FEN): will be updated
         moves: [],
         clocks: [-1, -1], //-1 = unstarted
@@ -581,7 +587,7 @@ export default {
       GameStorage.add(game);
       if (this.st.settings.sound >= 1)
         new Audio("/sounds/newgame.mp3").play().catch(err => {});
-      this.$router.push("/game/" + gameInfo.gameId);
+      this.$router.push("/game/" + gameInfo.id);
     },
   },
 };