fixed some shit ; added pagination
This commit is contained in:
@@ -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", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -170,6 +178,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
borderTop: "1px solid var(--border)",
|
borderTop: "1px solid var(--border)",
|
||||||
padding: "12px 0"
|
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");
|
this.problemsPagination.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,17 +407,55 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
this.currentSort = { column: "rank", direction: "asc" };
|
this.currentSort = { column: "rank", direction: "asc" };
|
||||||
this.allRows = [];
|
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();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
|
if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
|
||||||
|
|
||||||
this.initializeRows();
|
this.initializeRows();
|
||||||
this.attachEventListeners();
|
this.attachEventListeners();
|
||||||
this.calculateOverallRanking();
|
this.filterLeaderboard();
|
||||||
this.setInitialSortIndicator();
|
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() {
|
initializeRows() {
|
||||||
@@ -418,36 +471,90 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRankClasses() {
|
filterLeaderboard() {
|
||||||
const visibleRows = this.allRows.filter(
|
const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
|
||||||
(row) => row.element.style.display !== "none"
|
const runtimeType = this.runtimeFilter?.value || "all";
|
||||||
);
|
// Filter rows
|
||||||
|
this.filteredRows = this.allRows.filter((rowData) => {
|
||||||
visibleRows.forEach((rowData, index) => {
|
let visible = true;
|
||||||
const rank = index + 1;
|
if (problemTerm) {
|
||||||
const row = rowData.element;
|
visible = rowData.problem.toLowerCase().includes(problemTerm);
|
||||||
|
}
|
||||||
// Update rank cell
|
return visible;
|
||||||
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");
|
|
||||||
});
|
});
|
||||||
|
// 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() {
|
calculateOverallRanking() {
|
||||||
const visibleRows = this.allRows.filter(
|
// Only consider visible rows (current page)
|
||||||
(row) => row.element.style.display !== "none"
|
const visibleRows = this.filteredRows.slice(
|
||||||
|
(this.currentPage - 1) * this.itemsPerPage,
|
||||||
|
(this.currentPage - 1) * this.itemsPerPage + this.itemsPerPage
|
||||||
);
|
);
|
||||||
|
|
||||||
if (visibleRows.length === 0) return;
|
if (visibleRows.length === 0) return;
|
||||||
|
|
||||||
// Group submissions by problem to find the best performance for each
|
// Group submissions by problem to find the best performance for each
|
||||||
const problemBests = {};
|
const problemBests = {};
|
||||||
|
|
||||||
visibleRows.forEach((rowData) => {
|
visibleRows.forEach((rowData) => {
|
||||||
const problem = rowData.problem;
|
const problem = rowData.problem;
|
||||||
if (!problemBests[problem]) {
|
if (!problemBests[problem]) {
|
||||||
@@ -456,7 +563,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
bestMemory: Infinity,
|
bestMemory: Infinity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
problemBests[problem].bestRuntime = Math.min(
|
problemBests[problem].bestRuntime = Math.min(
|
||||||
problemBests[problem].bestRuntime,
|
problemBests[problem].bestRuntime,
|
||||||
rowData.runtime
|
rowData.runtime
|
||||||
@@ -466,144 +572,39 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
rowData.memory
|
rowData.memory
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate normalized scores for each submission
|
// Calculate normalized scores for each submission
|
||||||
visibleRows.forEach((rowData) => {
|
visibleRows.forEach((rowData) => {
|
||||||
const problemBest = problemBests[rowData.problem];
|
const problemBest = problemBests[rowData.problem];
|
||||||
|
const runtimeScore =
|
||||||
const runtimeScore = problemBest.bestRuntime > 0
|
problemBest.bestRuntime > 0
|
||||||
? rowData.runtime / problemBest.bestRuntime
|
? rowData.runtime / problemBest.bestRuntime
|
||||||
: 1;
|
: 1;
|
||||||
const memoryScore = problemBest.bestMemory > 0
|
const memoryScore =
|
||||||
|
problemBest.bestMemory > 0
|
||||||
? rowData.memory / problemBest.bestMemory
|
? rowData.memory / problemBest.bestMemory
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
// Weighted overall score (70% runtime, 30% memory)
|
|
||||||
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
|
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
|
// Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
|
||||||
visibleRows.sort((a, b) => {
|
visibleRows.sort((a, b) => {
|
||||||
const scoreDiff = a.overallScore - b.overallScore;
|
const scoreDiff = a.overallScore - b.overallScore;
|
||||||
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
|
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
|
||||||
return a.timestamp - b.timestamp;
|
return a.timestamp - b.timestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reorder DOM elements and update ranks
|
// Reorder DOM elements and update ranks
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
visibleRows.forEach((rowData) => {
|
visibleRows.forEach((rowData, index) => {
|
||||||
fragment.appendChild(rowData.element);
|
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.leaderboardBody.appendChild(fragment);
|
||||||
|
// this.updateRankClasses(); // Function does not exist, so remove this call
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachEventListeners() {
|
attachEventListeners() {
|
||||||
@@ -612,12 +613,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
header.addEventListener("click", () => {
|
header.addEventListener("click", () => {
|
||||||
const column = header.dataset.sort;
|
const column = header.dataset.sort;
|
||||||
if (!column) return;
|
if (!column) return;
|
||||||
|
|
||||||
// Remove sorting classes from all headers
|
// Remove sorting classes from all headers
|
||||||
this.sortableHeaders.forEach((h) =>
|
this.sortableHeaders.forEach((h) => h.classList.remove("sort-asc", "sort-desc"));
|
||||||
h.classList.remove("sort-asc", "sort-desc")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Toggle sort direction
|
// Toggle sort direction
|
||||||
if (this.currentSort.column === column) {
|
if (this.currentSort.column === column) {
|
||||||
this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
|
this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
|
||||||
@@ -625,20 +622,48 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
this.currentSort.column = column;
|
this.currentSort.column = column;
|
||||||
this.currentSort.direction = "asc";
|
this.currentSort.direction = "asc";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sorting class to current header
|
// Add sorting class to current header
|
||||||
header.classList.add(`sort-${this.currentSort.direction}`);
|
header.classList.add(`sort-${this.currentSort.direction}`);
|
||||||
|
// Sort filteredRows
|
||||||
this.sortLeaderboard(column, this.currentSort.direction);
|
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
|
// Filter event listeners with debouncing
|
||||||
this.problemFilter?.addEventListener("input",
|
this.problemFilter?.addEventListener("input", utils.debounce(() => {
|
||||||
utils.debounce(() => this.filterLeaderboard(), 300)
|
this.filterLeaderboard();
|
||||||
);
|
}, 300));
|
||||||
this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
|
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
|
// Rank info popout
|
||||||
this.rankInfoBtn?.addEventListener("click", (e) => {
|
this.rankInfoBtn?.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -45,9 +45,14 @@ def api_problem_manifest(folder):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
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")
|
@app.route("/script.js")
|
||||||
def script():
|
def script():
|
||||||
return send_from_directory("templates", "script.js")
|
return send_from_directory("JavaScript", "script.js")
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
def favicon():
|
def favicon():
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<section class="card" id="leaderboardSection">
|
<section class="card" id="leaderboardSection">
|
||||||
<div class="leaderboard-head">
|
<div class="leaderboard-head">
|
||||||
<h2 style="font-size:1.1rem;margin:0">Leaderboard
|
<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>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="leaderboard-controls">
|
<div class="leaderboard-controls">
|
||||||
@@ -141,6 +141,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
<script src="{{ url_for('serve_js', filename='script.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user