feat: build Stride local app with Garmin imports and friends-only sharing

This commit is contained in:
Peter Stockings
2026-10-01 14:55:59 +10:00
parent 16e71a69d5
commit e37569ad09
45 changed files with 3735 additions and 40 deletions
+32
View File
@@ -0,0 +1,32 @@
"use strict";
async function showActivity() {
const container = document.getElementById("route-map");
const status = document.getElementById("map-status");
try {
const response = await fetch(container.dataset.url);
if (!response.ok) throw new Error("Activity unavailable");
const data = await response.json();
if (data.segments.length && window.L) {
container.replaceChildren();
const map = L.map(container, {scrollWheelZoom: false});
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {maxZoom: 19, attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'}).addTo(map);
const route = L.polyline(data.segments, {color: "#345e40", weight: 4, opacity: .95}).addTo(map);
map.fitBounds(route.getBounds(), {padding: [35, 35], maxZoom: 16});
status.textContent = "Scroll to explore your charts. Drag the map to explore the route.";
} else {
container.querySelector("p").textContent = "No route to display. This activity may have been recorded indoors.";
status.textContent = "Activity statistics are available below.";
}
if (!window.Chart) return;
const config = (id, key, color, formatter, reverse = false) => {
const points = data.chart.filter(s => s.distance !== null).map(s => ({x: s.distance / 1000, y: formatter(s[key])}));
new Chart(document.getElementById(id), {type: "line", data: {datasets: [{data: points, borderColor: color, backgroundColor: `${color}12`, fill: true, borderWidth: 1.7, pointRadius: 0, pointHitRadius: 12, tension: .2, spanGaps: false}]}, options: {responsive: true, maintainAspectRatio: false, parsing: false, plugins: {legend: {display: false}}, scales: {x: {type: "linear", grid: {display: false}, border: {display: false}, ticks: {font: {size: 10}, color: "#949c8b", callback: v => `${v} km`}}, y: {reverse, grid: {color: "#f0f2eb"}, border: {display: false}, ticks: {font: {size: 10}, color: "#949c8b"}}}}});
};
config("pace-chart", "speed", "#668b47", v => v > .5 ? Math.min(30, 1000 / v / 60) : null, true);
config("hr-chart", "heart_rate", "#bd8063", v => v > 0 ? v : null);
} catch {
container.querySelector("p").textContent = "We couldn’t load this activity. Please refresh and try again.";
status.textContent = "The activity may no longer be available.";
}
}
showActivity();