fix(phase26): debounce trip clicks, guard against stale renders
This commit is contained in:
+43
-47
@@ -28,6 +28,7 @@
|
|||||||
let replayPoints = [];
|
let replayPoints = [];
|
||||||
let replayTimer = null;
|
let replayTimer = null;
|
||||||
let replayController = null;
|
let replayController = null;
|
||||||
|
let _replayDebounce = null;
|
||||||
const recenterBtn = document.getElementById("btn-recenter-history");
|
const recenterBtn = document.getElementById("btn-recenter-history");
|
||||||
const replayLocation = document.getElementById("trip-replay-location");
|
const replayLocation = document.getElementById("trip-replay-location");
|
||||||
|
|
||||||
@@ -193,48 +194,48 @@
|
|||||||
replayController.abort();
|
replayController.abort();
|
||||||
}
|
}
|
||||||
replayController = new AbortController();
|
replayController = new AbortController();
|
||||||
const response = await fetch(
|
const signal = replayController.signal;
|
||||||
`/api/trip/${tripId}`,
|
try {
|
||||||
{ signal: replayController.signal }
|
const response = await fetch(`/api/trip/${tripId}`, { signal });
|
||||||
);
|
const result = await response.json();
|
||||||
const result = await response.json();
|
if (!response.ok) throw new Error(result.error || "Could not load trip route.");
|
||||||
if (!response.ok) {
|
if (signal.aborted) return;
|
||||||
throw new Error(result.error || "Could not load trip route.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeLayer) {
|
if (routeLayer) routeLayer.remove();
|
||||||
routeLayer.remove();
|
const coordinates = result.points.map(p => [p.latitude, p.longitude]);
|
||||||
}
|
routeLayer = L.featureGroup();
|
||||||
const coordinates = result.points.map(function (point) {
|
L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer);
|
||||||
return [point.latitude, point.longitude];
|
if (coordinates.length) {
|
||||||
});
|
L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer);
|
||||||
routeLayer = L.featureGroup();
|
L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer);
|
||||||
L.polyline(coordinates, { color: "#4e73df", weight: 5 }).addTo(routeLayer);
|
}
|
||||||
if (coordinates.length) {
|
routeLayer.addTo(map);
|
||||||
L.marker(coordinates[0]).bindPopup("Trip start").addTo(routeLayer);
|
if (signal.aborted) { routeLayer.remove(); return; }
|
||||||
L.marker(coordinates[coordinates.length - 1]).bindPopup("Trip end").addTo(routeLayer);
|
|
||||||
}
|
|
||||||
routeLayer.addTo(map);
|
|
||||||
if (coordinates.length > 1) {
|
|
||||||
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
|
|
||||||
} else if (coordinates.length === 1) {
|
|
||||||
map.setView(coordinates[0], 15);
|
|
||||||
}
|
|
||||||
recenterBtn.hidden = false;
|
|
||||||
recenterBtn.onclick = function () {
|
|
||||||
if (coordinates.length > 1) {
|
if (coordinates.length > 1) {
|
||||||
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
|
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
|
||||||
} else if (coordinates.length === 1) {
|
} else if (coordinates.length === 1) {
|
||||||
map.setView(coordinates[0], 15);
|
map.setView(coordinates[0], 15);
|
||||||
}
|
}
|
||||||
};
|
if (signal.aborted) { routeLayer.remove(); return; }
|
||||||
summary.textContent = `${formatDistance(result.trip.distance_km)} · `
|
recenterBtn.hidden = false;
|
||||||
+ `${formatDuration(result.trip.duration_seconds)} · `
|
recenterBtn.onclick = function () {
|
||||||
+ `avg ${formatSpeed(result.trip.average_speed_kmh)} · `
|
if (coordinates.length > 1) {
|
||||||
+ `max ${formatSpeed(result.trip.max_speed_kmh)} · `
|
map.fitBounds(routeLayer.getBounds(), { padding: [24, 24] });
|
||||||
+ `${result.points.length} route points`;
|
} else if (coordinates.length === 1) {
|
||||||
preloadGeocodes(result.points);
|
map.setView(coordinates[0], 15);
|
||||||
initializeTimeline(result.points);
|
}
|
||||||
|
};
|
||||||
|
summary.textContent = `${formatDistance(result.trip.distance_km)} · `
|
||||||
|
+ `${formatDuration(result.trip.duration_seconds)} · `
|
||||||
|
+ `avg ${formatSpeed(result.trip.average_speed_kmh)} · `
|
||||||
|
+ `max ${formatSpeed(result.trip.max_speed_kmh)} · `
|
||||||
|
+ `${result.points.length} route points`;
|
||||||
|
if (signal.aborted) { routeLayer.remove(); return; }
|
||||||
|
preloadGeocodes(result.points);
|
||||||
|
initializeTimeline(result.points);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== "AbortError") showError(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tripList = document.getElementById("trip-list");
|
const tripList = document.getElementById("trip-list");
|
||||||
@@ -351,18 +352,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay button click (desktop) or row click (mobile)
|
// Replay button click (desktop) or row click (mobile) — debounced
|
||||||
const button = event.target.closest("[data-trip-id]");
|
|
||||||
const row = event.target.closest("tr[data-trip-id]");
|
|
||||||
const tripId = button ? button.dataset.tripId : (row ? row.dataset.tripId : null);
|
const tripId = button ? button.dataset.tripId : (row ? row.dataset.tripId : null);
|
||||||
if (!tripId) {
|
if (!tripId) return;
|
||||||
return;
|
clearTimeout(_replayDebounce);
|
||||||
}
|
_replayDebounce = setTimeout(() => {
|
||||||
replayTrip(tripId).catch(function (replayError) {
|
replayTrip(tripId);
|
||||||
if (replayError.name !== "AbortError") {
|
}, 100);
|
||||||
showError(replayError.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
scrubber.addEventListener("input", function () {
|
scrubber.addEventListener("input", function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user