fixed some shit ; added pagination

This commit is contained in:
2025-08-18 21:52:54 +02:00
parent 27f955151a
commit 6c8f34acb9
3 changed files with 724 additions and 694 deletions

View File

@@ -1,3 +1,11 @@
/**
*
* This is the stupid fucking JavaScript, i hate this so fucking much
* why the fuck does this need to exsits, idk.
*
* CHANGELOG:
* aug18@21:51-> pagination for leaderboard ; and some shit refactoring.
*/
document.addEventListener("DOMContentLoaded", () => {
"use strict";
@@ -170,6 +178,13 @@ document.addEventListener("DOMContentLoaded", () => {
borderTop: "1px solid var(--border)",
padding: "12px 0"
});
// Style the pagination buttons and info text
const prevBtn = this.problemsPagination.querySelector('#problemsPrevBtn');
const nextBtn = this.problemsPagination.querySelector('#problemsNextBtn');
const infoText = this.problemsPagination.querySelector('#problemsPaginationInfo');
if (prevBtn) prevBtn.style.marginRight = '10px';
if (nextBtn) nextBtn.style.marginLeft = '10px';
if (infoText) infoText.style.marginTop = '2px';
this.problemsPagination.classList.remove("hidden");
}
@@ -392,17 +407,55 @@ document.addEventListener("DOMContentLoaded", () => {
this.currentSort = { column: "rank", direction: "asc" };
this.allRows = [];
this.filteredRows = [];
this.currentPage = 1;
this.itemsPerPage = 5;
this.leaderboardPagination = document.createElement("div");
this.leaderboardPagination.className = "pagination-controls";
this.leaderboardPagination.style.display = "flex";
this.leaderboardPagination.style.justifyContent = "center";
this.leaderboardPagination.style.position = "absolute";
this.leaderboardPagination.style.left = 0;
this.leaderboardPagination.style.right = 0;
this.leaderboardPagination.style.bottom = 0;
this.leaderboardPagination.style.margin = "0 auto 0 auto";
this.leaderboardPagination.style.width = "100%";
this.leaderboardPagination.style.background = "inherit";
this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
this.leaderboardPagination.style.padding = "12px 0";
this.leaderboardPagination.innerHTML = `
<button class="pagination-btn" id="leaderboardPrevBtn" disabled style="margin-right:10px;"> Previous</button>
<span class="pagination-info" id="leaderboardPaginationInfo" style="margin-top:2px;">Page 1 of 1</span>
<button class="pagination-btn" id="leaderboardNextBtn" disabled style="margin-left:10px;">Next </button>
`;
this.leaderboardPrevBtn = this.leaderboardPagination.querySelector("#leaderboardPrevBtn");
this.leaderboardNextBtn = this.leaderboardPagination.querySelector("#leaderboardNextBtn");
this.leaderboardPaginationInfo = this.leaderboardPagination.querySelector("#leaderboardPaginationInfo");
this.init();
}
init() {
if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
this.initializeRows();
this.attachEventListeners();
this.calculateOverallRanking();
this.filterLeaderboard();
this.setInitialSortIndicator();
// Insert pagination controls after leaderboard table
const leaderboardContainer = document.getElementById("leaderboardContainer");
if (leaderboardContainer && !leaderboardContainer.contains(this.leaderboardPagination)) {
leaderboardContainer.appendChild(this.leaderboardPagination);
// Ensure parent card is relatively positioned and has enough bottom padding
const leaderboardCard = leaderboardContainer.closest('.card');
if (leaderboardCard) {
leaderboardCard.style.position = "relative";
leaderboardCard.style.paddingBottom = "56px";
}
}
// Also ensure the parent card (section.card) contains the controls for correct layout
const leaderboardCard = leaderboardContainer?.closest('.card');
if (leaderboardCard && !leaderboardCard.contains(this.leaderboardPagination)) {
leaderboardCard.appendChild(this.leaderboardPagination);
}
}
initializeRows() {
@@ -418,36 +471,90 @@ document.addEventListener("DOMContentLoaded", () => {
}));
}
updateRankClasses() {
const visibleRows = this.allRows.filter(
(row) => row.element.style.display !== "none"
);
visibleRows.forEach((rowData, index) => {
const rank = index + 1;
const row = rowData.element;
// Update rank cell
const rankCell = row.cells[0];
if (rankCell) rankCell.textContent = rank;
// Update rank classes
row.className = row.className.replace(/\brank-\d+\b/g, "");
if (rank === 1) row.classList.add("rank-1");
else if (rank <= 3) row.classList.add("rank-top3");
filterLeaderboard() {
const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
const runtimeType = this.runtimeFilter?.value || "all";
// Filter rows
this.filteredRows = this.allRows.filter((rowData) => {
let visible = true;
if (problemTerm) {
visible = rowData.problem.toLowerCase().includes(problemTerm);
}
return visible;
});
// Apply runtime filter (best/worst per user per problem)
if (runtimeType === "best" || runtimeType === "worst") {
const userProblemGroups = {};
this.filteredRows.forEach((rowData) => {
const key = `${rowData.user}::${rowData.problem}`;
if (!userProblemGroups[key]) userProblemGroups[key] = [];
userProblemGroups[key].push(rowData);
});
this.filteredRows = Object.values(userProblemGroups).flatMap((group) => {
if (group.length <= 1) return group;
group.sort((a, b) => a.runtime - b.runtime);
const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
return [group[keepIndex]];
});
}
this.currentPage = 1;
this.updateLeaderboardPagination();
}
updateLeaderboardPagination() {
const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
if (this.currentPage > totalPages) this.currentPage = totalPages;
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
// Hide all rows first
this.allRows.forEach((rowData) => {
rowData.element.style.display = "none";
});
// Show only current page rows
this.filteredRows.slice(startIndex, endIndex).forEach((rowData) => {
rowData.element.style.display = "";
});
// Update pagination controls
if (this.leaderboardPrevBtn) this.leaderboardPrevBtn.disabled = this.currentPage <= 1;
if (this.leaderboardNextBtn) this.leaderboardNextBtn.disabled = this.currentPage >= totalPages;
if (this.leaderboardPaginationInfo) {
this.leaderboardPaginationInfo.textContent =
totalPages > 0 ? `Page ${this.currentPage} of ${totalPages}` : "No entries found";
}
// Always show and center pagination at the bottom of the leaderboard card
if (this.leaderboardPagination) {
this.leaderboardPagination.classList.remove("hidden");
this.leaderboardPagination.style.display = "flex";
this.leaderboardPagination.style.justifyContent = "center";
this.leaderboardPagination.style.position = "absolute";
this.leaderboardPagination.style.left = 0;
this.leaderboardPagination.style.right = 0;
this.leaderboardPagination.style.bottom = 0;
this.leaderboardPagination.style.margin = "0 auto 0 auto";
this.leaderboardPagination.style.width = "100%";
this.leaderboardPagination.style.background = "inherit";
this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
this.leaderboardPagination.style.padding = "12px 0";
}
// Make sure the parent leaderboard card is relatively positioned
const leaderboardContainer = document.getElementById("leaderboardContainer");
if (leaderboardContainer && leaderboardContainer.parentElement) {
leaderboardContainer.parentElement.style.position = "relative";
leaderboardContainer.parentElement.style.paddingBottom = "56px";
}
// Recalculate ranks for visible rows
this.calculateOverallRanking();
}
calculateOverallRanking() {
const visibleRows = this.allRows.filter(
(row) => row.element.style.display !== "none"
// Only consider visible rows (current page)
const visibleRows = this.filteredRows.slice(
(this.currentPage - 1) * this.itemsPerPage,
(this.currentPage - 1) * this.itemsPerPage + this.itemsPerPage
);
if (visibleRows.length === 0) return;
// Group submissions by problem to find the best performance for each
const problemBests = {};
visibleRows.forEach((rowData) => {
const problem = rowData.problem;
if (!problemBests[problem]) {
@@ -456,7 +563,6 @@ document.addEventListener("DOMContentLoaded", () => {
bestMemory: Infinity,
};
}
problemBests[problem].bestRuntime = Math.min(
problemBests[problem].bestRuntime,
rowData.runtime
@@ -466,144 +572,39 @@ document.addEventListener("DOMContentLoaded", () => {
rowData.memory
);
});
// Calculate normalized scores for each submission
visibleRows.forEach((rowData) => {
const problemBest = problemBests[rowData.problem];
const runtimeScore = problemBest.bestRuntime > 0
const runtimeScore =
problemBest.bestRuntime > 0
? rowData.runtime / problemBest.bestRuntime
: 1;
const memoryScore = problemBest.bestMemory > 0
const memoryScore =
problemBest.bestMemory > 0
? rowData.memory / problemBest.bestMemory
: 1;
// Weighted overall score (70% runtime, 30% memory)
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
});
// Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
visibleRows.sort((a, b) => {
const scoreDiff = a.overallScore - b.overallScore;
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
return a.timestamp - b.timestamp;
});
// Reorder DOM elements and update ranks
const fragment = document.createDocumentFragment();
visibleRows.forEach((rowData) => {
visibleRows.forEach((rowData, index) => {
fragment.appendChild(rowData.element);
// Update rank cell
const rankCell = rowData.element.cells[0];
if (rankCell) rankCell.textContent = index + 1 + (this.currentPage - 1) * this.itemsPerPage;
// Update rank classes
rowData.element.className = rowData.element.className.replace(/\brank-\d+\b/g, "");
if (index === 0) rowData.element.classList.add("rank-1");
else if (index < 3) rowData.element.classList.add("rank-top3");
});
this.leaderboardBody.appendChild(fragment);
this.updateRankClasses();
}
filterLeaderboard() {
const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
const runtimeType = this.runtimeFilter?.value || "all";
// Reset all rows to visible first
this.allRows.forEach((rowData) => {
rowData.element.style.display = "";
});
// Apply problem filter
if (problemTerm) {
this.allRows.forEach((rowData) => {
const problemMatch = rowData.problem.toLowerCase().includes(problemTerm);
if (!problemMatch) {
rowData.element.style.display = "none";
}
});
}
// Apply runtime filter (best/worst per user per problem)
if (runtimeType === "best" || runtimeType === "worst") {
const userProblemGroups = {};
// Group by user + problem combination
this.allRows.forEach((rowData) => {
if (rowData.element.style.display === "none") return;
const key = `${rowData.user}::${rowData.problem}`;
if (!userProblemGroups[key]) {
userProblemGroups[key] = [];
}
userProblemGroups[key].push(rowData);
});
// Hide all except best/worst for each user-problem combination
Object.values(userProblemGroups).forEach((group) => {
if (group.length <= 1) return;
group.sort((a, b) => a.runtime - b.runtime);
const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
group.forEach((rowData, index) => {
if (index !== keepIndex) {
rowData.element.style.display = "none";
}
});
});
}
this.calculateOverallRanking();
}
getCellValue(rowData, column) {
switch (column) {
case "rank":
return parseInt(rowData.element.cells[0]?.textContent) || 0;
case "user":
return rowData.user.toLowerCase();
case "problem":
return rowData.problem.toLowerCase();
case "runtime":
return rowData.runtime;
case "memory":
return rowData.memory;
case "timestamp":
return rowData.timestamp;
case "language":
return rowData.language.toLowerCase();
default:
return "";
}
}
sortLeaderboard(column, direction) {
if (column === "rank") {
this.calculateOverallRanking();
return;
}
const visibleRows = this.allRows.filter(
(row) => row.element.style.display !== "none"
);
visibleRows.sort((a, b) => {
const valueA = this.getCellValue(a, column);
const valueB = this.getCellValue(b, column);
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") {
comparison = valueA - valueB;
} else {
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return direction === "asc" ? comparison : -comparison;
});
// Reorder DOM elements using document fragment for better performance
const fragment = document.createDocumentFragment();
visibleRows.forEach((rowData) => {
fragment.appendChild(rowData.element);
});
this.leaderboardBody.appendChild(fragment);
this.updateRankClasses();
// this.updateRankClasses(); // Function does not exist, so remove this call
}
attachEventListeners() {
@@ -612,12 +613,8 @@ document.addEventListener("DOMContentLoaded", () => {
header.addEventListener("click", () => {
const column = header.dataset.sort;
if (!column) return;
// Remove sorting classes from all headers
this.sortableHeaders.forEach((h) =>
h.classList.remove("sort-asc", "sort-desc")
);
this.sortableHeaders.forEach((h) => h.classList.remove("sort-asc", "sort-desc"));
// Toggle sort direction
if (this.currentSort.column === column) {
this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
@@ -625,20 +622,48 @@ document.addEventListener("DOMContentLoaded", () => {
this.currentSort.column = column;
this.currentSort.direction = "asc";
}
// Add sorting class to current header
header.classList.add(`sort-${this.currentSort.direction}`);
this.sortLeaderboard(column, this.currentSort.direction);
// Sort filteredRows
this.filteredRows.sort((a, b) => {
let valueA = a[column];
let valueB = b[column];
if (typeof valueA === "string") valueA = valueA.toLowerCase();
if (typeof valueB === "string") valueB = valueB.toLowerCase();
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") {
comparison = valueA - valueB;
} else {
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return this.currentSort.direction === "asc" ? comparison : -comparison;
});
this.currentPage = 1;
this.updateLeaderboardPagination();
});
});
// Filter event listeners with debouncing
this.problemFilter?.addEventListener("input",
utils.debounce(() => this.filterLeaderboard(), 300)
);
this.problemFilter?.addEventListener("input", utils.debounce(() => {
this.filterLeaderboard();
}, 300));
this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
// Pagination event listeners
this.leaderboardPrevBtn?.addEventListener("click", () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updateLeaderboardPagination();
}
});
this.leaderboardNextBtn?.addEventListener("click", () => {
const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
if (this.currentPage < totalPages) {
this.currentPage++;
this.updateLeaderboardPagination();
}
});
// Rank info popout
this.rankInfoBtn?.addEventListener("click", (e) => {
e.preventDefault();

View File

@@ -45,9 +45,14 @@ def api_problem_manifest(folder):
except Exception as e:
return jsonify({'error': str(e)}), 500
# I introduce you to the fucking JavaScript shit routes, fuck javascripts
@app.route('/JavaScript/<path:filename>')
def serve_js(filename):
return send_from_directory('JavaScript', filename)
@app.route("/script.js")
def script():
return send_from_directory("templates", "script.js")
return send_from_directory("JavaScript", "script.js")
@app.route('/favicon.ico')
def favicon():

View File

@@ -69,7 +69,7 @@
<section class="card" id="leaderboardSection">
<div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard
<span id="rankInfoBtn" title="How ranking works"></span>
<!--<span id="rankInfoBtn" title="How ranking works"></span>-->
</h2>
</div>
<div class="leaderboard-controls">
@@ -141,6 +141,6 @@
</section>
</div>
</div>
<script src="{{ url_for('static', filename='script.js') }}"></script>
<script src="{{ url_for('serve_js', filename='script.js') }}"></script>
</body>
</html>