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;
}
}