Finished. Now some last styling
[vchess.git] / client / src / views / Hall.vue
index 851201f..7b42854 100644 (file)
@@ -7,8 +7,9 @@ main
       h3#infoMessage.section
         p(v-html="infoMessage")
   input#modalNewgame.modal(type="checkbox")
-  div(role="dialog" aria-labelledby="titleFenedit")
-    .card.smallpad
+  div(role="dialog" data-checkbox="modalNewgame"
+      aria-labelledby="titleFenedit")
+    .card.smallpad(@keyup.enter="newChallenge")
       label#closeNewgame.modal-close(for="modalNewgame")
       fieldset
         label(for="selectVariant") {{ st.tr["Variant"] }}
@@ -26,8 +27,8 @@ main
         input#inputFen(type="text" v-model="newchallenge.fen")
       button(@click="newChallenge") {{ st.tr["Send challenge"] }}
   .row
-    .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
-      button(onClick="doClick('modalNewgame')") New game
+    .col-sm-12
+      button#newGame(onClick="doClick('modalNewgame')") New game
   .row
     .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       .collapse
@@ -48,13 +49,13 @@ main
             button(@click="pdisplay='players'") Players
             button(@click="pdisplay='chat'") Chat
           #players(v-show="pdisplay=='players'")
-            h3 Online players
-            .player(v-for="p in uniquePlayers" @click="tryChallenge(p)"
-              :class="{anonymous: !!p.count}"
-            )
-              | {{ p.name + (!!p.count ? " ("+p.count+")" : "") }}
+            p.text-center(v-for="p in uniquePlayers")
+              span(:class="{anonymous: !!p.count}")
+                | {{ (p.name || '@nonymous') + (!!p.count ? " ("+p.count+")" : "") }}
+              button.player-action(v-if="!p.count" @click="challOrWatch(p,$event)")
+                | {{ whatPlayerDoes(p) }}
           #chat(v-show="pdisplay=='chat'")
-            h3 Chat (TODO)
+            Chat(:players="[]")
         input#gameSection(type="radio" aria-hidden="true" name="accordion")
         label(for="gameSection" aria-hidden="true") Games
         div
@@ -73,12 +74,14 @@ import { checkChallenge } from "@/data/challengeCheck";
 import { ArrayFun } from "@/utils/array";
 import { ajax } from "@/utils/ajax";
 import { getRandString, shuffle } from "@/utils/alea";
