Initial commit: SREBOT website (Express/EJS + i18n) - extracted from SREBOT monorepo
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
// Seasons Filter Utility
|
||||
// Parses and manages season/week data from the seasons constant file
|
||||
|
||||
class SeasonsFilter {
|
||||
constructor() {
|
||||
this.seasons = [];
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
// Parse seasons text content into structured data
|
||||
parseSeasons(content) {
|
||||
const seasons = [];
|
||||
const lines = content.split('\n');
|
||||
let currentSeason = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (!line) continue;
|
||||
|
||||
// Check if it's a season header (e.g., "2025-V")
|
||||
const seasonMatch = line.match(/^(\d{4}-[IVX]+)$/);
|
||||
if (seasonMatch) {
|
||||
if (currentSeason) {
|
||||
seasons.push(currentSeason);
|
||||
}
|
||||
currentSeason = {
|
||||
name: seasonMatch[1],
|
||||
weeks: []
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a week line
|
||||
// Format: "week 1 (01.09 – 07.09) <t:1756735246:R>: max BR 14.0"
|
||||
// or: "until eos (27.10 – 31.10) <t:1761487200:R>: max BR 5.0"
|
||||
const weekMatch = line.match(/(?:week\s+(\d+)|until eos)\s+\((\d{2})\.(\d{2})\s*[–—]\s*(\d{2})\.(\d{2})\)\s*<t:(\d+):R>/);
|
||||
if (weekMatch && currentSeason) {
|
||||
const weekNum = weekMatch[1] ? parseInt(weekMatch[1]) : null;
|
||||
const startDay = parseInt(weekMatch[2]);
|
||||
const startMonth = parseInt(weekMatch[3]);
|
||||
const endDay = parseInt(weekMatch[4]);
|
||||
const endMonth = parseInt(weekMatch[5]);
|
||||
const timestamp = parseInt(weekMatch[6]);
|
||||
|
||||
// Extract max BR if present (preserve .0 formatting)
|
||||
const brMatch = line.match(/max BR ([\d.]+)/);
|
||||
const maxBR = brMatch ? brMatch[1] : null; // Keep as string to preserve ".0"
|
||||
|
||||
// Use the Unix timestamp directly from the file (it's already correct!)
|
||||
// The timestamp is the START of the week
|
||||
const startDate = new Date(timestamp * 1000); // Convert to milliseconds
|
||||
|
||||
// Calculate end date: 7 days from start (or until end of season for "until eos")
|
||||
// For "until eos" entries, we'll need to calculate based on the actual end day
|
||||
let endDate;
|
||||
|
||||
// Determine year from season name (e.g., "2025-V" -> 2025)
|
||||
const yearMatch = currentSeason.name.match(/^(\d{4})/);
|
||||
const year = yearMatch ? parseInt(yearMatch[1]) : new Date().getFullYear();
|
||||
|
||||
// Build end date from the parsed end day/month
|
||||
// Need to handle year rollover (e.g., December -> January)
|
||||
let endYear = year;
|
||||
if (endMonth < startMonth) {
|
||||
endYear = year + 1; // Crossed into next year
|
||||
}
|
||||
endDate = new Date(endYear, endMonth - 1, endDay, 23, 59, 59, 999);
|
||||
|
||||
// Build display name with max BR (preserve decimal formatting like 14.0)
|
||||
let displayName;
|
||||
if (weekNum) {
|
||||
displayName = `Week ${weekNum} (${weekMatch[2]}.${weekMatch[3]} – ${weekMatch[4]}.${weekMatch[5]})`;
|
||||
if (maxBR) displayName += ` - BR ${maxBR}`;
|
||||
} else {
|
||||
displayName = `Until EOS (${weekMatch[2]}.${weekMatch[3]} – ${weekMatch[4]}.${weekMatch[5]})`;
|
||||
if (maxBR) displayName += ` - BR ${maxBR}`;
|
||||
}
|
||||
|
||||
currentSeason.weeks.push({
|
||||
weekNumber: weekNum,
|
||||
name: weekNum ? `Week ${weekNum}` : 'Until EOS',
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
startTimestamp: timestamp, // Store original Unix timestamp (seconds)
|
||||
maxBR: maxBR,
|
||||
displayName: displayName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last season
|
||||
if (currentSeason) {
|
||||
seasons.push(currentSeason);
|
||||
}
|
||||
|
||||
// Post-process: set each week's endDate to the next week's startDate - 1ms
|
||||
// This uses the exact Unix timestamps instead of the approximate text-parsed dates
|
||||
seasons.forEach(season => {
|
||||
season.weeks.forEach((week, i) => {
|
||||
if (i + 1 < season.weeks.length) {
|
||||
week.endDate = new Date(season.weeks[i + 1].startDate.getTime() - 1);
|
||||
}
|
||||
// Last week of each season keeps its text-parsed endDate
|
||||
});
|
||||
});
|
||||
|
||||
return seasons;
|
||||
}
|
||||
|
||||
// Load seasons data from the constant file
|
||||
async loadSeasons() {
|
||||
if (this.loaded) {
|
||||
return this.seasons;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/constants/seasons');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load seasons: ${response.status}`);
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
this.seasons = this.parseSeasons(content);
|
||||
this.loaded = true;
|
||||
|
||||
console.log('[Seasons Filter] Loaded seasons:', this.seasons);
|
||||
return this.seasons;
|
||||
} catch (error) {
|
||||
console.error('[Seasons Filter] Error loading seasons:', error);
|
||||
this.seasons = [];
|
||||
this.loaded = false;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get all seasons
|
||||
getSeasons() {
|
||||
return this.seasons;
|
||||
}
|
||||
|
||||
// Get a specific season by name
|
||||
getSeason(seasonName) {
|
||||
return this.seasons.find(s => s.name === seasonName);
|
||||
}
|
||||
|
||||
// Get current season (based on current date)
|
||||
getCurrentSeason() {
|
||||
const now = new Date();
|
||||
|
||||
for (const season of this.seasons) {
|
||||
for (const week of season.weeks) {
|
||||
if (now >= week.startDate && now <= week.endDate) {
|
||||
return season;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no current season found, return the most recent one
|
||||
return this.seasons.length > 0 ? this.seasons[this.seasons.length - 1] : null;
|
||||
}
|
||||
|
||||
// Get current week (based on current date)
|
||||
getCurrentWeek() {
|
||||
const now = new Date();
|
||||
|
||||
for (const season of this.seasons) {
|
||||
for (const week of season.weeks) {
|
||||
if (now >= week.startDate && now <= week.endDate) {
|
||||
return { season, week };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get date range for a specific season
|
||||
getSeasonDateRange(seasonName) {
|
||||
const season = this.getSeason(seasonName);
|
||||
if (!season || season.weeks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
startDate: season.weeks[0].startDate,
|
||||
endDate: season.weeks[season.weeks.length - 1].endDate,
|
||||
season: season
|
||||
};
|
||||
}
|
||||
|
||||
// Get date range for a specific week in a season
|
||||
getWeekDateRange(seasonName, weekNumber) {
|
||||
const season = this.getSeason(seasonName);
|
||||
if (!season) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const week = season.weeks.find(w => w.weekNumber === weekNumber);
|
||||
if (!week) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
startDate: week.startDate,
|
||||
endDate: week.endDate,
|
||||
week: week
|
||||
};
|
||||
}
|
||||
|
||||
// Get all unique BR values across all seasons (returns array of strings, preserving ".0")
|
||||
getAllBRValues() {
|
||||
const brSet = new Set();
|
||||
|
||||
this.seasons.forEach(season => {
|
||||
season.weeks.forEach(week => {
|
||||
if (week.maxBR) {
|
||||
brSet.add(week.maxBR);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sort descending (convert to float for comparison, but keep as strings)
|
||||
return Array.from(brSet).sort((a, b) => parseFloat(b) - parseFloat(a));
|
||||
}
|
||||
|
||||
// Get all weeks that match a specific BR
|
||||
getWeeksByBR(maxBR) {
|
||||
const matchingWeeks = [];
|
||||
|
||||
this.seasons.forEach(season => {
|
||||
season.weeks.forEach(week => {
|
||||
if (week.maxBR === maxBR) {
|
||||
matchingWeeks.push({
|
||||
season: season,
|
||||
week: week,
|
||||
displayName: `${season.name} - ${week.name}`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return matchingWeeks;
|
||||
}
|
||||
|
||||
// Get combined date range for all weeks with a specific BR (for filtering multiple weeks)
|
||||
getDateRangeForBR(maxBR) {
|
||||
const weeks = this.getWeeksByBR(maxBR);
|
||||
|
||||
if (weeks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return array of date ranges (one for each week with this BR)
|
||||
return weeks.map(({ season, week }) => ({
|
||||
startDate: week.startDate,
|
||||
endDate: week.endDate,
|
||||
seasonName: season.name,
|
||||
weekName: week.name,
|
||||
displayName: `${season.name} - ${week.displayName}`
|
||||
}));
|
||||
}
|
||||
|
||||
// Populate a select element with season options
|
||||
populateSeasonSelect(selectElement, includeAllOption = true) {
|
||||
selectElement.innerHTML = '';
|
||||
|
||||
if (includeAllOption) {
|
||||
const allOption = document.createElement('option');
|
||||
allOption.value = 'all';
|
||||
allOption.textContent = window.__t ? window.__t('leaderboard.allSeasons') : 'All Seasons';
|
||||
selectElement.appendChild(allOption);
|
||||
}
|
||||
|
||||
// Add seasons in reverse order (most recent first)
|
||||
for (let i = this.seasons.length - 1; i >= 0; i--) {
|
||||
const season = this.seasons[i];
|
||||
const option = document.createElement('option');
|
||||
option.value = season.name;
|
||||
option.textContent = season.name;
|
||||
selectElement.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate a select element with BR options
|
||||
populateBRSelect(selectElement, includeAllOption = true) {
|
||||
selectElement.innerHTML = '';
|
||||
|
||||
if (includeAllOption) {
|
||||
const allOption = document.createElement('option');
|
||||
allOption.value = '';
|
||||
allOption.textContent = window.__t ? window.__t('leaderboard.allBR') : 'All BR';
|
||||
selectElement.appendChild(allOption);
|
||||
}
|
||||
|
||||
const brValues = this.getAllBRValues();
|
||||
brValues.forEach(br => {
|
||||
const option = document.createElement('option');
|
||||
option.value = br;
|
||||
option.textContent = `BR ${br}`;
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Populate a select element with week options for a given season
|
||||
populateWeekSelect(selectElement, seasonName, includeAllOption = true) {
|
||||
selectElement.innerHTML = '';
|
||||
|
||||
if (includeAllOption) {
|
||||
const allOption = document.createElement('option');
|
||||
allOption.value = 'all';
|
||||
allOption.textContent = window.__t ? window.__t('leaderboard.allWeeks') : 'All Weeks';
|
||||
selectElement.appendChild(allOption);
|
||||
}
|
||||
|
||||
const season = this.getSeason(seasonName);
|
||||
if (!season) {
|
||||
return;
|
||||
}
|
||||
|
||||
season.weeks.forEach(week => {
|
||||
const option = document.createElement('option');
|
||||
option.value = week.weekNumber !== null ? week.weekNumber.toString() : 'final';
|
||||
option.textContent = week.displayName;
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Global instance
|
||||
window.seasonsFilter = window.seasonsFilter || new SeasonsFilter();
|
||||
|
||||
Reference in New Issue
Block a user