import { MONTHS } from '../config.js'; export class Timeline { constructor(container) { this.container = container; this.events = []; this.sortedEvents = []; this.selectedIndex = -1; this.isPlaying = false; this.onEventSelect = () => {}; this.onPlayStateChange = () => {}; } setEvents(events) { this.events = events; // Sort events by date for playback this.sortedEvents = [...events].sort((a, b) => new Date(a.date) - new Date(b.date)); this.render(); } render() { const grouped = this.groupByMonth(this.events); const sortedKeys = Object.keys(grouped).sort(); let currentYear = null; let monthsHtml = ''; for (const monthKey of sortedKeys) { const [year] = monthKey.split('-'); // Add year separator when year changes if (year !== currentYear) { monthsHtml += `
${year}
`; currentYear = year; } monthsHtml += this.renderMonth(monthKey, grouped[monthKey]); } this.container.innerHTML = `
${monthsHtml}
`; this.attachEventListeners(); } groupByMonth(events) { return events.reduce((acc, event) => { const date = new Date(event.date); const key = `${date.getFullYear()}-${String(date.getMonth()).padStart(2, '0')}`; if (!acc[key]) { acc[key] = []; } acc[key].push(event); return acc; }, {}); } renderMonth(monthKey, events) { const [year, month] = monthKey.split('-'); const monthName = MONTHS[parseInt(month)]; const eventsHtml = events .sort((a, b) => new Date(a.date) - new Date(b.date)) .map(event => this.renderEvent(event)) .join(''); return `
${monthName}
${eventsHtml}
`; } renderEvent(event) { const date = new Date(event.date); const day = date.getDate(); const eventIndex = this.sortedEvents.findIndex(e => e.id === event.id); const isSelected = eventIndex === this.selectedIndex; return ` `; } attachEventListeners() { // Play button const playBtn = this.container.querySelector('.timeline-play-btn'); if (playBtn) { playBtn.addEventListener('click', () => this.togglePlay()); } // Event clicks this.container.querySelectorAll('.timeline-event').forEach(el => { el.addEventListener('click', () => { const eventIndex = parseInt(el.dataset.eventIndex, 10); this.selectEventByIndex(eventIndex); }); }); } togglePlay() { this.isPlaying = !this.isPlaying; this.updatePlayButton(); this.onPlayStateChange(this.isPlaying); } play() { this.isPlaying = true; this.updatePlayButton(); } pause() { this.isPlaying = false; this.updatePlayButton(); } updatePlayButton() { const playBtn = this.container.querySelector('.timeline-play-btn'); if (playBtn) { playBtn.classList.toggle('playing', this.isPlaying); playBtn.setAttribute('aria-label', this.isPlaying ? 'Pause' : 'Play'); } } selectEventByIndex(index) { if (index < 0 || index >= this.sortedEvents.length) return; this.selectedIndex = index; const event = this.sortedEvents[index]; // Update visual selection this.container.querySelectorAll('.timeline-event').forEach(el => { const elIndex = parseInt(el.dataset.eventIndex, 10); el.classList.toggle('selected', elIndex === index); }); // Scroll event into view const eventEl = this.container.querySelector(`[data-event-index="${index}"]`); if (eventEl) { eventEl.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); } this.onEventSelect(event, index); } selectEvent(eventId) { const index = this.sortedEvents.findIndex(e => e.id === eventId); if (index !== -1) { this.selectEventByIndex(index); } } // Advance to next event, returns false if at end nextEvent() { if (this.selectedIndex < this.sortedEvents.length - 1) { this.selectEventByIndex(this.selectedIndex + 1); return true; } return false; } getCurrentEvent() { if (this.selectedIndex >= 0 && this.selectedIndex < this.sortedEvents.length) { return this.sortedEvents[this.selectedIndex]; } return null; } getNextEvent() { if (this.selectedIndex >= 0 && this.selectedIndex < this.sortedEvents.length - 1) { return this.sortedEvents[this.selectedIndex + 1]; } return null; } clearSelection() { this.selectedIndex = -1; this.container.querySelectorAll('.timeline-event').forEach(el => { el.classList.remove('selected'); }); } setOnEventSelect(callback) { this.onEventSelect = callback; } setOnPlayStateChange(callback) { this.onPlayStateChange = callback; } }