const CLIENT_ID = '163773'; let accessToken = ''; let latlngs = []; let activities = []; let activityIndex = 0; // Detect access token from redirect const params = new URLSearchParams(window.location.search); //------------------------------------------------------------------------------- const showMap = (activity) => { document.getElementById("login").style.display = "none"; document.getElementById("map").style.display = "block"; const API_KEY = 'Dp0Y4WgtEgqMzlXttJnbX9GAg2Ijj9lZUaFawE466Gk'; const map = L.map('map').setView(activity.latlngs[0], 16); L.tileLayer(`https://api.mapy.com/v1/maptiles/outdoor/256/{z}/{x}/{y}?apikey=${API_KEY}`, { minZoom: 0, maxZoom: 19, attribution: '© Seznam.cz a.s. a další', }).addTo(map); const LogoControl = L.Control.extend({ options: { position: 'bottomleft' }, onAdd: function (map) { const container = L.DomUtil.create('div'); const link = L.DomUtil.create('a', '', container); link.setAttribute('href', 'http://mapy.com/'); link.setAttribute('target', '_blank'); link.innerHTML = ''; L.DomEvent.disableClickPropagation(link); return container; }, }); new LogoControl().addTo(map); const StravaLogoControl = L.Control.extend({ options: { position: 'bottomright' }, onAdd: function (map) { const container = L.DomUtil.create('div'); const img = L.DomUtil.create('img', '', container); img.src = '/api_logo_pwrdBy_strava_stack_orange.png'; img.style.width = '100px'; // optional size control //img.style.opacity = '0.8'; // optional transparency return container; }, }); new StravaLogoControl().addTo(map); const polyline = L.polyline(activity.latlngs, { color: 'blue', weight: 5, opacity: 0.6, dashArray: [10, 10] }) .addTo(map); // Create the starting marker with custom icon const startMarker = L.marker(activity.latlngs[0], {}).addTo(map); startMarker.bindPopup("Start: " + formatDate(activity.start_date_local)); startMarker.setOpacity(0.8); // Create the ending marker with custom icon const endMarker = L.marker(activity.latlngs[activity.latlngs.length - 1], {}).addTo(map); const startDate = new Date(activity.start_date_local); endMarker.bindPopup("End: " + formatDate(new Date(startDate.getTime() + activity.elapsed_time * 1000))); endMarker.setOpacity(0.8); map.fitBounds(polyline.getBounds()); // Create arrows if (this.activityIndex > 0) { const arrowLeft = L.DomUtil.create('div', 'arrow-control right-arrow', map.getContainer()); arrowLeft.innerHTML = '▶'; // ▶ arrowLeft.title = getInfoTextShort(this.activities[this.activityIndex-1]); arrowLeft.onclick = () => onSwitchActivity(this.activityIndex-1, map); } if (this.activityIndex < this.activities.length - 1) { const arrowRight = L.DomUtil.create('div', 'arrow-control left-arrow', map.getContainer()); arrowRight.innerHTML = '◀'; // ◀ arrowRight.title = getInfoTextShort(this.activities[this.activityIndex+1]); arrowRight.onclick = () => onSwitchActivity(this.activityIndex+1, map); } // info text const infoText = L.DomUtil.create('div', 'info-text', map.getContainer()); infoText.innerHTML = getInfoText(activity); // Add click handler to open a new tab /*infoText.onclick = () => { window.open('https://www.strava.com/activities/'+activity.id, '_blank'); };*/ // Combo-box const selectBox = L.DomUtil.create('select', 'activity-selector', map.getContainer()); this.activities.forEach((act, i) => { const option = document.createElement('option'); option.value = i; option.text = getInfoTextShort(act); if (act.id === activity.id) { option.selected = true; } selectBox.appendChild(option); }); selectBox.onchange = (e) => { const selectedIndex = parseInt(e.target.value, 10); onSwitchActivity(selectedIndex, map); }; // Change URL without reloading window.history.pushState({}, '', `/activities/${activity.id}`); /*const gpxUrl = 'track.gpx'; new L.GPX(gpxUrl, { async: true, marker_options: { startIconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-icon-start.png', endIconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-icon-end.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-shadow.png' } }).on('loaded', function(e) { map.fitBounds(e.target.getBounds()); }).addTo(map);*/ }; async function onSwitchActivity(selectedIndex, map) { if (map) { map.remove(); } // Remove info-text and activity-selector from the DOM const infoText = document.querySelector('.info-text'); if (infoText) { infoText.remove(); } const activitySelector = document.querySelector('.activity-selector'); if (activitySelector) { activitySelector.remove(); } const arrowLeft = document.querySelector('.left-arrow'); if (arrowLeft) { arrowLeft.remove(); } const arrowRight = document.querySelector('.right-arrow'); if (arrowRight) { arrowRight.remove(); } this.activityIndex = selectedIndex; const mapContainer = document.getElementById("map"); mapContainer.style.display = "none"; let activity = this.activities[selectedIndex]; activity.latlngs = await downloadGpx(activity.id); showMap(activity); } //------------------------------------------------------------------------ // Function to format distance in kilometers (max 2 decimal places) function formatDistance(distance) { return (distance / 1000).toFixed(2); // Convert meters to kilometers and round to 2 decimals } // Function to convert start date to local time without trailing seconds function formatDate(dateStr) { const date = new Date(dateStr); return date.toLocaleString().replace(/:\d{2}$/, ''); // Remove seconds } // ------------------------------------------------------------ function getActivityIcon(type) { if (type === 'Run') return "🏃"; if (type === 'Ride') return "🚴"; return ""; } function getInfoText(activity) { return ` ${activity.name}
${formatDate(activity.start_date_local)} – ${getActivityIcon(activity.type)} ${activity.type}, ${formatDistance(activity.distance)} km` } function getInfoTextShort(activity) { return `${formatDate(activity.start_date_local)}, ${getActivityIcon(activity.type)} ${activity.name}, ${formatDistance(activity.distance)} km` } const getActivityIdFromURL = () => { const path = window.location.pathname; const match = path.match(/^\/activities\/([^\/]+)$/); if (match) { const activityId = match[1]; console.log("Activity ID:", activityId); return activityId } else { return '' } } const init = async () => { try { const code = params.get("code"); // Check if accessToken is already stored in localStorage const storedToken = localStorage.getItem('accessToken'); if (storedToken) { console.log("Using stored Strava token:", storedToken); accessToken = storedToken; // Use the stored access token await fetchActivities(); } else if (code) { console.log("Strava code:", code); const res = await fetch("https://api.strava-mapy.com/exchange-token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code }), }); const data = await res.json(); console.log("Strava token:", data.access_token); accessToken = data.access_token; // Store the access token in localStorage localStorage.setItem('accessToken', accessToken); // reflect activityId in URL const activityId = params.get("state"); //activityId redirectToBase(activityId ? `/activities/${activityId}`:''); } else { document.getElementById("login").style.display = "block"; document.getElementById("strava-connect").href = `https://www.strava.com/oauth/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(window.location.origin + window.location.pathname)}&approval_prompt=auto&scope=read,activity:read,activity:read_all&state=${getActivityIdFromURL()}`; } } catch (error) { console.error("Error occurred during initialization:", error); } }; function redirectToBase(activitySuffix='') { window.location.href = window.location.origin + activitySuffix; } //---------------------------------------------------------- async function fetchFromStrava(url) { const response = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } }); if (response.status === 401) { goToLoginPage(); } if (!response.ok) { console.log('Error fetching activities'); return; } return await response.json() } //------------------------------------------------------------------------------ async function fetchActivities() { let data = await fetchFromStrava("https://www.strava.com/api/v3/athlete/activities?per_page=20&page=1"); this.activities = data.filter(activity => activity.map && activity.map.summary_polyline); if(this.activities.length===0) { alert("Sorry, no recent activity with map found."); return; } const id=getActivityIdFromURL(); let index=0; if(id) { index=this.activities.findIndex(activity => activity.id==id) } if(index==-1) { data=await fetchFromStrava("https://www.strava.com/api/v3/activities/"+id); if(!data || data.length==0) { console.error("No activity with ID "+id); return; } index=this.activities.push(data)-1; } onSwitchActivity(index, null) /* this.activities[0].latlngs = await downloadGpx(this.activities[0].id); showMap(this.activities[0])*/ } // ------------------------------------------------ function goToLoginPage() { console.error("Invalid access token. Redirecting to the login page.") localStorage.removeItem('accessToken'); redirectToBase(); } //----------------------------------------------------------------------------------------------- async function downloadGpx(activityId) { const url = `https://www.strava.com/api/v3/activities/${activityId}/streams?keys=latlng&key_by_type=true`; const response = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } }); if (response.status === 401) { goToLoginPage(); } if (!response.ok) { alert("Failed to download track data."); return; } const data = await response.json(); return data.latlng.data; } //------------------------------------------------------------------------------------- init() .then(() => { console.log("Init completed successfully"); }) .catch((error) => { console.error("Error in init:", error); });