mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-11 08:05:56 +08:00
Merge branch 'leaderboards'
This commit is contained in:
commit
a2a8c01d95
8 changed files with 1582 additions and 118 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -73,7 +73,10 @@ public/css/style.css
|
|||
public/css/style.css.map
|
||||
functions/serviceAccountKey.json
|
||||
functions/serviceAccountKey_live.json
|
||||
functions/serviceAccountKey_copy.json
|
||||
functions/serviceAccountKey_live_copy.json
|
||||
.firebaserc
|
||||
.firebaserc_copy
|
||||
functions/serviceAccountKey_copy.json
|
||||
functions/serviceAccountKey_live_copy.json
|
||||
|
||||
|
|
|
@ -6,25 +6,22 @@
|
|||
"**/.*",
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/soon",
|
||||
"destination": "/",
|
||||
"type": 301
|
||||
}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
],
|
||||
"redirects": [{
|
||||
"source": "/soon",
|
||||
"destination": "/",
|
||||
"type": 301
|
||||
}],
|
||||
"rewrites": [{
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}],
|
||||
"cleanUrls": true,
|
||||
"trailingSlash": false
|
||||
},
|
||||
"functions": {
|
||||
"predeploy": [
|
||||
"npm --prefix \"$RESOURCE_DIR\" run lint"
|
||||
]
|
||||
}
|
||||
}
|
||||
// },
|
||||
// "functions": {
|
||||
// "predeploy": [
|
||||
// "npm --prefix \"$RESOURCE_DIR\" run lint"
|
||||
// ]
|
||||
// }
|
||||
}
|
|
@ -60,7 +60,7 @@ function getAllUsers() {
|
|||
|
||||
function isUsernameValid(name) {
|
||||
if (name === null || name === undefined || name === "") return false;
|
||||
if (/miodec/.test(name)) return false;
|
||||
if (/miodec/.test(name.toLowerCase())) return false;
|
||||
if (name.length > 12) return false;
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
}
|
||||
|
@ -141,63 +141,92 @@ exports.changeName = functions.https.onCall((request, response) => {
|
|||
exports.checkIfNeedsToChangeName = functions.https.onCall(
|
||||
(request, response) => {
|
||||
try {
|
||||
return admin
|
||||
.auth()
|
||||
.getUser(request.uid)
|
||||
.then((requestUser) => {
|
||||
if (!isUsernameValid(requestUser.displayName)) {
|
||||
//invalid name, needs to change
|
||||
console.log(
|
||||
`user ${requestUser.uid} ${requestUser.displayName} needs to change name`
|
||||
);
|
||||
return 1;
|
||||
} else {
|
||||
//valid name, but need to change if not duplicate
|
||||
|
||||
return getAllUsers().then((users) => {
|
||||
let sameName = [];
|
||||
|
||||
//look for name names
|
||||
users.forEach((user) => {
|
||||
if (user.uid !== requestUser.uid) {
|
||||
try {
|
||||
if (
|
||||
user.displayName.toLowerCase() ===
|
||||
requestUser.displayName.toLowerCase()
|
||||
) {
|
||||
sameName.push(user);
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (sameName.length === 0) {
|
||||
return 0;
|
||||
} else {
|
||||
//check when the request user made the account compared to others
|
||||
let earliestTimestamp = 999999999999999;
|
||||
sameName.forEach((sn) => {
|
||||
let ts = new Date(sn.metadata.creationTime).getTime() / 1000;
|
||||
if (ts <= earliestTimestamp) {
|
||||
earliestTimestamp = ts;
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
new Date(requestUser.metadata.creationTime).getTime() / 1000 >
|
||||
earliestTimestamp
|
||||
) {
|
||||
return db
|
||||
.collection("users")
|
||||
.doc(request.uid)
|
||||
.get()
|
||||
.then((doc) => {
|
||||
if (doc.data().name === undefined) {
|
||||
return admin
|
||||
.auth()
|
||||
.getUser(request.uid)
|
||||
.then((requestUser) => {
|
||||
if (!isUsernameValid(requestUser.displayName)) {
|
||||
//invalid name, needs to change
|
||||
console.log(
|
||||
`user ${requestUser.uid} ${requestUser.displayName} needs to change name`
|
||||
);
|
||||
return 2;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
//valid name, but need to change if not duplicate
|
||||
|
||||
return getAllUsers()
|
||||
.then((users) => {
|
||||
let sameName = [];
|
||||
|
||||
//look for name names
|
||||
users.forEach((user) => {
|
||||
if (user.uid !== requestUser.uid) {
|
||||
try {
|
||||
if (
|
||||
user.displayName.toLowerCase() ===
|
||||
requestUser.displayName.toLowerCase()
|
||||
) {
|
||||
sameName.push(user);
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (sameName.length === 0) {
|
||||
db.collection("users")
|
||||
.doc(request.uid)
|
||||
.update({ name: requestUser.displayName })
|
||||
.then(() => {
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
//check when the request user made the account compared to others
|
||||
let earliestTimestamp = 999999999999999;
|
||||
sameName.forEach((sn) => {
|
||||
let ts =
|
||||
new Date(sn.metadata.creationTime).getTime() / 1000;
|
||||
if (ts <= earliestTimestamp) {
|
||||
earliestTimestamp = ts;
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
new Date(
|
||||
requestUser.metadata.creationTime
|
||||
).getTime() /
|
||||
1000 >
|
||||
earliestTimestamp
|
||||
) {
|
||||
console.log(
|
||||
`user ${requestUser.uid} ${requestUser.displayName} needs to change name`
|
||||
);
|
||||
return 2;
|
||||
} else {
|
||||
db.collection("users")
|
||||
.doc(request.uid)
|
||||
.update({ name: requestUser.displayName })
|
||||
.then(() => {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`error getting all users - ${e}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("name is good");
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -326,11 +355,19 @@ function checkIfPB(uid, obj) {
|
|||
});
|
||||
}
|
||||
|
||||
function stdDev(array) {
|
||||
const n = array.length;
|
||||
const mean = array.reduce((a, b) => a + b) / n;
|
||||
return Math.sqrt(
|
||||
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
|
||||
);
|
||||
}
|
||||
|
||||
exports.testCompleted = functions.https.onCall((request, response) => {
|
||||
try {
|
||||
if (request.uid === undefined || request.obj === undefined) {
|
||||
console.error(`error saving result for ${request.uid} - missing input`);
|
||||
return -1;
|
||||
return { resultCode: -999 };
|
||||
}
|
||||
|
||||
let obj = request.obj;
|
||||
|
@ -359,18 +396,75 @@ exports.testCompleted = functions.https.onCall((request, response) => {
|
|||
return -1;
|
||||
}
|
||||
|
||||
let keySpacing = {
|
||||
average:
|
||||
obj.keySpacing.reduce((previous, current) => (current += previous)) /
|
||||
obj.keySpacing.length,
|
||||
sd: stdDev(obj.keySpacing),
|
||||
};
|
||||
|
||||
let keyDuration = {
|
||||
average:
|
||||
obj.keyDuration.reduce((previous, current) => (current += previous)) /
|
||||
obj.keyDuration.length,
|
||||
sd: stdDev(obj.keyDuration),
|
||||
};
|
||||
|
||||
return db
|
||||
.collection("users")
|
||||
.doc(request.uid)
|
||||
.get()
|
||||
.then((ret) => {
|
||||
let userdata = ret.data();
|
||||
let name = userdata.name === undefined ? false : userdata.name;
|
||||
let banned = userdata.banned === undefined ? false : userdata.banned;
|
||||
let verified =
|
||||
userdata.verified === undefined ? false : userdata.verified;
|
||||
|
||||
request.obj.name = name;
|
||||
|
||||
//check keyspacing and duration here
|
||||
if (!verified) {
|
||||
if (
|
||||
keySpacing.sd < 15 ||
|
||||
keyDuration.sd < 15 ||
|
||||
keyDuration.average < 15
|
||||
) {
|
||||
console.error(
|
||||
`possible bot detected by user ${
|
||||
request.uid
|
||||
} ${name} - spacing ${JSON.stringify(
|
||||
keySpacing
|
||||
)} duration ${JSON.stringify(keyDuration)}`
|
||||
);
|
||||
return { resultCode: -2 };
|
||||
}
|
||||
}
|
||||
|
||||
return db
|
||||
.collection(`users/${request.uid}/results`)
|
||||
.add(obj)
|
||||
.then((e) => {
|
||||
return checkIfPB(request.uid, request.obj).then((e) => {
|
||||
if (e) {
|
||||
return Promise.all([
|
||||
checkLeaderboards(request.obj, "global", banned, name),
|
||||
checkLeaderboards(request.obj, "daily", banned, name),
|
||||
checkIfPB(request.uid, request.obj),
|
||||
]).then((values) => {
|
||||
let globallb = values[0].insertedAt;
|
||||
let dailylb = values[1].insertedAt;
|
||||
let ispb = values[2];
|
||||
// console.log(values);
|
||||
|
||||
let returnobj = {
|
||||
resultCode: null,
|
||||
globalLeaderboard: globallb,
|
||||
dailyLeaderboard: dailylb,
|
||||
lbBanned: banned,
|
||||
name: name,
|
||||
};
|
||||
request.obj.keySpacing = "removed";
|
||||
request.obj.keyDuration = "removed";
|
||||
if (ispb) {
|
||||
console.log(
|
||||
`saved result for ${request.uid} (new PB) - ${JSON.stringify(
|
||||
request.obj
|
||||
|
@ -388,33 +482,35 @@ exports.testCompleted = functions.https.onCall((request, response) => {
|
|||
updateDiscordRole(userdata.discordId, Math.round(obj.wpm));
|
||||
return;
|
||||
}
|
||||
return 2;
|
||||
returnobj.resultCode = 2;
|
||||
} else {
|
||||
console.log(
|
||||
`saved result for ${request.uid} - ${JSON.stringify(
|
||||
request.obj
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
returnobj.resultCode = 1;
|
||||
}
|
||||
// console.log(returnobj);
|
||||
return returnobj;
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`error saving result when checking for PB for ${request.uid} - ${e.message}`
|
||||
`error saving result when checking for PB / checking leaderboards for ${request.uid} - ${e.message}`
|
||||
);
|
||||
return -1;
|
||||
return { resultCode: -999 };
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`error saving result when getting user info ${request.uid} - ${e.message}`
|
||||
`error saving result when getting user data for ${request.uid} - ${e.message}`
|
||||
);
|
||||
return -1;
|
||||
return { resultCode: -999 };
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`error saving result for ${request.uid} - ${e}`);
|
||||
return -1;
|
||||
return { resultCode: -999 };
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -634,6 +730,176 @@ function generate(n) {
|
|||
return ("" + number).substring(add);
|
||||
}
|
||||
|
||||
class Leaderboard {
|
||||
constructor(size, mode, mode2, type, starting) {
|
||||
this.size = size;
|
||||
this.board = [];
|
||||
this.mode = mode;
|
||||
this.mode2 = mode2;
|
||||
this.type = type;
|
||||
if (starting !== undefined && starting !== null) {
|
||||
starting.forEach((entry) => {
|
||||
if (entry.mode === this.mode && entry.mode2 === this.mode2) {
|
||||
this.board.push({
|
||||
uid: entry.uid,
|
||||
wpm: parseFloat(entry.wpm),
|
||||
raw: parseFloat(entry.raw),
|
||||
acc: parseFloat(entry.acc),
|
||||
mode: entry.mode,
|
||||
mode2: entry.mode2,
|
||||
timestamp: entry.timestamp,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
this.sortBoard();
|
||||
this.clipBoard();
|
||||
}
|
||||
sortBoard() {
|
||||
this.board.sort((a, b) => {
|
||||
if (a.wpm === b.wpm) {
|
||||
if (a.acc === b.acc) {
|
||||
return a.timestamp - b.timestamp;
|
||||
} else {
|
||||
return b.acc - a.acc;
|
||||
}
|
||||
} else {
|
||||
return b.wpm - a.wpm;
|
||||
}
|
||||
});
|
||||
}
|
||||
clipBoard() {
|
||||
let boardLength = this.board.length;
|
||||
if (boardLength > this.size) {
|
||||
while (this.board.length !== this.size) {
|
||||
this.board.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
logBoard() {
|
||||
console.log(this.board);
|
||||
}
|
||||
removeDuplicates(insertedAt, uid) {
|
||||
//return true if a better result is found
|
||||
let found = false;
|
||||
// let ret;
|
||||
let foundAt = null;
|
||||
if (this.board !== undefined) {
|
||||
this.board.forEach((entry, index) => {
|
||||
if (entry.uid === uid) {
|
||||
if (found) {
|
||||
this.board.splice(index, 1);
|
||||
// if (index > insertedAt) {
|
||||
// //removed old result
|
||||
// ret = false;
|
||||
// } else {
|
||||
// ret = true;
|
||||
// }
|
||||
} else {
|
||||
found = true;
|
||||
foundAt = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// console.log(ret);
|
||||
// return ret;
|
||||
return foundAt;
|
||||
}
|
||||
insert(a) {
|
||||
let insertedAt = -1;
|
||||
if (a.mode === this.mode && a.mode2 === this.mode2) {
|
||||
this.board.forEach((b, index) => {
|
||||
if (insertedAt !== -1) return;
|
||||
if (a.wpm === b.wpm) {
|
||||
if (a.acc === b.acc) {
|
||||
if (a.timestamp < b.timestamp) {
|
||||
this.board.splice(index, 0, {
|
||||
uid: a.uid,
|
||||
name: a.name,
|
||||
wpm: parseFloat(a.wpm),
|
||||
raw: parseFloat(a.rawWpm),
|
||||
acc: parseFloat(a.acc),
|
||||
mode: a.mode,
|
||||
mode2: a.mode2,
|
||||
timestamp: a.timestamp,
|
||||
});
|
||||
insertedAt = index;
|
||||
}
|
||||
} else {
|
||||
if (a.acc > b.acc) {
|
||||
this.board.splice(index, 0, {
|
||||
uid: a.uid,
|
||||
name: a.name,
|
||||
wpm: parseFloat(a.wpm),
|
||||
raw: parseFloat(a.rawWpm),
|
||||
acc: parseFloat(a.acc),
|
||||
mode: a.mode,
|
||||
mode2: a.mode2,
|
||||
timestamp: a.timestamp,
|
||||
});
|
||||
insertedAt = index;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (a.wpm > b.wpm) {
|
||||
this.board.splice(index, 0, {
|
||||
uid: a.uid,
|
||||
name: a.name,
|
||||
wpm: parseFloat(a.wpm),
|
||||
raw: parseFloat(a.rawWpm),
|
||||
acc: parseFloat(a.acc),
|
||||
mode: a.mode,
|
||||
mode2: a.mode2,
|
||||
timestamp: a.timestamp,
|
||||
});
|
||||
insertedAt = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this.board.length < this.size && insertedAt === -1) {
|
||||
this.board.push({
|
||||
uid: a.uid,
|
||||
name: a.name,
|
||||
wpm: parseFloat(a.wpm),
|
||||
raw: parseFloat(a.rawWpm),
|
||||
acc: parseFloat(a.acc),
|
||||
mode: a.mode,
|
||||
mode2: a.mode2,
|
||||
timestamp: a.timestamp,
|
||||
});
|
||||
insertedAt = this.board.length - 1;
|
||||
}
|
||||
// console.log("before duplicate remove");
|
||||
// console.log(this.board);
|
||||
let newBest = false;
|
||||
let foundAt = null;
|
||||
if (insertedAt >= 0) {
|
||||
// if (this.removeDuplicates(insertedAt, a.uid)) {
|
||||
// insertedAt = -2;
|
||||
// }
|
||||
foundAt = this.removeDuplicates(insertedAt, a.uid);
|
||||
|
||||
if (foundAt >= insertedAt) {
|
||||
//new better result
|
||||
newBest = true;
|
||||
}
|
||||
}
|
||||
// console.log(this.board);
|
||||
this.clipBoard();
|
||||
return {
|
||||
insertedAt: insertedAt,
|
||||
newBest: newBest,
|
||||
foundAt: foundAt,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
insertedAt: -999,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.generatePairingCode = functions.https.onCall((request, response) => {
|
||||
try {
|
||||
if (request === null) {
|
||||
|
@ -720,21 +986,224 @@ exports.generatePairingCode = functions.https.onCall((request, response) => {
|
|||
}
|
||||
});
|
||||
|
||||
// exports.getConfig = functions.https.onCall((request,response) => {
|
||||
// try{
|
||||
// if(request.uid === undefined){
|
||||
// console.error(`error getting config for ${request.uid} - missing input`);
|
||||
// return -1;
|
||||
// }
|
||||
async function checkLeaderboards(resultObj, type, banned, name) {
|
||||
try {
|
||||
if (!name)
|
||||
return {
|
||||
insertedAt: null,
|
||||
noName: true,
|
||||
};
|
||||
if (banned)
|
||||
return {
|
||||
insertedAt: null,
|
||||
banned: true,
|
||||
};
|
||||
if (
|
||||
resultObj.mode === "time" &&
|
||||
["15", "60"].includes(String(resultObj.mode2)) &&
|
||||
resultObj.language === "english"
|
||||
) {
|
||||
return db
|
||||
.collection("leaderboards")
|
||||
.where("mode", "==", String(resultObj.mode))
|
||||
.where("mode2", "==", String(resultObj.mode2))
|
||||
.where("type", "==", type)
|
||||
.get()
|
||||
.then((ret) => {
|
||||
if (ret.docs.length === 0) {
|
||||
//no lb found, create
|
||||
console.log(
|
||||
`no ${resultObj.mode} ${resultObj.mode2} ${type} leaderboard found - creating`
|
||||
);
|
||||
let toAdd = {
|
||||
size: 20,
|
||||
mode: String(resultObj.mode),
|
||||
mode2: String(resultObj.mode2),
|
||||
type: type,
|
||||
};
|
||||
return db
|
||||
.collection("leaderboards")
|
||||
.doc(
|
||||
`${String(resultObj.mode)}_${String(resultObj.mode2)}_${type}`
|
||||
)
|
||||
.set(toAdd)
|
||||
.then((ret) => {
|
||||
return cont(
|
||||
`${String(resultObj.mode)}_${String(
|
||||
resultObj.mode2
|
||||
)}_${type}`,
|
||||
toAdd
|
||||
);
|
||||
});
|
||||
} else {
|
||||
//continue
|
||||
return cont(
|
||||
`${String(resultObj.mode)}_${String(resultObj.mode2)}_${type}`,
|
||||
ret.docs[0].data()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// return admin.firestore().collection(`users`).doc(request.uid).get().then(e => {
|
||||
// return e.data().config;
|
||||
// }).catch(e => {
|
||||
// console.error(`error getting config from DB for ${request.uid} - ${e.message}`);
|
||||
// return -1;
|
||||
// });
|
||||
// }catch(e){
|
||||
// console.error(`error getting config for ${request.uid} - ${e}`);
|
||||
// return {resultCode:-999};
|
||||
// }
|
||||
// })
|
||||
function cont(docid, documentData) {
|
||||
let boardInfo = documentData;
|
||||
let boardData = boardInfo.board;
|
||||
|
||||
// console.log(`info ${JSON.stringify(boardInfo)}`);
|
||||
// console.log(`data ${JSON.stringify(boardData)}`);
|
||||
|
||||
let lb = new Leaderboard(
|
||||
boardInfo.size,
|
||||
resultObj.mode,
|
||||
resultObj.mode2,
|
||||
boardInfo.type,
|
||||
boardData
|
||||
);
|
||||
|
||||
// console.log("board created");
|
||||
// lb.logBoard();
|
||||
|
||||
let insertResult = lb.insert(resultObj);
|
||||
|
||||
// console.log("board after inseft");
|
||||
// lb.logBoard();
|
||||
|
||||
if (insertResult.insertedAt >= 0) {
|
||||
//update the database here
|
||||
console.log(
|
||||
`leaderboard changed ${resultObj.mode} ${
|
||||
resultObj.mode2
|
||||
} ${type} - ${JSON.stringify(lb.board)}`
|
||||
);
|
||||
db.collection("leaderboards").doc(docid).set(
|
||||
{
|
||||
size: lb.size,
|
||||
type: lb.type,
|
||||
board: lb.board,
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
} else {
|
||||
// console.log("board is the same");
|
||||
}
|
||||
|
||||
return {
|
||||
insertedAt: insertResult,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
insertedAt: null,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`error while checking leaderboards - ${e} - ${type} ${resultObj}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
exports.getLeaderboard = functions.https.onCall((request, response) => {
|
||||
return db
|
||||
.collection("leaderboards")
|
||||
.where("mode", "==", String(request.mode))
|
||||
.where("mode2", "==", String(request.mode2))
|
||||
.where("type", "==", String(request.type))
|
||||
.get()
|
||||
.then(async (data) => {
|
||||
// console.log("got data");
|
||||
if (data.docs.length === 0) return null;
|
||||
let lbdata = data.docs[0].data();
|
||||
if (lbdata.board !== undefined) {
|
||||
// console.log("replacing users");
|
||||
|
||||
// for (let i = 0; i < lbdata.board.length; i++) {
|
||||
// await db
|
||||
// .collection("users")
|
||||
// .doc(lbdata.board[i].uid)
|
||||
// .get()
|
||||
// .then((doc) => {
|
||||
// if (
|
||||
// lbdata.board[i].uid !== null &&
|
||||
// lbdata.board[i].uid === request.uid
|
||||
// ) {
|
||||
// lbdata.board[i].currentUser = true;
|
||||
// }
|
||||
// lbdata.board[i].name = doc.data().name;
|
||||
// lbdata.board[i].uid = null;
|
||||
// });
|
||||
// }
|
||||
|
||||
lbdata.board.forEach((boardentry) => {
|
||||
if (boardentry.uid !== null && boardentry.uid === request.uid) {
|
||||
boardentry.currentUser = true;
|
||||
}
|
||||
boardentry.uid = null;
|
||||
});
|
||||
|
||||
// console.log(lbdata);
|
||||
if (request.type === "daily") {
|
||||
let resetTime = new Date(Date.now());
|
||||
resetTime.setHours(0, 0, 0, 0);
|
||||
resetTime.setDate(resetTime.getUTCDate() + 1);
|
||||
resetTime = resetTime.valueOf();
|
||||
lbdata.resetTime = resetTime;
|
||||
}
|
||||
|
||||
return lbdata;
|
||||
} else {
|
||||
if (
|
||||
lbdata.board === undefined ||
|
||||
lbdata.board === [] ||
|
||||
lbdata.board.length === 0
|
||||
) {
|
||||
return lbdata;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.scheduledFunctionCrontab = functions.pubsub
|
||||
.schedule("00 00 * * *")
|
||||
.timeZone("Africa/Abidjan")
|
||||
.onRun((context) => {
|
||||
try {
|
||||
console.log("moving daily leaderboards to history");
|
||||
db.collection("leaderboards")
|
||||
.where("type", "==", "daily")
|
||||
.get()
|
||||
.then((res) => {
|
||||
res.docs.forEach((doc) => {
|
||||
let lbdata = doc.data();
|
||||
t = new Date();
|
||||
db.collection("leaderboards_history")
|
||||
.doc(
|
||||
`${t.getUTCDate()}_${t.getUTCMonth()}_${t.getUTCFullYear()}_${
|
||||
lbdata.mode
|
||||
}_${lbdata.mode2}`
|
||||
)
|
||||
.set(lbdata);
|
||||
db.collection("leaderboards").doc(doc.id).set(
|
||||
{
|
||||
board: [],
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error(`error while moving daily leaderboards to history - ${e}`);
|
||||
}
|
||||
});
|
||||
|
||||
async function announceLbUpdate(discordId, pos, lb, wpm) {
|
||||
db.collection("bot-commands").add({
|
||||
command: "updateRole",
|
||||
arguments: [discordId, pos, lb, wpm],
|
||||
executed: false,
|
||||
requestTimestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
/* Firefox */
|
||||
}
|
||||
|
||||
html {
|
||||
@extend .ffscroll;
|
||||
}
|
||||
|
||||
.ffscroll {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--sub-color) transparent;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
border: none;
|
||||
|
@ -85,7 +94,7 @@ html {
|
|||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
|
@ -135,6 +144,7 @@ a:hover {
|
|||
animation-iteration-count: infinite;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: cubic-bezier(0.38, 0.16, 0.57, 0.82);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
|
@ -154,6 +164,127 @@ a:hover {
|
|||
}
|
||||
}
|
||||
|
||||
#leaderboardsWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5rem 0;
|
||||
|
||||
#leaderboards {
|
||||
width: 85vw;
|
||||
height: calc(100vh - 10rem);
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--roundness);
|
||||
padding: 2rem;
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
grid-template-rows: 3rem auto;
|
||||
grid-template-areas: "title buttons"
|
||||
"tables tables";
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
.mainTitle {
|
||||
font-size: 3rem;
|
||||
line-height: 3rem;
|
||||
grid-area: title;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
line-height: 2rem;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.tables {
|
||||
grid-area: tables;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-bottom: 2rem;
|
||||
font-size: .8rem;
|
||||
|
||||
.titleAndTable {
|
||||
display: grid;
|
||||
|
||||
.title {
|
||||
grid-area: 1/1;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
grid-area: 1/1;
|
||||
align-self: center;
|
||||
justify-self: right;
|
||||
color: var(--sub-color);
|
||||
}
|
||||
}
|
||||
|
||||
.globalTableWrapper,
|
||||
.dailyTableWrapper {
|
||||
height: calc(100vh - 22rem);
|
||||
@extend .ffscroll;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
|
||||
tr td:first-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: .25rem .5rem;
|
||||
|
||||
&.me {
|
||||
color: var(--main-color);
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
color: var(--sub-color);
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
tbody {
|
||||
color: var(--text-color);
|
||||
|
||||
tr:nth-child(odd) td {
|
||||
background: rgba(0, 0, 0, .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
grid-area: buttons;
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-self: center;
|
||||
|
||||
.buttonGroup {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 1rem;
|
||||
grid-area: 1/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#tagsWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -230,6 +361,7 @@ a:hover {
|
|||
padding: 2rem;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
@extend .ffscroll;
|
||||
overflow-y: scroll;
|
||||
|
||||
.tip {
|
||||
|
@ -346,6 +478,7 @@ a:hover {
|
|||
|
||||
.suggestions {
|
||||
display: block;
|
||||
@extend .ffscroll;
|
||||
overflow-y: scroll;
|
||||
max-height: calc(100vh - 10rem - 3rem);
|
||||
display: grid;
|
||||
|
@ -791,7 +924,7 @@ key {
|
|||
|
||||
.stats {
|
||||
display: grid;
|
||||
gap: .5rem;
|
||||
column-gap: .5rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
grid-template-areas:
|
||||
|
@ -799,9 +932,13 @@ key {
|
|||
"wpm key"
|
||||
"raw time"
|
||||
"source source"
|
||||
"leaderboards leaderboards"
|
||||
"testType infoAndTags";
|
||||
|
||||
|
||||
.group {
|
||||
margin-bottom: .5rem;
|
||||
|
||||
.top {
|
||||
color: var(--sub-color);
|
||||
font-size: 1rem;
|
||||
|
@ -831,6 +968,22 @@ key {
|
|||
}
|
||||
}
|
||||
|
||||
.leaderboards {
|
||||
align-self: baseline;
|
||||
grid-area: leaderboards;
|
||||
color: var(--sub-color);
|
||||
|
||||
.top {
|
||||
font-size: 1rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
font-size: 1rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.source {
|
||||
align-self: baseline;
|
||||
grid-area: source;
|
||||
|
|
|
@ -52,6 +52,428 @@
|
|||
<div class="button confirmButton"><i class="fas fa-check"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="leaderboardsWrapper" class="hidden">
|
||||
<div id="leaderboards">
|
||||
<div class="mainTitle">
|
||||
Leaderboards
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="buttonGroup">
|
||||
<div class="button active" board="time_15">time 15</div>
|
||||
<div class="button" board="time_60">time 60</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tables">
|
||||
<div class="titleAndTable">
|
||||
<div class="title">
|
||||
Global
|
||||
</div>
|
||||
<div class="globalTableWrapper">
|
||||
<table class="global">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td>name</td>
|
||||
<td>wpm</td>
|
||||
<td>raw</td>
|
||||
<td>acc</td>
|
||||
<td>test</td>
|
||||
<td>date</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="titleAndTable">
|
||||
<div class="title">
|
||||
Daily
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
-
|
||||
</div>
|
||||
<div class="dailyTableWrapper">
|
||||
<table class="daily">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td>name</td>
|
||||
<td>wpm</td>
|
||||
<td>raw</td>
|
||||
<td>acc</td>
|
||||
<td>test</td>
|
||||
<td>date</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="versionHistoryWrapper" class="hidden">
|
||||
<div id="versionHistory">
|
||||
<div class="tip">Click anywhere to dismiss</div>
|
||||
|
@ -97,6 +519,11 @@
|
|||
<i class="fas fa-fw fa-keyboard"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-button leaderboards" tabindex="2" onclick="this.blur();">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-crown"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-button" tabindex="2" href="/about" onclick="this.blur();">
|
||||
<div class="icon">
|
||||
<i class="fas fa-fw fa-info"></i>
|
||||
|
@ -228,6 +655,10 @@
|
|||
<div class="bottom">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group leaderboards">
|
||||
<div class="top">leaderboards</div>
|
||||
<div class="bottom">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<!-- <div class="title">wpm over time</div> -->
|
||||
|
@ -852,14 +1283,14 @@
|
|||
<script src="js/chartjs-plugin-trendline.js?v=27"></script>
|
||||
<script src="js/chartjs-plugin-annotation.js"></script>
|
||||
<script src="js/html2canvas.js"></script>
|
||||
<script src="js/words.js?v=27"></script>
|
||||
<script src="js/layouts.js?v=27"></script>
|
||||
<script src="js/db.js?v=27"></script>
|
||||
<script src="js/userconfig.js?v=27"></script>
|
||||
<script src="js/commandline.js?v=27"></script>
|
||||
<script src="js/settings.js?v=27"></script>
|
||||
<script src="js/account.js?v=27"></script>
|
||||
<script src="js/script.js?v=27"></script>
|
||||
|
||||
<script src="js/words.js?v=25"></script>
|
||||
<script src="js/layouts.js?v=25"></script>
|
||||
<script src="js/db.js?v=25"></script>
|
||||
<script src="js/userconfig.js?v=25"></script>
|
||||
<script src="js/commandline.js?v=25"></script>
|
||||
<script src="js/leaderboards.js?v=25"></script>
|
||||
<script src="js/settings.js?v=25"></script>
|
||||
<script src="js/account.js?v=25"></script>
|
||||
<script src="js/script.js?v=25"></script>
|
||||
|
||||
</html>
|
|
@ -135,6 +135,11 @@ function signUp() {
|
|||
})
|
||||
.then(function () {
|
||||
// Update successful.
|
||||
firebase
|
||||
.firestore()
|
||||
.collection("users")
|
||||
.doc(usr.uid)
|
||||
.set({ name: nname }, { merge: true });
|
||||
showNotification("Account created", 2000);
|
||||
$("#menu .icon-button.account .text").text(nname);
|
||||
try {
|
||||
|
@ -147,11 +152,12 @@ function signUp() {
|
|||
})
|
||||
.catch(function (error) {
|
||||
// An error happened.
|
||||
console.error(error);
|
||||
usr
|
||||
.delete()
|
||||
.then(function () {
|
||||
// User deleted.
|
||||
showNotification("Name invalid", 2000);
|
||||
showNotification("An error occured", 2000);
|
||||
$(".pageLogin .preloader").addClass("hidden");
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
@ -210,7 +216,7 @@ firebase.auth().onAuthStateChanged(function (user) {
|
|||
// showNotification('Applying db config',3000);
|
||||
updateSettingsPage();
|
||||
saveConfigToCookie();
|
||||
} else {
|
||||
} else if (dbSnapshot.config !== undefined) {
|
||||
let configsDifferent = false;
|
||||
Object.keys(config).forEach((key) => {
|
||||
if (!configsDifferent) {
|
||||
|
|
225
public/js/leaderboards.js
Normal file
225
public/js/leaderboards.js
Normal file
|
@ -0,0 +1,225 @@
|
|||
let currentLeaderboard = "time_15";
|
||||
|
||||
function showLeaderboards() {
|
||||
if ($("#leaderboardsWrapper").hasClass("hidden")) {
|
||||
$("#leaderboardsWrapper")
|
||||
.stop(true, true)
|
||||
.css("opacity", 0)
|
||||
.removeClass("hidden")
|
||||
.animate(
|
||||
{
|
||||
opacity: 1,
|
||||
},
|
||||
125,
|
||||
() => {
|
||||
updateLeaderboards();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function hideLeaderboards() {
|
||||
$("#leaderboardsWrapper")
|
||||
.stop(true, true)
|
||||
.css("opacity", 1)
|
||||
.animate(
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
100,
|
||||
() => {
|
||||
$("#leaderboardsWrapper").addClass("hidden");
|
||||
}
|
||||
);
|
||||
focusWords();
|
||||
}
|
||||
|
||||
function updateLeaderboards() {
|
||||
$("#leaderboardsWrapper .buttons .button").removeClass("active");
|
||||
$(
|
||||
`#leaderboardsWrapper .buttons .button[board=${currentLeaderboard}]`
|
||||
).addClass("active");
|
||||
|
||||
// $(
|
||||
// `#leaderboardsWrapper .leaderboardMode .button[mode=${currentLeaderboard.mode}]`
|
||||
// ).addClass("active");
|
||||
|
||||
// $("#leaderboardsWrapper .leaderboardWords .button").removeClass("active");
|
||||
// $(
|
||||
// `#leaderboardsWrapper .leaderboardWords .button[words=${currentLeaderboard.words}]`
|
||||
// ).addClass("active");
|
||||
|
||||
// $("#leaderboardsWrapper .leaderboardTime .button").removeClass("active");
|
||||
// $(
|
||||
// `#leaderboardsWrapper .leaderboardTime .button[time=${currentLeaderboard.time}]`
|
||||
// ).addClass("active");
|
||||
|
||||
let boardinfo = currentLeaderboard.split("_");
|
||||
|
||||
// if (boardinfo[0] === "time") {
|
||||
// $("#leaderboardsWrapper .leaderboardWords").addClass("hidden");
|
||||
// $("#leaderboardsWrapper .leaderboardTime").removeClass("hidden");
|
||||
// } else if (currentLeaderboard.mode === "words") {
|
||||
// $("#leaderboardsWrapper .leaderboardWords").removeClass("hidden");
|
||||
// $("#leaderboardsWrapper .leaderboardTime").addClass("hidden");
|
||||
// }
|
||||
|
||||
// let mode2;
|
||||
// if (currentLeaderboard.mode === "words") {
|
||||
// mode2 = currentLeaderboard.words;
|
||||
// } else if (currentLeaderboard.mode === "time") {
|
||||
// mode2 = currentLeaderboard.time;
|
||||
// }
|
||||
|
||||
let uid = null;
|
||||
if (firebase.auth().currentUser !== null) {
|
||||
uid = firebase.auth().currentUser.uid;
|
||||
}
|
||||
|
||||
showBackgroundLoader();
|
||||
Promise.all([
|
||||
firebase.functions().httpsCallable("getLeaderboard")({
|
||||
mode: boardinfo[0],
|
||||
mode2: boardinfo[1],
|
||||
type: "daily",
|
||||
uid: uid,
|
||||
}),
|
||||
firebase.functions().httpsCallable("getLeaderboard")({
|
||||
mode: boardinfo[0],
|
||||
mode2: boardinfo[1],
|
||||
type: "global",
|
||||
uid: uid,
|
||||
}),
|
||||
])
|
||||
.then((lbdata) => {
|
||||
hideBackgroundLoader();
|
||||
let dailyData = lbdata[0].data;
|
||||
let globalData = lbdata[1].data;
|
||||
|
||||
//daily
|
||||
let diffAsDate = new Date(dailyData.resetTime - Date.now());
|
||||
|
||||
let diffHours = diffAsDate.getUTCHours();
|
||||
let diffMinutes = diffAsDate.getUTCMinutes();
|
||||
let diffSeconds = diffAsDate.getUTCSeconds();
|
||||
|
||||
let resetString = "";
|
||||
if (diffHours > 0) {
|
||||
resetString = `resets in ${diffHours} ${
|
||||
diffHours == 1 ? "hour" : "hours"
|
||||
} ${diffMinutes} ${diffMinutes == 1 ? "minute" : "minutes"}
|
||||
`;
|
||||
} else if (diffMinutes > 0) {
|
||||
resetString = `resets in ${diffMinutes} ${
|
||||
diffMinutes == 1 ? "minute" : "minutes"
|
||||
} ${diffSeconds} ${diffSeconds == 1 ? "second" : "seconds"}`;
|
||||
} else if (diffSeconds > 0) {
|
||||
resetString = `resets in ${diffSeconds} ${
|
||||
diffSeconds == 1 ? "second" : "seconds"
|
||||
}`;
|
||||
}
|
||||
|
||||
$("#leaderboardsWrapper .subtitle").text(resetString);
|
||||
|
||||
$("#leaderboardsWrapper table.daily tbody").empty();
|
||||
if (dailyData.board !== undefined) {
|
||||
dailyData.board.forEach((entry, index) => {
|
||||
let meClassString = "";
|
||||
if (entry.currentUser) meClassString = ' class="me"';
|
||||
$("#leaderboardsWrapper table.daily tbody").append(`
|
||||
<tr>
|
||||
<td>${
|
||||
index === 0 ? '<i class="fas fa-fw fa-crown"></i>' : index + 1
|
||||
}</td>
|
||||
<td ${meClassString}>${entry.name}</td>
|
||||
<td>${entry.wpm}</td>
|
||||
<td>${entry.raw}</td>
|
||||
<td>${entry.acc}%</td>
|
||||
<td>${entry.mode} ${entry.mode2}</td>
|
||||
<td>${moment(entry.timestamp).format("DD MMM YYYY<br>HH:mm")}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
let lenDaily = 0;
|
||||
if (dailyData.board !== undefined) lenDaily = dailyData.board.length;
|
||||
if (dailyData.length === 0 || lenDaily !== dailyData.size) {
|
||||
for (let i = lenDaily; i < dailyData.size; i++) {
|
||||
$("#leaderboardsWrapper table.daily tbody").append(`
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
//global
|
||||
$("#leaderboardsWrapper table.global tbody").empty();
|
||||
if (globalData.board !== undefined) {
|
||||
globalData.board.forEach((entry, index) => {
|
||||
let meClassString = "";
|
||||
if (entry.currentUser) meClassString = ' class="me"';
|
||||
$("#leaderboardsWrapper table.global tbody").append(`
|
||||
<tr>
|
||||
<td>${
|
||||
index === 0 ? '<i class="fas fa-fw fa-crown"></i>' : index + 1
|
||||
}</td>
|
||||
<td ${meClassString}>${entry.name}</td>
|
||||
<td>${entry.wpm}</td>
|
||||
<td>${entry.raw}</td>
|
||||
<td>${entry.acc}%</td>
|
||||
<td>${entry.mode} ${entry.mode2}</td>
|
||||
<td>${moment(entry.timestamp).format("DD MMM YYYY<br>HH:mm")}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
let lenGlobal = 0;
|
||||
if (globalData.board !== undefined) lenGlobal = globalData.board.length;
|
||||
if (globalData.length === 0 || lenGlobal !== globalData.size) {
|
||||
for (let i = lenGlobal; i < globalData.size; i++) {
|
||||
$("#leaderboardsWrapper table.global tbody").append(`
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-<br>-</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
showNotification("Something went wrong", 3000);
|
||||
});
|
||||
}
|
||||
|
||||
$("#leaderboardsWrapper").click((e) => {
|
||||
if ($(e.target).attr("id") === "leaderboardsWrapper") {
|
||||
hideLeaderboards();
|
||||
}
|
||||
});
|
||||
|
||||
$("#leaderboardsWrapper .buttons .button").click((e) => {
|
||||
currentLeaderboard = $(e.target).attr("board");
|
||||
updateLeaderboards();
|
||||
});
|
||||
|
||||
// $("#leaderboardsWrapper .leaderboardWords .button").click((e) => {
|
||||
// currentLeaderboard.words = $(e.target).attr("words");
|
||||
// updateLeaderboards();
|
||||
// });
|
||||
|
||||
// $("#leaderboardsWrapper .leaderboardTime .button").click((e) => {
|
||||
// currentLeaderboard.time = $(e.target).attr("time");
|
||||
// updateLeaderboards();
|
||||
// });
|
|
@ -29,6 +29,17 @@ let accuracyStats = {
|
|||
incorrect: 0,
|
||||
};
|
||||
|
||||
let keypressStats = {
|
||||
spacing: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
duration: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
};
|
||||
|
||||
let customText = "The quick brown fox jumps over the lazy dog".split(" ");
|
||||
let randomQuote = null;
|
||||
|
||||
|
@ -908,6 +919,9 @@ function showResult(difficultyFailed = false) {
|
|||
// 'margin-bottom': 0
|
||||
});
|
||||
|
||||
$("#result .stats .leaderboards .bottom").text("");
|
||||
$("#result .stats .leaderboards").addClass("hidden");
|
||||
|
||||
let mode2 = "";
|
||||
if (config.mode === "time") {
|
||||
mode2 = config.time;
|
||||
|
@ -972,6 +986,29 @@ function showResult(difficultyFailed = false) {
|
|||
});
|
||||
}
|
||||
|
||||
function stdDev(array) {
|
||||
const n = array.length;
|
||||
const mean = array.reduce((a, b) => a + b) / n;
|
||||
return Math.sqrt(
|
||||
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`avg time between keys ${
|
||||
keypressStats.spacing.array.reduce(
|
||||
(previous, current) => (current += previous)
|
||||
) / keypressStats.spacing.array.length
|
||||
} std(${stdDev(keypressStats.spacing.array)})`
|
||||
);
|
||||
console.log(
|
||||
`avg key down time ${
|
||||
keypressStats.duration.array.reduce(
|
||||
(previous, current) => (current += previous)
|
||||
) / keypressStats.duration.array.length
|
||||
} std(${stdDev(keypressStats.duration.array)})`
|
||||
);
|
||||
|
||||
wpmOverTimeChart.data.datasets[2].data = errorsNoZero;
|
||||
|
||||
if (difficultyFailed) {
|
||||
|
@ -1008,6 +1045,8 @@ function showResult(difficultyFailed = false) {
|
|||
blindMode: config.blindMode,
|
||||
theme: config.theme,
|
||||
tags: activeTags,
|
||||
keySpacing: keypressStats.spacing.array,
|
||||
keyDuration: keypressStats.duration.array,
|
||||
};
|
||||
if (
|
||||
config.difficulty == "normal" ||
|
||||
|
@ -1075,14 +1114,24 @@ function showResult(difficultyFailed = false) {
|
|||
);
|
||||
wpmOverTimeChart.update({ duration: 0 });
|
||||
}
|
||||
$("#result .stats .leaderboards").removeClass("hidden");
|
||||
$("#result .stats .leaderboards .bottom").html("checking...");
|
||||
testCompleted({
|
||||
uid: firebase.auth().currentUser.uid,
|
||||
obj: completedEvent,
|
||||
}).then((e) => {
|
||||
accountIconLoading(false);
|
||||
if (e.data === -1) {
|
||||
console.log(e.data);
|
||||
if (e.data.resultCode === -1) {
|
||||
showNotification("Could not save result", 3000);
|
||||
} else if (e.data === 1 || e.data === 2) {
|
||||
} else if (e.data.resultCode === -2) {
|
||||
showNotification(
|
||||
"Possible bot detected. Result not saved.",
|
||||
4000
|
||||
);
|
||||
} else if (e.data.resultCode === -999) {
|
||||
showNotification("Internal error. Result not saved.", 4000);
|
||||
} else if (e.data.resultCode === 1 || e.data.resultCode === 2) {
|
||||
dbSnapshot.results.unshift(completedEvent);
|
||||
try {
|
||||
firebase
|
||||
|
@ -1091,7 +1140,93 @@ function showResult(difficultyFailed = false) {
|
|||
} catch (e) {
|
||||
console.log("Analytics unavailable");
|
||||
}
|
||||
if (e.data === 2) {
|
||||
|
||||
//global
|
||||
let globalLbString = "";
|
||||
if (e.data.globalLeaderboard === null) {
|
||||
globalLbString = "global: not found";
|
||||
} else if (e.data.globalLeaderboard.insertedAt === -1) {
|
||||
globalLbString = "global: not qualified";
|
||||
} else if (e.data.globalLeaderboard.insertedAt >= 0) {
|
||||
if (e.data.globalLeaderboard.newBest) {
|
||||
let pos = e.data.globalLeaderboard.insertedAt + 1;
|
||||
let numend = "th";
|
||||
if (pos === 1) {
|
||||
numend = "st";
|
||||
} else if (pos === 2) {
|
||||
numend = "nd";
|
||||
} else if (pos === 3) {
|
||||
numend = "rd";
|
||||
}
|
||||
globalLbString = `global: ${pos}${numend} place`;
|
||||
} else {
|
||||
let pos = e.data.globalLeaderboard.foundAt + 1;
|
||||
let numend = "th";
|
||||
if (pos === 1) {
|
||||
numend = "st";
|
||||
} else if (pos === 2) {
|
||||
numend = "nd";
|
||||
} else if (pos === 3) {
|
||||
numend = "rd";
|
||||
}
|
||||
globalLbString = `global: already ${pos}${numend}`;
|
||||
}
|
||||
}
|
||||
|
||||
//daily
|
||||
let dailyLbString = "";
|
||||
if (e.data.dailyLeaderboard === null) {
|
||||
dailyLbString = "daily: not found";
|
||||
} else if (e.data.dailyLeaderboard.insertedAt === -1) {
|
||||
dailyLbString = "daily: not qualified";
|
||||
} else if (e.data.dailyLeaderboard.insertedAt >= 0) {
|
||||
if (e.data.dailyLeaderboard.newBest) {
|
||||
let pos = e.data.dailyLeaderboard.insertedAt + 1;
|
||||
let numend = "th";
|
||||
if (pos === 1) {
|
||||
numend = "st";
|
||||
} else if (pos === 2) {
|
||||
numend = "nd";
|
||||
} else if (pos === 3) {
|
||||
numend = "rd";
|
||||
}
|
||||
dailyLbString = `daily: ${pos}${numend} place`;
|
||||
} else {
|
||||
let pos = e.data.dailyLeaderboard.foundAt + 1;
|
||||
let numend = "th";
|
||||
if (pos === 1) {
|
||||
numend = "st";
|
||||
} else if (pos === 2) {
|
||||
numend = "nd";
|
||||
} else if (pos === 3) {
|
||||
numend = "rd";
|
||||
}
|
||||
dailyLbString = `daily: already ${pos}${numend}`;
|
||||
}
|
||||
}
|
||||
if (
|
||||
e.data.dailyLeaderboard === null &&
|
||||
e.data.globalLeaderboard === null &&
|
||||
e.data.lbBanned === false &&
|
||||
e.data.name !== false
|
||||
) {
|
||||
$("#result .stats .leaderboards").addClass("hidden");
|
||||
} else {
|
||||
$("#result .stats .leaderboards").removeClass("hidden");
|
||||
if (e.data.lbBanned) {
|
||||
$("#result .stats .leaderboards .bottom").html("banned");
|
||||
} else if (e.data.name === false) {
|
||||
$("#result .stats .leaderboards .bottom").html(
|
||||
"update your name to access leaderboards"
|
||||
);
|
||||
} else {
|
||||
$("#result .stats .leaderboards .bottom").html(
|
||||
globalLbString + "<br>" + dailyLbString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.data.resultCode === 2) {
|
||||
//new pb
|
||||
if (!localPb) {
|
||||
showNotification(
|
||||
|
@ -1142,6 +1277,7 @@ function showResult(difficultyFailed = false) {
|
|||
if (firebase.auth().currentUser != null) {
|
||||
$("#result .loginTip").addClass("hidden");
|
||||
} else {
|
||||
$("#result .stats .leaderboards").addClass("hidden");
|
||||
$("#result .loginTip").removeClass("hidden");
|
||||
}
|
||||
|
||||
|
@ -1260,6 +1396,16 @@ function restartTest(withSameWordset = false) {
|
|||
currentErrorCount = 0;
|
||||
currentTestLine = 0;
|
||||
activeWordJumped = false;
|
||||
keypressStats = {
|
||||
spacing: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
duration: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
};
|
||||
hideTimer();
|
||||
// restartTimer();
|
||||
let el = null;
|
||||
|
@ -1981,8 +2127,12 @@ $(document).on("click", "#top .config .mode .text-button", (e) => {
|
|||
|
||||
$(document).on("click", "#top #menu .icon-button", (e) => {
|
||||
if ($(e.currentTarget).hasClass("discord")) return;
|
||||
href = $(e.currentTarget).attr("href");
|
||||
changePage(href.replace("/", ""));
|
||||
if ($(e.currentTarget).hasClass("leaderboards")) {
|
||||
showLeaderboards();
|
||||
} else {
|
||||
href = $(e.currentTarget).attr("href");
|
||||
changePage(href.replace("/", ""));
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on("popstate", (e) => {
|
||||
|
@ -2110,6 +2260,16 @@ $(document).keypress(function (event) {
|
|||
updateActiveElement();
|
||||
updateTimer();
|
||||
clearIntervals();
|
||||
keypressStats = {
|
||||
spacing: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
duration: {
|
||||
current: -1,
|
||||
array: [],
|
||||
},
|
||||
};
|
||||
timers.push(
|
||||
setInterval(function () {
|
||||
time++;
|
||||
|
@ -2161,6 +2321,7 @@ $(document).keypress(function (event) {
|
|||
} else {
|
||||
accuracyStats.correct++;
|
||||
}
|
||||
|
||||
currentKeypressCount++;
|
||||
currentInput += event["key"];
|
||||
$("#words .word.active").attr("input", currentInput);
|
||||
|
@ -2175,10 +2336,29 @@ $(document).keypress(function (event) {
|
|||
updateCaretPosition();
|
||||
});
|
||||
|
||||
$(document).keydown((event) => {
|
||||
keypressStats.duration.current = performance.now();
|
||||
});
|
||||
|
||||
$(document).keyup((event) => {
|
||||
let now = performance.now();
|
||||
let diff = Math.abs(keypressStats.duration.current - now);
|
||||
if (keypressStats.duration.current !== -1) {
|
||||
keypressStats.duration.array.push(diff);
|
||||
}
|
||||
keypressStats.duration.current = now;
|
||||
});
|
||||
|
||||
//handle keyboard events
|
||||
$(document).keydown((event) => {
|
||||
//tab
|
||||
let now = performance.now();
|
||||
let diff = Math.abs(keypressStats.spacing.current - now);
|
||||
if (keypressStats.spacing.current !== -1) {
|
||||
keypressStats.spacing.array.push(diff);
|
||||
}
|
||||
keypressStats.spacing.current = now;
|
||||
|
||||
//tab
|
||||
if (event["keyCode"] == 9) {
|
||||
if (config.quickTab) {
|
||||
event.preventDefault();
|
||||
|
|
Loading…
Add table
Reference in a new issue