-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1276a62
commit 811da33
Showing
9 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<title>QB Reader</title> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<meta name="description" content=""> | ||
|
||
<link href="/apple-touch-icon.png" rel="apple-touch-icon"> | ||
<link href="/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed"> | ||
<link type="image/x-icon" href="/favicon.ico" rel="icon"> | ||
|
||
<link href="/bootstrap/light.css" rel="stylesheet"> | ||
<link href="/bootstrap/dark.css" rel="stylesheet" id="custom-css"> | ||
<script type="module" src="/scripts/apply-theme.js"></script> | ||
</head> | ||
|
||
<body> | ||
<nav class="navbar navbar-light navbar-expand-lg bg-custom" id="navbar" style="z-index: 10"> | ||
<div class="container-fluid"> | ||
<a class="navbar-brand ms-1 py-0" id="logo" href="/"> | ||
<span class="logo-prefix">QB</span><span class="logo-suffix">Reader</span> | ||
</a> | ||
<button class="navbar-toggler" data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button" | ||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> | ||
<span class="navbar-toggler-icon"></span> | ||
</button> | ||
<div class="collapse navbar-collapse" id="navbarSupportedContent"> | ||
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/singleplayer">Singleplayer</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/multiplayer">Multiplayer</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/db">Database</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/frequency-list">Frequency List</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/geoword">Geoword</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/api-docs">API</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/about">About</a> | ||
</li> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/settings">Settings</a> | ||
</li> | ||
</ul> | ||
<div class="d-flex"> | ||
<ul class="navbar-nav mb-2 mb-lg-0"> | ||
<li class="nav-item"> | ||
<a class="nav-link" href="/user/login" id="login-link">Log in</a> | ||
</li> | ||
</ul> | ||
</div> | ||
</div> | ||
</div> | ||
</nav> | ||
|
||
<div class="container-xl mt-3 mb-5 pb-5"> | ||
<p class="lead">Most active QB Reader players:</p> | ||
<table class="table table-hover"> | ||
<thead> | ||
<tr> | ||
<th scope="col">#</th> | ||
<th scope="col">Username</th> | ||
<th scope="col">Tossups Read</th> | ||
<th scope="col">Bonuses Read</th> | ||
<th scope="col">Total</th> | ||
</tr> | ||
</thead> | ||
<tbody class="table-group-divider" id="leaderboard"></tbody> | ||
</table> | ||
<div class="d-block mx-auto mt-3 spinner-border" id="spinner" role="status"><span class='d-none'>Loading...</span></div> | ||
</div> | ||
|
||
<script type="module" src="/bootstrap/bootstrap.bundle.min.js"></script> | ||
<script type="module" src="/script.js"></script> | ||
|
||
<script type="module" src="/admin/leaderboard/index.js"></script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import sortTable from '../../scripts/utilities/tables.js'; | ||
|
||
const limit = 20; | ||
fetch('/api/admin/leaderboard?' + new URLSearchParams({ limit })) | ||
.then(res => res.json()) | ||
.then(data => data.data) | ||
.then(data => { | ||
document.getElementById('spinner').classList.add('d-none'); | ||
const table = document.getElementById('leaderboard'); | ||
data.forEach((document, index) => { | ||
const row = table.insertRow(-1); | ||
row.insertCell(-1).textContent = index + 1; | ||
row.insertCell(-1).textContent = document.username; | ||
row.insertCell(-1).textContent = document.tossupCount; | ||
row.insertCell(-1).textContent = document.bonusCount; | ||
row.insertCell(-1).textContent = document.total; | ||
}); | ||
}); | ||
|
||
document.querySelectorAll('th').forEach((th, index) => { | ||
const numeric = [true, false, true, true, true]; | ||
th.addEventListener('click', () => sortTable(index, numeric[index], 'leaderboard', 0, 0)); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* Sorts a table by the values in a specified column. | ||
* @param {number} n - a zero-indexed column number to sort | ||
* @param {boolean} numeric - whether the column values represent numeric values | ||
* @param {string} tableId - the id of the table to sort | ||
* @param {number} headers - the number of headers of the table to skip (default 1) | ||
* @param {number} footers - the number of footers of the table to skip (default 0) | ||
*/ | ||
export default function sortTable (n, numeric = false, tableId = 'table', headers = 1, footers = 0) { | ||
let rows; let switching; let i; let x; let y; let shouldSwitch; let dir; let switchcount = 0; | ||
const table = document.getElementById(tableId); | ||
switching = true; | ||
// Set the sorting direction to ascending for text; | ||
// Numerals are always sorted in the opposite order from text | ||
dir = 'asc'; | ||
/* Make a loop that will continue until | ||
no switching has been done: */ | ||
while (switching) { | ||
// Start by saying: no switching is done: | ||
switching = false; | ||
rows = table.rows; | ||
// Loop through all table rows (except headers and footers) | ||
for (i = headers; i < rows.length - 1 - footers; i++) { | ||
// Start by saying there should be no switching: | ||
shouldSwitch = false; | ||
/* Get the two elements you want to compare, | ||
one from current row and one from the next: */ | ||
x = rows[i].children[n]; | ||
y = rows[i + 1].children[n]; | ||
/* Check if the two rows should switch place, | ||
based on the direction, asc or desc: */ | ||
if (dir === 'asc') { | ||
if (numeric) { | ||
if (parseFloat(x.innerHTML) < parseFloat(y.innerHTML)) { | ||
// If so, mark as a switch and break the loop: | ||
shouldSwitch = true; | ||
break; | ||
} | ||
} else { | ||
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) { | ||
// If so, mark as a switch and break the loop: | ||
shouldSwitch = true; | ||
break; | ||
} | ||
} | ||
} else if (dir === 'desc') { | ||
if (numeric) { | ||
if (parseFloat(x.innerHTML) > parseFloat(y.innerHTML)) { | ||
// If so, mark as a switch and break the loop: | ||
shouldSwitch = true; | ||
break; | ||
} | ||
} else { | ||
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) { | ||
// If so, mark as a switch and break the loop: | ||
shouldSwitch = true; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
if (shouldSwitch) { | ||
/* If a switch has been marked, make the switch | ||
and mark that a switch has been done: */ | ||
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); | ||
switching = true; | ||
// Each time a switch is done, increase this count by 1: | ||
switchcount++; | ||
} else { | ||
/* If no switching has been done AND the direction is "asc", | ||
set the direction to "desc" and run the while loop again. */ | ||
if (switchcount === 0 && dir === 'asc') { | ||
dir = 'desc'; | ||
switching = true; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { tossupData, bonusData } from '../../database/qbreader/collections.js'; | ||
import mergeTwoSortedArrays from '../../merge-two-sorted-arrays.js'; | ||
|
||
export default async function leaderboard (limit) { | ||
const tossupLeaderboard = await helper('tossup'); | ||
const bonusLeaderboard = await helper('bonus'); | ||
const overall = mergeTwoSortedArrays( | ||
tossupLeaderboard, | ||
bonusLeaderboard, | ||
(document) => document.username, | ||
(document1, document2) => ({ _id: document1._id, username: document1.username, tossupCount: document1.tossupCount, bonusCount: document2.bonusCount, total: document1.total + document2.total }) | ||
); | ||
// sort from most to least | ||
overall.sort((a, b) => b.total - a.total); | ||
return overall.slice(0, limit); | ||
} | ||
|
||
/** | ||
* | ||
* @param {'tossup' | 'bonus'} type - the type of questions to filter by | ||
* @returns | ||
*/ | ||
async function helper (type = 'tossup') { | ||
const aggregation = [ | ||
{ | ||
$group: { | ||
_id: '$user_id', | ||
count: { $sum: 1 } | ||
} | ||
}, | ||
{ | ||
$lookup: { | ||
from: 'users', | ||
localField: '_id', | ||
foreignField: '_id', | ||
as: 'user' | ||
} | ||
}, | ||
{ | ||
$project: { | ||
username: { $arrayElemAt: ['$user.username', 0] }, | ||
_id: 1, | ||
total: '$count' | ||
} | ||
}, | ||
{ $sort: { username: 1 } } | ||
]; | ||
|
||
switch (type) { | ||
case 'tossup': { | ||
const results = await tossupData.aggregate(aggregation).toArray(); | ||
return results.map((result) => ({ ...result, tossupCount: result.total, bonusCount: 0 })); | ||
} | ||
case 'bonus': { | ||
const results = await bonusData.aggregate(aggregation).toArray(); | ||
return results.map((result) => ({ ...result, tossupCount: 0, bonusCount: result.total })); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Router } from 'express'; | ||
|
||
const router = Router(); | ||
|
||
router.get('/', async (req, res) => { | ||
res.sendFile('index.html', { root: './client/admin/leaderboard' }); | ||
}); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import leaderboard from '../../../database/account-info/leaderboard.js'; | ||
|
||
import { Router } from 'express'; | ||
|
||
const router = Router(); | ||
|
||
router.get('/', async (req, res) => { | ||
const limit = req.query.limit ? parseInt(req.query.limit) : null; | ||
const data = await leaderboard(limit); | ||
res.json({ data }); | ||
}); | ||
|
||
export default router; |