333 lines
12 KiB
JavaScript
333 lines
12 KiB
JavaScript
// 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();
|
||
|