Avoid map zoom-out when navigating between routes

Route links (route_master list, other directions) now intercept clicks
and call loadRoute() + history.pushState() instead of doing a full page
reload, so the map stays at its current position and zooms smoothly into
the new route. Ctrl/cmd/middle-click still opens in a new tab via href.

Introduce currentRelationId (mutable) to track the loaded relation so
loadSegment() uses the correct ID after in-page navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-02-27 19:18:05 +00:00
parent 3223d4b063
commit f6dd68ed75

View file

@ -22,6 +22,7 @@ let routeMasterLayers = []; // coloured polylines when viewing a route_master
// ── State ───────────────────────────────────────────────────────────────── // ── State ─────────────────────────────────────────────────────────────────
let currentRelationId = RELATION_ID; // tracks the currently loaded relation
let routeData = null; // response from /api/route/ let routeData = null; // response from /api/route/
let segmentGeoJson = null; // last response from /api/segment/ (always includes stops) let segmentGeoJson = null; // last response from /api/segment/ (always includes stops)
let activeSlot = 'from'; // 'from' | 'to' | null let activeSlot = 'from'; // 'from' | 'to' | null
@ -301,6 +302,21 @@ document.getElementById('include-stops').addEventListener('change', () => {
document.getElementById('slot-from').addEventListener('click', () => setActiveSlot('from')); document.getElementById('slot-from').addEventListener('click', () => setActiveSlot('from'));
document.getElementById('slot-to').addEventListener('click', () => setActiveSlot('to')); document.getElementById('slot-to').addEventListener('click', () => setActiveSlot('to'));
// ── Navigation ─────────────────────────────────────────────────────────────
/**
* Load a relation without a full page reload, updating the browser URL.
* Allows middle-click / ctrl-click to still open in a new tab via the href.
* @param {number} id
* @param {MouseEvent} e
*/
function navigateTo(id, e) {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
history.pushState(null, '', `/${id}`);
loadRoute(id);
}
// ── API calls ────────────────────────────────────────────────────────────── // ── API calls ──────────────────────────────────────────────────────────────
/** /**
@ -320,6 +336,7 @@ async function loadRoute(relationId) {
return; return;
} }
routeData = data; routeData = data;
currentRelationId = relationId;
// Clean up any previous route_master view // Clean up any previous route_master view
for (const l of routeMasterLayers) l.remove(); for (const l of routeMasterLayers) l.remove();
routeMasterLayers = []; routeMasterLayers = [];
@ -358,6 +375,7 @@ async function loadRoute(relationId) {
a.href = `/${dir.id}`; a.href = `/${dir.id}`;
a.className = 'stop-item d-block text-decoration-none'; a.className = 'stop-item d-block text-decoration-none';
a.textContent = dir.name; a.textContent = dir.name;
a.addEventListener('click', (e) => navigateTo(dir.id, e));
dirList.appendChild(a); dirList.appendChild(a);
} }
} else { } else {
@ -373,7 +391,7 @@ async function loadRoute(relationId) {
*/ */
async function loadSegment() { async function loadSegment() {
if (!selectedFrom || !selectedTo || !routeData) return; if (!selectedFrom || !selectedTo || !routeData) return;
const rid = RELATION_ID; const rid = currentRelationId;
const params = new URLSearchParams({ from: selectedFrom, to: selectedTo, stops: '1' }); const params = new URLSearchParams({ from: selectedFrom, to: selectedTo, stops: '1' });
try { try {
const resp = await fetch(`/api/segment/${rid}?${params}`); const resp = await fetch(`/api/segment/${rid}?${params}`);
@ -444,6 +462,7 @@ async function loadRouteMaster(relationId) {
a.href = `/${route.id}`; a.href = `/${route.id}`;
a.className = 'text-decoration-none text-reset flex-grow-1'; a.className = 'text-decoration-none text-reset flex-grow-1';
a.textContent = route.name; a.textContent = route.name;
a.addEventListener('click', (e) => navigateTo(route.id, e));
div.appendChild(dot); div.appendChild(dot);
div.appendChild(a); div.appendChild(a);