loadMore button for my local games as well
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 19 Mar 2020 13:24:01 +0000 (14:24 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 19 Mar 2020 13:24:01 +0000 (14:24 +0100)
client/src/App.vue
client/src/utils/compgameStorage.js
client/src/utils/gameStorage.js
client/src/views/Game.vue
client/src/views/Hall.vue
client/src/views/MyGames.vue
client/src/views/Problems.vue

index d8ae21d..c71ebfc 100644 (file)
@@ -279,10 +279,19 @@ footer
     padding: 5px 0
 
 .menuitem.somenews
+  animation: blinkNews 1s infinite;
   color: red
   &:link, &:visited, &:hover
     color: red
 
+@keyframes blinkNews
+  0%, 49%
+    background-color: yellow
+    padding: 3px
+  50%, 100%
+    background-color: grey
+    padding: 3px
+
 // Styles for diagrams and board (partial).
 // TODO: where to put that ?
 
index 05a2074..a5c58af 100644 (file)
@@ -16,28 +16,28 @@ function dbOperation(callback) {
 
   DBOpenRequest.onerror = function(event) {
     alert(store.state.tr["Database error: stop private browsing, or update your browser"]);
-    callback("error",null);
+    callback("error", null);
   };
 
   DBOpenRequest.onsuccess = function(event) {
     db = DBOpenRequest.result;
-    callback(null,db);
+    callback(null, db);
     db.close();
   };
 
   DBOpenRequest.onupgradeneeded = function(event) {
     let db = event.target.result;
-    let objectStore = db.createObjectStore("compgames", { keyPath: "vname" });
+    db.createObjectStore("compgames", { keyPath: "vname" });
   };
 }
 
 export const CompgameStorage = {
   add: function(game) {
     dbOperation((err,db) => {
-      if (err)
-        return;
-      let transaction = db.transaction("compgames", "readwrite");
-      let objectStore = transaction.objectStore("compgames");
+      if (err) return;
+      let objectStore = db
+        .transaction("compgames", "readwrite")
+        .objectStore("compgames");
       objectStore.add(game);
     });
   },
@@ -66,7 +66,9 @@ export const CompgameStorage = {
   // NOTE: need callback because result is obtained asynchronously
   get: function(gameId, callback) {
     dbOperation((err,db) => {
-      let objectStore = db.transaction("compgames").objectStore("compgames");
+      let objectStore = db
+        .transaction("compgames", "readonly")
+        .objectStore("compgames");
       objectStore.get(gameId).onsuccess = function(event) {
         callback(event.target.result);
       };
@@ -77,8 +79,9 @@ export const CompgameStorage = {
   remove: function(gameId) {
     dbOperation((err,db) => {
       if (!err) {
-        let transaction = db.transaction(["compgames"], "readwrite");
-        transaction.objectStore("compgames").delete(gameId);
+        db.transaction("compgames", "readwrite")
+          .objectStore("compgames")
+          .delete(gameId);
       }
     });
   }
index 1298667..fc179f1 100644 (file)
@@ -23,19 +23,22 @@ function dbOperation(callback) {
 
   DBOpenRequest.onerror = function(event) {
     alert(store.state.tr["Database error: stop private browsing, or update your browser"]);
-    callback("error",null);
+    callback("error", null);
   };
 
   DBOpenRequest.onsuccess = function() {
     db = DBOpenRequest.result;
-    callback(null,db);
+    callback(null, db);
     db.close();
   };
 
   DBOpenRequest.onupgradeneeded = function(event) {
     let db = event.target.result;
     let objectStore = db.createObjectStore("games", { keyPath: "id" });
-    objectStore.createIndex("score", "score"); //to search by game result
+    // To sarch games by score (useful for running games)
+    objectStore.createIndex("score", "score", { unique: false });
+    // To search by date intervals. Two games cannot start at the same time
+    objectStore.createIndex("created", "created", { unique: true });
   };
 }
 
@@ -49,13 +52,14 @@ export const GameStorage = {
       }
       let transaction = db.transaction("games", "readwrite");
       transaction.oncomplete = function() {
-        callback(); //everything's fine
+        // Everything's fine
+        callback();
       };
       transaction.onerror = function(err) {
-        callback(err); //duplicate key error (most likely)
+        // Duplicate key error (most likely)
+        callback(err);
       };
-      let objectStore = transaction.objectStore("games");
-      objectStore.add(game);
+      transaction.objectStore("games").add(game);
     });
   },
 
@@ -82,15 +86,20 @@ export const GameStorage = {
     });
   },
 
-  // Retrieve all local games (running, completed, imported...)
-  getAll: function(callback) {
+  // Retrieve (all) running local games
+  getRunning: function(callback) {
     dbOperation((err,db) => {
-      let objectStore = db.transaction("games").objectStore("games");
+      let objectStore = db
+        .transaction("games", "readonly")
+        .objectStore("games");
+      let index = objectStore.index("score");
+      const range = IDBKeyRange.only("*");
       let games = [];
-      objectStore.openCursor().onsuccess = function(event) {
+      index.openCursor(range).onsuccess = function(event) {
         let cursor = event.target.result;
-        // if there is still another cursor to go, keep running this code
-        if (cursor) {
+        if (!cursor) callback(games);
+        else {
+          // If there is still another cursor to go, keep running this code
           let g = cursor.value;
           // Do not retrieve moves or clocks (unused in list mode)
           g.movesCount = g.moves.length;
@@ -99,7 +108,41 @@ export const GameStorage = {
           delete g.initime;
           games.push(g);
           cursor.continue();
-        } else callback(games);
+        }
+      };
+    });
+  },
+
+  // Retrieve completed local games
+  getNext: function(upperDt, callback) {
+    dbOperation((err,db) => {
+      let objectStore = db
+        .transaction("games", "readonly")
+        .objectStore("games");
+      let index = objectStore.index("created");
+      const range = IDBKeyRange.upperBound(upperDt);
+      let games = [];
+      index.openCursor(range).onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          // Most recent games first:
+          games = games.sort((g1, g2) => g2.created - g1.created);
+          // TODO: 20 games showed per request is arbitrary
+          callback(games.slice(0, 20));
+        }
+        else {
+          // If there is still another cursor to go, keep running this code
+          let g = cursor.value;
+          if (g.score != "*") {
+            // Do not retrieve moves or clocks (unused in list mode)
+            g.movesCount = g.moves.length;
+            delete g.moves;
+            delete g.clocks;
+            delete g.initime;
+            games.push(g);
+          }
+          cursor.continue();
+        }
       };
     });
   },
@@ -121,7 +164,7 @@ export const GameStorage = {
   remove: function(gameId, callback) {
     dbOperation((err,db) => {
       if (!err) {
-        let transaction = db.transaction(["games"], "readwrite");
+        let transaction = db.transaction("games", "readwrite");
         transaction.oncomplete = function() {
           callback(); //everything's fine
         };
index 68c8c94..1a86d9b 100644 (file)
@@ -445,6 +445,8 @@ export default {
       const data = JSON.parse(msg.data);
       switch (data.code) {
         case "pollclients":
+          // TODO: shuffling and random filtering on server, if
+          // the room is really crowded.
           data.sockIds.forEach(sid => {
             if (sid != this.st.user.sid) {
               this.people[sid] = { focus: true };
index d11da8b..47a8697 100644 (file)
@@ -585,6 +585,8 @@ export default {
           // Since people can be both in Hall and Game,
           // need to track "askIdentity" requests:
           let identityAsked = {};
+          // TODO: shuffling and random filtering on server, if
+          // the room is really crowded.
           data.sockIds.forEach(s => {
             const page = s.page || "/";
             if (s.sid != this.st.user.sid && !identityAsked[s.sid]) {
index a9e69f6..6078394 100644 (file)
@@ -14,18 +14,18 @@ main
         @show-game="showGame"
         @abortgame="abortGame"
       )
-      div(v-show="display=='corr'")
-        GameList(
-          ref="corrgames"
-          :games="corrGames"
-          @show-game="showGame"
-          @abortgame="abortGame"
-        )
-        button#loadMoreBtn(
-          v-if="hasMore"
-          @click="loadMore()"
-        )
-          | {{ st.tr["Load more"] }}
+      GameList(
+        v-show="display=='corr'"
+        ref="corrgames"
+        :games="corrGames"
+        @show-game="showGame"
+        @abortgame="abortGame"
+      )
+      button#loadMoreBtn(
+        v-show="hasMore[display]"
+        @click="loadMore(display)"
+      )
+        | {{ st.tr["Load more"] }}
 </template>
 
 <script>
@@ -47,10 +47,13 @@ export default {
       display: "live",
       liveGames: [],
       corrGames: [],
-      // timestamp of last showed (oldest) corr game:
-      cursor: Number.MAX_SAFE_INTEGER,
+      // timestamp of last showed (oldest) game:
+      cursor: {
+        live: Number.MAX_SAFE_INTEGER,
+        corr: Number.MAX_SAFE_INTEGER
+      },
       // hasMore == TRUE: a priori there could be more games to load
-      hasMore: true,
+      hasMore: { live: true, corr: true },
       conn: null,
       connexionString: ""
     };
@@ -93,7 +96,7 @@ export default {
       }
       this.setDisplay(showType);
     };
-    GameStorage.getAll(localGames => {
+    GameStorage.getRunning(localGames => {
       localGames.forEach(g => g.type = "live");
       this.decorate(localGames);
       this.liveGames = localGames;
@@ -113,29 +116,19 @@ export default {
               });
               this.decorate(this.corrGames);
               // Now ask completed games (partial list)
-              ajax(
-                "/completedgames",
-                "GET",
-                {
-                  credentials: true,
-                  data: { cursor: this.cursor },
-                  success: (res2) => {
-                    const L = res2.games.length;
-                    if (L > 0) {
-                      this.cursor = res2.games[L - 1].created;
-                      let completedGames = res2.games;
-                      completedGames.forEach(g => g.type = "corr");
-                      this.decorate(completedGames);
-                      this.corrGames = this.corrGames.concat(completedGames);
-                      adjustAndSetDisplay();
-                    }
-                  }
-                }
+              this.loadMore(
+                "live",
+                () => this.loadMore("corr", adjustAndSetDisplay)
               );
             }
           }
         );
-      } else adjustAndSetDisplay();
+      } else {
+        this.loadMore(
+          "live",
+          () => this.loadMore("corr", adjustAndSetDisplay)
+        );
+      }
     });
   },
   beforeDestroy: function() {
@@ -287,25 +280,40 @@ export default {
         );
       }
     },
-    loadMore: function() {
-      ajax(
-        "/completedgames",
-        "GET",
-        {
-          credentials: true,
-          data: { cursor: this.cursor },
-          success: (res) => {
-            const L = res.games.length;
-            if (L > 0) {
-              this.cursor = res.games[L - 1].created;
-              let moreGames = res.games;
-              moreGames.forEach(g => g.type = "corr");
-              this.decorate(moreGames);
-              this.corrGames = this.corrGames.concat(moreGames);
-            } else this.hasMore = false;
+    loadMore: function(type, cb) {
+      if (type == "corr") {
+        ajax(
+          "/completedgames",
+          "GET",
+          {
+            credentials: true,
+            data: { cursor: this.cursor["corr"] },
+            success: (res) => {
+              const L = res.games.length;
+              if (L > 0) {
+                this.cursor["corr"] = res.games[L - 1].created;
+                let moreGames = res.games;
+                moreGames.forEach(g => g.type = "corr");
+                this.decorate(moreGames);
+                this.corrGames = this.corrGames.concat(moreGames);
+              } else this.hasMore["corr"] = false;
+              if (!!cb) cb();
+            }
           }
-        }
-      );
+        );
+      } else if (type == "live") {
+        GameStorage.getNext(this.cursor["live"], localGames => {
+          const L = localGames.length;
+          if (L > 0) {
+            // Add "-1" because IDBKeyRange.upperBound seems to include boundary
+            this.cursor["live"] = localGames[L - 1].created - 1;
+            localGames.forEach(g => g.type = "live");
+            this.decorate(localGames);
+            this.liveGames = this.liveGames.concat(localGames);
+          } else this.hasMore["live"] = false;
+          if (!!cb) cb();
+        });
+      }
     }
   }
 };
index cfc0637..2d5526d 100644 (file)
@@ -144,11 +144,11 @@ export default {
       problems: { "mine": [], "others": [] },
       // timestamp of oldest showed problem:
       cursor: {
-        "mine": Number.MAX_SAFE_INTEGER,
-        "others": Number.MAX_SAFE_INTEGER
+        mine: Number.MAX_SAFE_INTEGER,
+        others: Number.MAX_SAFE_INTEGER
       },
       // hasMore == TRUE: a priori there could be more problems to load
-      hasMore: { "mine": true, "others": true },
+      hasMore: { mine: true, others: true },
       onlyMine: false,
       showOne: false,
       infoMsg: "",