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 ?
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);
});
},
// 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);
};
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);
}
});
}
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 });
};
}
}
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);
});
},
});
},
- // 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;
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();
+ }
};
});
},
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
};
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 };
// 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]) {
@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>
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: ""
};
}
this.setDisplay(showType);
};
- GameStorage.getAll(localGames => {
+ GameStorage.getRunning(localGames => {
localGames.forEach(g => g.type = "live");
this.decorate(localGames);
this.liveGames = localGames;
});
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() {
);
}
},
- 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();
+ });
+ }
}
}
};
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: "",