+import Chat from "@/components/Chat.vue";
 import GameList from "@/components/GameList.vue";
 import ChallengeList from "@/components/ChallengeList.vue";
 import { GameStorage } from "@/utils/gameStorage";
 export default {
   name: "my-hall",
   components: {
+    Chat,
     GameList,
     ChallengeList,
   },
@@ -90,7 +93,7 @@ export default {
       gdisplay: "live",
       games: [],
       challenges: [],
-      people: [], //people in main hall
+      people: {}, //people in main hall
       infoMessage: "",
       newchallenge: {
         fen: "",
@@ -117,14 +120,14 @@ export default {
   computed: {
     uniquePlayers: function() {
       // Show e.g. "@nonymous (5)", and do nothing on click on anonymous
-      let anonymous = {name:"@nonymous", count:0};
+      let anonymous = {name:"", count:0};
       let playerList = {};
-      this.people.forEach(p => {
+      Object.values(this.people).forEach(p => {
         if (p.id > 0)
         {
           // 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};
+            playerList[p.id] = {name: p.name};
         }
         else
           anonymous.count++;
@@ -137,7 +140,7 @@ export default {
   created: function() {
     // Always add myself to players' list
     const my = this.st.user;
-    this.people.push({sid:my.sid, id:my.id, name:my.name});
+    this.$set(this.people, my.sid, {id:my.id, name:my.name});
     // Retrieve live challenge (not older than 30 minute) if any:
     const chall = JSON.parse(localStorage.getItem("challenge") || "false");
     if (!!chall)
@@ -221,11 +224,7 @@ export default {
       // ==> Moves sent by connected remote player(s) if live game
       let url = "/game/" + g.id;
       if (g.type == "live")
-      {
-        const remotes = g.players.filter(p => this.people.some(pl => pl.sid == p.sid));
-        const rIdx = (remotes.length == 1 ? 0 : Math.floor(Math.random()*2));
-        url += "?rid=" + remotes[rIdx].sid;
-      }
+        url += "?rid=" + g.rid;
       this.$router.push(url);
     },
     getVname: function(vid) {
@@ -233,13 +232,10 @@ export default {
       // this.st.variants might be uninitialized (variant == null)
       return (!!variant ? variant.name : "");
     },
-    getSid: function(pname) {
-      const pIdx = this.people.findIndex(pl => pl.name == pname);
-      return (pIdx === -1 ? null : this.people[pIdx].sid);
-    },
-    getPname: function(sid) {
-      const pIdx = this.people.findIndex(pl => pl.sid == sid);
-      return (pIdx === -1 ? null : this.people[pIdx].name);
+    whatPlayerDoes: function(p) {
+      if (this.games.some(g => g.players.some(pl => pl.sid == p.sid)))
+        return "Playing";
+      return "Challenge"; //player is available
     },
     sendSomethingTo: function(to, code, obj, warnDisconnected) {
       const doSend = (code, obj, sid) => {
@@ -253,7 +249,8 @@ export default {
       if (!!to)
       {
         // Challenge with targeted players
-        const targetSid = this.getSid(to);
+        const targetSid =
+          Object.keys(this.people).find(sid => this.people[sid].name == to);
         if (!targetSid)
         {
           if (!!warnDisconnected)
@@ -265,9 +262,9 @@ export default {
       else
       {
         // Open challenge: send to all connected players (except us)
-        this.people.forEach(p => {
-          if (p.sid != this.st.user.sid) //only sid is always set
-            doSend(code, obj, p.sid);
+        Object.keys(this.people).forEach(sid => {
+          if (sid != this.st.user.sid)
+            doSend(code, obj, sid);
         });
       }
     },
@@ -276,16 +273,20 @@ export default {
       const data = JSON.parse(msg.data);
       switch (data.code)
       {
+        case "duplicate":
+          alert("Warning: duplicate 'offline' connection");
+          break;
         // 0.2] Receive clients list (just socket IDs)
         case "pollclients":
         {
           data.sockIds.forEach(sid => {
-            this.people.push({sid:sid, id:0, name:""});
+            this.$set(this.people, sid, {id:0, name:""});
             // Ask identity, challenges and game(s)
             this.st.conn.send(JSON.stringify({code:"askidentity", target:sid}));
             this.st.conn.send(JSON.stringify({code:"askchallenge", target:sid}));
-            this.st.conn.send(JSON.stringify({code:"askgame", target:sid}));
           });
+          // Also ask current games to all playing peers (TODO: some design issue)
+          this.st.conn.send(JSON.stringify({code:"askgames"}));
           break;
         }
         case "askidentity":
@@ -293,12 +294,23 @@ export default {
           // Request for identification: reply if I'm not anonymous
           if (this.st.user.id > 0)
           {
-            this.st.conn.send(JSON.stringify(
-              // people[0] instead of st.user to avoid sending email
-              {code:"identity", user:this.people[0], target:data.from}));
+            this.st.conn.send(JSON.stringify({code:"identity",
+              user: {
+                // NOTE: decompose to avoid revealing email
+                name: this.st.user.name,
+                sid: this.st.user.sid,
+                id: this.st.user.id,
+              },
+              target:data.from}));
           }
           break;
         }
+        case "identity":
+        {
+          this.$set(this.people, data.user.sid,
+            {id: data.user.id, name: data.user.name});
+          break;
+        }
         case "askchallenge":
         {
           // Send my current live challenge (if any)
@@ -321,20 +333,13 @@ export default {
           }
           break;
         }
-        case "identity":
-        {
-          const pIdx = this.people.findIndex(p => p.sid == data.user.sid);
-          this.people[pIdx].id = data.user.id;
-          this.people[pIdx].name = data.user.name;
-          break;
-        }
         case "challenge":
         {
           // Receive challenge from some player (+sid)
           let newChall = data.chall;
           newChall.type = this.classifyObject(data.chall);
-          const pIdx = this.people.findIndex(p => p.sid == data.from);
-          newChall.from = this.people[pIdx]; //may be anonymous
+          newChall.from =
+            Object.assign({sid:data.from}, this.people[data.from]);
           newChall.added = Date.now(); //TODO: this is reception timestamp, not creation
           newChall.vname = this.getVname(newChall.vid);
           this.challenges.push(newChall);
@@ -375,7 +380,7 @@ export default {
         }
         case "refusechallenge":
         {
-          alert(this.getPname(data.from) + " declined your challenge");
+          alert(this.people[data.from].name + " declined your challenge");
           ArrayFun.remove(this.challenges, c => c.id == data.cid);
           break;
         }
@@ -388,7 +393,7 @@ export default {
         }
         case "connect":
         {
-          this.people.push({name:"", id:0, sid:data.from});
+          this.$set(this.people, data.from, {name:"", id:0});
           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}));
@@ -396,13 +401,13 @@ export default {
         }
         case "disconnect":
         {
-          ArrayFun.remove(this.people, p => p.sid == data.from);
+          this.$delete(this.people, data.from);
           // Also remove all challenges sent by this player:
           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.from
-              || !this.people.some(pl => pl.sid == p.sid))), "all");
+              || !this.people[p.sid])), "all");
           break;
         }
       }
@@ -414,10 +419,25 @@ export default {
       this.newchallenge.to = player.name;
       doClick("modalNewgame");
     },
+    challOrWatch: function(p, e) {
+      switch (e.target.innerHTML)
+      {
+        case "Challenge":
+          this.tryChallenge(p);
+          break;
+        case "Playing":
+          // NOTE: this search for game was already done for rendering
+          this.showGame(this.games.find(
+            g => g.players.some(pl => pl.sid == p.sid)));
+          break;
+      };
+    },
     newChallenge: async function() {
       const vname = this.getVname(this.newchallenge.vid);
       const vModule = await import("@/variants/" + vname + ".js");
       window.V = vModule.VariantRules;
+      if (!!this.newchallenge.timeControl.match(/^[0-9]+$/))
+        this.newchallenge.timeControl += "+0"; //assume minutes, no increment
       const error = checkChallenge(this.newchallenge);
       if (!!error)
         return alert(error);
@@ -434,7 +454,11 @@ export default {
         // NOTE: vname and type are redundant (can be deduced from timeControl + vid)
         chall.type = ctype;
         chall.vname = vname;
-        chall.from = this.people[0]; //avoid sending email
+        chall.from = { //decompose to avoid revealing email
+          sid: this.st.user.sid,
+          id: this.st.user.id,
+          name: this.st.user.name,
+        };
         this.challenges.push(chall);
         if (ctype == "live")
           localStorage.setItem("challenge", JSON.stringify(chall));
@@ -488,7 +512,11 @@ export default {
         }
         if (c.accepted)
         {
-          c.seat = this.people[0]; //== this.st.user, avoid revealing email
+          c.seat = { //again, avoid c.seat = st.user to not reveal email
+            sid: this.st.user.sid,
+            id: this.st.user.id,
+            name: this.st.user.name,
+          };
           this.launchGame(c);
         }
         else
@@ -533,9 +561,8 @@ export default {
       let target = c.from.sid; //may not be defined if corr + offline opp
       if (!target)
       {
-        const opponent = this.people.find(p => p.id == c.from.id);
-        if (!!opponent)
-          target = opponent.sid
+        target = Object.keys(this.people).find(sid =>
+          this.people[sid].id == c.from.id);
       }
       const tryNotifyOpponent = () => {
         if (!!target) //opponent is online
@@ -594,5 +621,15 @@ export default {
 </script>
 
 <style lang="sass">
-// TODO
+#newGame
+  display: block
+  margin: 10px auto 5px auto
+#chat > .card
+  max-width: 100%
+  margin: 0;
+  border: none;
+.anonymous
+  font-style: italic
+button.player-action
+  margin-left: 20px
 </style>