/** * Timeline functionality for FitPub * Handles loading and displaying timeline activities with preview maps */ const FitPubTimeline = { currentPage: 0, totalPages: 0, timelineType: 'public', /** * Initialize the timeline * @param {string} type - Timeline type: 'public', 'federated', or 'user' */ init: function(type) { this.timelineType = type; this.loadTimeline(0); }, /** * Load timeline activities * @param {number} page - Page number to load */ loadTimeline: async function(page) { const loadingIndicator = document.getElementById('loadingIndicator'); const errorAlert = document.getElementById('errorAlert'); const errorMessage = document.getElementById('errorMessage'); const timelineList = document.getElementById('timelineList'); const emptyState = document.getElementById('emptyState'); const pagination = document.getElementById('pagination'); try { // Show loading loadingIndicator.classList.remove('d-none'); timelineList.classList.add('d-none'); emptyState.classList.add('d-none'); errorAlert.classList.add('d-none'); pagination.classList.add('d-none'); // Determine endpoint let endpoint; let fetchOptions = {}; switch (this.timelineType) { case 'public': endpoint = `/api/timeline/public?page=${page}&size=20`; // Public timeline is optionally authenticated fetchOptions = { useAuth: FitPubAuth.isAuthenticated() }; break; case 'federated': endpoint = `/api/timeline/federated?page=${page}&size=20`; fetchOptions = { useAuth: true }; break; case 'user': endpoint = `/api/timeline/user?page=${page}&size=20`; fetchOptions = { useAuth: true }; break; default: throw new Error('Invalid timeline type'); } // Fetch timeline data const response = fetchOptions.useAuth ? await FitPubAuth.authenticatedFetch(endpoint) : await fetch(endpoint); if (response.ok) { const data = await response.json(); // Hide loading loadingIndicator.classList.add('d-none'); if (data.content && data.content.length > 0) { this.renderTimeline(data.content); this.renderPagination(data); timelineList.classList.remove('d-none'); pagination.classList.remove('d-none'); } else { emptyState.classList.remove('d-none'); } this.totalPages = data.totalPages; this.currentPage = data.number; } else { throw new Error('Failed to load timeline'); } } catch (error) { console.error('Error loading timeline:', error); loadingIndicator.classList.add('d-none'); errorMessage.textContent = 'Failed to load timeline. Please try again.'; errorAlert.classList.remove('d-none'); } }, /** * Render timeline activities * @param {Array} activities - Array of timeline activity objects */ renderTimeline: function(activities) { const timelineList = document.getElementById('timelineList'); timelineList.innerHTML = activities.map((activity, index) => { const mapId = `map-${activity.id}`; return `
${this.escapeHtml(activity.description).substring(0, 200)}${activity.description.length > 200 ? '...' : ''}
` : '' }No GPS data available
Failed to load map