fixed some shit ; added pagination
This commit is contained in:
@@ -1,692 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
"use strict";
|
||||
|
||||
// Utility functions
|
||||
const utils = {
|
||||
safeLocalStorage: {
|
||||
getItem(key) {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch (e) {
|
||||
console.warn("localStorage.getItem failed:", e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setItem(key, value) {
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn("localStorage.setItem failed:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func.apply(this, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
|
||||
throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Dark Mode Manager
|
||||
class DarkModeManager {
|
||||
constructor() {
|
||||
this.darkModeToggle = document.getElementById("darkModeToggle");
|
||||
this.html = document.documentElement;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.loadSavedPreference();
|
||||
this.attachEventListeners();
|
||||
}
|
||||
|
||||
loadSavedPreference() {
|
||||
const savedDarkMode = utils.safeLocalStorage.getItem("darkMode");
|
||||
if (
|
||||
savedDarkMode === "true" ||
|
||||
(savedDarkMode === null &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
this.html.classList.add("dark");
|
||||
}
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
this.darkModeToggle?.addEventListener("click", () => {
|
||||
this.html.classList.toggle("dark");
|
||||
utils.safeLocalStorage.setItem("darkMode", this.html.classList.contains("dark"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Problem Manager
|
||||
class ProblemManager {
|
||||
constructor() {
|
||||
this.problemSearch = document.getElementById("problemSearch");
|
||||
this.problemsContainer = document.getElementById("problemsContainer");
|
||||
this.problemsPagination = document.getElementById("problemsPagination");
|
||||
this.problemsPrevBtn = document.getElementById("problemsPrevBtn");
|
||||
this.problemsNextBtn = document.getElementById("problemsNextBtn");
|
||||
this.problemsPaginationInfo = document.getElementById("problemsPaginationInfo");
|
||||
this.difficultyFilter = document.getElementById("difficultyFilter");
|
||||
this.sortProblems = document.getElementById("sortProblems");
|
||||
|
||||
this.allProblemItems = [];
|
||||
this.filteredProblemItems = [];
|
||||
this.currentPage = 1;
|
||||
this.itemsPerPage = 5;
|
||||
this.problemSort = { column: "alpha", direction: "asc" };
|
||||
this.problemDescriptionPopover = null;
|
||||
this.manifestCache = new Map();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.problemsContainer) return;
|
||||
|
||||
this.initializeProblemItems();
|
||||
this.attachEventListeners();
|
||||
this.injectPopoverCSS();
|
||||
this.attachProblemHoverEvents();
|
||||
}
|
||||
|
||||
initializeProblemItems() {
|
||||
this.allProblemItems = Array.from(
|
||||
this.problemsContainer.querySelectorAll(".problem-item") || []
|
||||
);
|
||||
this.filteredProblemItems = this.allProblemItems.map(this.getProblemData);
|
||||
this.updatePagination();
|
||||
}
|
||||
|
||||
getProblemData = (item) => ({
|
||||
element: item,
|
||||
name: item.dataset.name?.toLowerCase() || "",
|
||||
desc: item.dataset.desc?.toLowerCase() || "",
|
||||
difficulty: item.dataset.difficulty || "",
|
||||
});
|
||||
|
||||
updatePagination() {
|
||||
const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
|
||||
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const endIndex = startIndex + this.itemsPerPage;
|
||||
|
||||
// Hide all items first
|
||||
this.allProblemItems.forEach((item) => {
|
||||
item.style.display = "none";
|
||||
});
|
||||
|
||||
// Show current page items
|
||||
this.filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
|
||||
item.element.style.display = "";
|
||||
});
|
||||
|
||||
// Update pagination controls
|
||||
if (this.problemsPrevBtn) this.problemsPrevBtn.disabled = this.currentPage <= 1;
|
||||
if (this.problemsNextBtn) this.problemsNextBtn.disabled = this.currentPage >= totalPages;
|
||||
|
||||
if (this.problemsPaginationInfo) {
|
||||
this.problemsPaginationInfo.textContent =
|
||||
totalPages > 0
|
||||
? `Page ${this.currentPage} of ${totalPages}`
|
||||
: "No problems found";
|
||||
}
|
||||
|
||||
this.setupPaginationLayout();
|
||||
}
|
||||
|
||||
setupPaginationLayout() {
|
||||
if (this.problemsPagination) {
|
||||
Object.assign(this.problemsPagination.style, {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
position: "absolute",
|
||||
left: "0",
|
||||
right: "0",
|
||||
bottom: "0",
|
||||
margin: "0 auto",
|
||||
width: "100%",
|
||||
background: "inherit",
|
||||
borderTop: "1px solid var(--border)",
|
||||
padding: "12px 0"
|
||||
});
|
||||
this.problemsPagination.classList.remove("hidden");
|
||||
}
|
||||
|
||||
if (this.problemsContainer?.parentElement) {
|
||||
Object.assign(this.problemsContainer.parentElement.style, {
|
||||
position: "relative",
|
||||
paddingBottom: "56px"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showProblemDescription = async (item) => {
|
||||
this.hideProblemDescription();
|
||||
|
||||
const folder = item.querySelector('a')?.getAttribute('href')?.split('/').pop();
|
||||
if (!folder) return;
|
||||
|
||||
try {
|
||||
let manifest = this.manifestCache.get(folder);
|
||||
|
||||
if (!manifest) {
|
||||
// Try localStorage cache first
|
||||
const cacheKey = `problem_manifest_${folder}`;
|
||||
const cached = utils.safeLocalStorage.getItem(cacheKey);
|
||||
|
||||
if (cached) {
|
||||
manifest = JSON.parse(cached);
|
||||
this.manifestCache.set(folder, manifest);
|
||||
} else {
|
||||
// Fetch from API
|
||||
const response = await fetch(`/api/problem_manifest/${encodeURIComponent(folder)}`);
|
||||
manifest = response.ok ? await response.json() : { description: 'No description.' };
|
||||
|
||||
this.manifestCache.set(folder, manifest);
|
||||
utils.safeLocalStorage.setItem(cacheKey, JSON.stringify(manifest));
|
||||
}
|
||||
}
|
||||
|
||||
this.createPopover(manifest.description || 'No description.', item);
|
||||
} catch (error) {
|
||||
console.warn("Failed to load problem description:", error);
|
||||
this.createPopover('No description available.', item);
|
||||
}
|
||||
};
|
||||
|
||||
createPopover(description, item) {
|
||||
this.problemDescriptionPopover = document.createElement("div");
|
||||
this.problemDescriptionPopover.className = "problem-desc-popover";
|
||||
this.problemDescriptionPopover.textContent = description;
|
||||
document.body.appendChild(this.problemDescriptionPopover);
|
||||
|
||||
const rect = item.getBoundingClientRect();
|
||||
Object.assign(this.problemDescriptionPopover.style, {
|
||||
position: "fixed",
|
||||
left: `${rect.left + window.scrollX}px`,
|
||||
top: `${rect.bottom + window.scrollY + 6}px`,
|
||||
zIndex: "1000",
|
||||
minWidth: `${rect.width}px`
|
||||
});
|
||||
}
|
||||
|
||||
hideProblemDescription = () => {
|
||||
if (this.problemDescriptionPopover) {
|
||||
this.problemDescriptionPopover.remove();
|
||||
this.problemDescriptionPopover = null;
|
||||
}
|
||||
};
|
||||
|
||||
attachProblemHoverEvents() {
|
||||
this.allProblemItems.forEach((item) => {
|
||||
item.addEventListener("mouseenter", () => this.showProblemDescription(item));
|
||||
item.addEventListener("mouseleave", this.hideProblemDescription);
|
||||
item.addEventListener("mousemove", this.handleMouseMove);
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseMove = utils.throttle((e) => {
|
||||
if (this.problemDescriptionPopover) {
|
||||
this.problemDescriptionPopover.style.left = `${e.clientX + 10}px`;
|
||||
}
|
||||
}, 16); // ~60fps
|
||||
|
||||
sortProblemItems(column, direction) {
|
||||
this.filteredProblemItems.sort((a, b) => {
|
||||
let valueA, valueB;
|
||||
|
||||
switch (column) {
|
||||
case "alpha":
|
||||
valueA = a.name;
|
||||
valueB = b.name;
|
||||
break;
|
||||
case "difficulty":
|
||||
const difficultyOrder = { easy: 1, medium: 2, hard: 3 };
|
||||
valueA = difficultyOrder[a.difficulty] || 0;
|
||||
valueB = difficultyOrder[b.difficulty] || 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
this.problemsPrevBtn?.addEventListener("click", () => {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
this.updatePagination();
|
||||
}
|
||||
});
|
||||
|
||||
this.problemsNextBtn?.addEventListener("click", () => {
|
||||
const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
|
||||
if (this.currentPage < totalPages) {
|
||||
this.currentPage++;
|
||||
this.updatePagination();
|
||||
}
|
||||
});
|
||||
|
||||
this.problemSearch?.addEventListener("input", utils.debounce(() => {
|
||||
this.filterProblems();
|
||||
this.currentPage = 1;
|
||||
this.updatePagination();
|
||||
}, 300));
|
||||
|
||||
this.difficultyFilter?.addEventListener("change", () => {
|
||||
this.filterProblems();
|
||||
this.currentPage = 1;
|
||||
this.updatePagination();
|
||||
});
|
||||
|
||||
this.sortProblems?.addEventListener("change", () => {
|
||||
const value = this.sortProblems.value;
|
||||
if (value === "alpha" || value === "difficulty") {
|
||||
if (this.problemSort.column === value) {
|
||||
this.problemSort.direction = this.problemSort.direction === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
this.problemSort.column = value;
|
||||
this.problemSort.direction = "asc";
|
||||
}
|
||||
this.sortProblemItems(this.problemSort.column, this.problemSort.direction);
|
||||
this.currentPage = 1;
|
||||
this.updatePagination();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
filterProblems() {
|
||||
const searchTerm = (this.problemSearch?.value || "").toLowerCase().trim();
|
||||
const difficulty = this.difficultyFilter?.value || "all";
|
||||
|
||||
this.filteredProblemItems = this.allProblemItems
|
||||
.map(this.getProblemData)
|
||||
.filter(item => {
|
||||
const matchesSearch = !searchTerm ||
|
||||
item.name.includes(searchTerm) ||
|
||||
item.desc.includes(searchTerm);
|
||||
|
||||
const matchesDifficulty = difficulty === "all" ||
|
||||
item.difficulty === difficulty;
|
||||
|
||||
return matchesSearch && matchesDifficulty;
|
||||
});
|
||||
}
|
||||
|
||||
injectPopoverCSS() {
|
||||
if (document.getElementById("problem-desc-popover-style")) return;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = "problem-desc-popover-style";
|
||||
style.textContent = `
|
||||
.problem-desc-popover {
|
||||
background: var(--card, #fff);
|
||||
color: var(--text, #222);
|
||||
border: 1px solid var(--border, #e5e7eb);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(16,24,40,0.13);
|
||||
padding: 12px 16px;
|
||||
font-size: 0.98rem;
|
||||
max-width: 350px;
|
||||
min-width: 180px;
|
||||
pointer-events: none;
|
||||
opacity: 0.97;
|
||||
transition: opacity 0.2s;
|
||||
word-break: break-word;
|
||||
}
|
||||
html.dark .problem-desc-popover {
|
||||
background: var(--card, #1e293b);
|
||||
color: var(--text, #f1f5f9);
|
||||
border: 1px solid var(--border, #334155);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Clean up event listeners and resources
|
||||
this.hideProblemDescription();
|
||||
this.manifestCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Leaderboard Manager
|
||||
class LeaderboardManager {
|
||||
constructor() {
|
||||
this.problemFilter = document.getElementById("problemFilter");
|
||||
this.runtimeFilter = document.getElementById("runtimeFilter");
|
||||
this.leaderboardBody = document.getElementById("leaderboardBody");
|
||||
this.sortableHeaders = document.querySelectorAll(".sortable");
|
||||
this.rankInfoBtn = document.getElementById("rankInfoBtn");
|
||||
this.rankingExplanation = document.getElementById("rankingExplanation");
|
||||
|
||||
this.currentSort = { column: "rank", direction: "asc" };
|
||||
this.allRows = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
|
||||
|
||||
this.initializeRows();
|
||||
this.attachEventListeners();
|
||||
this.calculateOverallRanking();
|
||||
this.setInitialSortIndicator();
|
||||
}
|
||||
|
||||
initializeRows() {
|
||||
this.allRows = Array.from(this.leaderboardBody.querySelectorAll("tr")).map((row, index) => ({
|
||||
element: row,
|
||||
user: row.dataset.user || "",
|
||||
problem: row.dataset.problem || "",
|
||||
runtime: parseFloat(row.dataset.runtime) || 0,
|
||||
memory: parseFloat(row.dataset.memory) || 0,
|
||||
timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
|
||||
language: row.dataset.language || "",
|
||||
originalIndex: index,
|
||||
}));
|
||||
}
|
||||
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
calculateOverallRanking() {
|
||||
const visibleRows = this.allRows.filter(
|
||||
(row) => row.element.style.display !== "none"
|
||||
);
|
||||
|
||||
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]) {
|
||||
problemBests[problem] = {
|
||||
bestRuntime: Infinity,
|
||||
bestMemory: Infinity,
|
||||
};
|
||||
}
|
||||
|
||||
problemBests[problem].bestRuntime = Math.min(
|
||||
problemBests[problem].bestRuntime,
|
||||
rowData.runtime
|
||||
);
|
||||
problemBests[problem].bestMemory = Math.min(
|
||||
problemBests[problem].bestMemory,
|
||||
rowData.memory
|
||||
);
|
||||
});
|
||||
|
||||
// Calculate normalized scores for each submission
|
||||
visibleRows.forEach((rowData) => {
|
||||
const problemBest = problemBests[rowData.problem];
|
||||
|
||||
const runtimeScore = problemBest.bestRuntime > 0
|
||||
? rowData.runtime / problemBest.bestRuntime
|
||||
: 1;
|
||||
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) => {
|
||||
fragment.appendChild(rowData.element);
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
// Sorting event listeners
|
||||
this.sortableHeaders.forEach((header) => {
|
||||
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")
|
||||
);
|
||||
|
||||
// Toggle sort direction
|
||||
if (this.currentSort.column === column) {
|
||||
this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
// Filter event listeners with debouncing
|
||||
this.problemFilter?.addEventListener("input",
|
||||
utils.debounce(() => this.filterLeaderboard(), 300)
|
||||
);
|
||||
this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
|
||||
|
||||
// Rank info popout
|
||||
this.rankInfoBtn?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.rankingExplanation?.classList.toggle("active");
|
||||
this.rankInfoBtn?.classList.toggle("active");
|
||||
});
|
||||
|
||||
// Close ranking explanation when clicking outside
|
||||
document.addEventListener("click", (e) => {
|
||||
if (
|
||||
this.rankingExplanation?.classList.contains("active") &&
|
||||
!this.rankingExplanation.contains(e.target) &&
|
||||
!this.rankInfoBtn?.contains(e.target)
|
||||
) {
|
||||
this.rankingExplanation.classList.remove("active");
|
||||
this.rankInfoBtn?.classList.remove("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setInitialSortIndicator() {
|
||||
const defaultHeader = document.querySelector('[data-sort="rank"]');
|
||||
if (defaultHeader) {
|
||||
defaultHeader.classList.add("sort-asc");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize all managers
|
||||
const darkModeManager = new DarkModeManager();
|
||||
const problemManager = new ProblemManager();
|
||||
const leaderboardManager = new LeaderboardManager();
|
||||
|
||||
// Apply dark mode to dynamically created elements
|
||||
const applyDarkModeToElements = () => {
|
||||
// Any additional dark mode styling for dynamically created elements can go here
|
||||
};
|
||||
|
||||
// Watch for dark mode changes
|
||||
const darkModeObserver = new MutationObserver(applyDarkModeToElements);
|
||||
darkModeObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener("beforeunload", () => {
|
||||
problemManager.destroy();
|
||||
darkModeObserver.disconnect();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user