فهرست منبع

Show track name/date in hover tooltip; double-click track to fit view

Tooltip:
- Elevation.setTrack() now accepts optional meta {name, trackDate, ...}
  stored as trackMeta alongside the point data
- formatTooltip(p, meta) renders the track name (bold) and date
  (muted) above the distance/elevation/time/coords lines
- meta is passed through all call sites: browser.js, app.js (share page
  and hash-restore), local.js
- MapView stores trackData.meta in trackLayers[id].meta and passes it
  to showHoverMarker → formatTooltip for the Leaflet map tooltip
- elevation.js also passes trackMeta when calling
  MapView.showHoverMarker from the chart hover path

Double-click to fit:
- Each polyline layer gets a dblclick handler that calls fitTrack(id)
- L.DomEvent.stopPropagation prevents the map's own double-click zoom
  from firing at the same time

Also fixes: elevation chart was missing on tracks opened via URL hash
on page load (hash-restore now calls Elevation.setTrack).

No backend restart required (frontend-only change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
k4be 7 ساعت پیش
والد
کامیت
1a31748be1
5فایلهای تغییر یافته به همراه40 افزوده شده و 19 حذف شده
  1. 5 1
      gpx-vis-frontend/js/app.js
  2. 1 1
      gpx-vis-frontend/js/browser.js
  3. 19 9
      gpx-vis-frontend/js/elevation.js
  4. 1 1
      gpx-vis-frontend/js/local.js
  5. 14 7
      gpx-vis-frontend/js/map.js

+ 5 - 1
gpx-vis-frontend/js/app.js

@@ -215,7 +215,7 @@ async function initSharePage(code) {
     MapView.fitTrack('shared');
     MapView.setCurrentTrack('shared');
     const pts = MapView.getTrackPoints('shared');
-    if (pts) Elevation.setTrack(pts);
+    if (pts) Elevation.setTrack(pts, data.meta);
 
     panel.style.display = 'block';
     infoContent.innerHTML = `
@@ -394,6 +394,10 @@ async function main() {
           MapView.fitTrack(trackId);
         }
         MapView.setCurrentTrack(trackId);
+        if (typeof Elevation !== 'undefined') {
+          const pts = MapView.getTrackPoints(trackId);
+          if (pts) Elevation.setTrack(pts, data.meta);
+        }
         // Show track info panel
         if (data.meta) {
           const panel = document.getElementById('track-info-panel');

+ 1 - 1
gpx-vis-frontend/js/browser.js

@@ -484,7 +484,7 @@ const Browser = (() => {
       showTrackInfo(data.meta);
       if (typeof Elevation !== 'undefined') {
         const pts = MapView.getTrackPoints(trackId);
-        if (pts) Elevation.setTrack(pts);
+        if (pts) Elevation.setTrack(pts, data.meta);
       }
     } catch (e) {
       showToast('Error loading track: ' + e.message, 'error');

+ 19 - 9
gpx-vis-frontend/js/elevation.js

@@ -7,6 +7,7 @@ const Elevation = (() => {
   let points = null;    // full-res flat [{lat,lon,ele,time,dist}]
   let chartPts = null;  // downsampled
   let bounds = null;    // computed after draw: {cw,ch,minE,eRange,totDist}
+  let trackMeta = null; // {name, trackDate, ...} from the track's meta
 
   // ===== Public API =====
 
@@ -19,15 +20,16 @@ const Elevation = (() => {
     window.addEventListener('resize', () => { if (points) raf(draw); });
   }
 
-  function setTrack(pts) {
-    points   = pts;
-    chartPts = downsample(pts, CHART_MAX_PTS);
-    bounds   = null;
+  function setTrack(pts, meta) {
+    points    = pts;
+    trackMeta = meta || null;
+    chartPts  = downsample(pts, CHART_MAX_PTS);
+    bounds    = null;
     raf(draw);
   }
 
   function clear() {
-    points = chartPts = bounds = null;
+    points = chartPts = bounds = trackMeta = null;
     hideTooltip();
     if (canvas) {
       canvas.width = canvas.width; // reset context
@@ -49,11 +51,19 @@ const Elevation = (() => {
   }
 
   // Shared tooltip formatter used by MapView for the Leaflet map tooltip
-  function formatTooltip(p) {
+  function formatTooltip(p, meta) {
     const dist = p.dist >= 1000
       ? (p.dist / 1000).toFixed(2) + ' km'
       : Math.round(p.dist) + ' m';
-    let html = `<div><b>Dist:</b> ${dist}</div>`;
+    let html = '';
+    if (meta?.name) {
+      html += `<div style="font-weight:600;margin-bottom:2px">${escHtml(meta.name)}</div>`;
+    }
+    if (meta?.trackDate) {
+      const d = new Date(meta.trackDate);
+      if (!isNaN(d)) html += `<div style="color:rgba(255,255,255,0.75);margin-bottom:4px">${d.toLocaleDateString()}</div>`;
+    }
+    html += `<div><b>Dist:</b> ${dist}</div>`;
     if (p.ele != null) html += `<div><b>Ele:</b> ${Math.round(p.ele)} m</div>`;
     if (p.time) {
       const t = new Date(p.time);
@@ -242,7 +252,7 @@ const Elevation = (() => {
     positionTooltip(chartPt, x, e.clientY - rect.top);
 
     if (fullPt && typeof MapView !== 'undefined') {
-      MapView.showHoverMarker(fullPt.lat, fullPt.lon, fullPt);
+      MapView.showHoverMarker(fullPt.lat, fullPt.lon, fullPt, trackMeta);
     }
   }
 
@@ -264,7 +274,7 @@ const Elevation = (() => {
 
   function positionTooltip(point, cx, cy) {
     if (!tooltip || !canvas) return;
-    tooltip.innerHTML = formatTooltip(point);
+    tooltip.innerHTML = formatTooltip(point, trackMeta);
     tooltip.classList.add('visible');
     const cRect = canvas.getBoundingClientRect();
     tooltip.style.left = Math.min(cx + 10, cRect.width  - tooltip.offsetWidth  - 4) + 'px';

+ 1 - 1
gpx-vis-frontend/js/local.js

@@ -96,7 +96,7 @@ const LocalViewer = (() => {
       showTrackInfo(entry.data.meta);
       if (typeof Elevation !== 'undefined') {
         const pts = MapView.getTrackPoints(id);
-        if (pts) Elevation.setTrack(pts);
+        if (pts) Elevation.setTrack(pts, entry.data.meta);
       }
     }
     renderList();

+ 14 - 7
gpx-vis-frontend/js/map.js

@@ -132,11 +132,16 @@ const MapView = (() => {
     }
 
     layer.addTo(map);
+    layer.on('dblclick', (e) => {
+      L.DomEvent.stopPropagation(e);
+      fitTrack(trackId);
+    });
     trackLayers[trackId] = {
       layer,
       color,
       markers: addedMarkers,
-      points: flattenPoints(trackData)
+      points: flattenPoints(trackData),
+      meta: trackData.meta || null
     };
 
     return layer;
@@ -219,7 +224,7 @@ const MapView = (() => {
     }
 
     if (nearest && nearestD <= HOVER_TOLERANCE_PX) {
-      showHoverMarker(nearest.lat, nearest.lon, nearest);
+      showHoverMarker(nearest.lat, nearest.lon, nearest, trackLayers[nearestTid]?.meta);
       // Update elevation chart only when hovering the current (active) track
       if (String(nearestTid) === String(currentTrackId) && typeof Elevation !== 'undefined') {
         Elevation.onMapHover(nearest);
@@ -248,14 +253,14 @@ const MapView = (() => {
     }
   }
 
-  function showHoverMarker(lat, lon, point) {
+  function showHoverMarker(lat, lon, point, meta) {
     if (!hoverMarker) return;
     hoverMarker.setLatLng([lat, lon]);
     if (!map.hasLayer(hoverMarker)) hoverMarker.addTo(map);
     hoverMarker.unbindTooltip();
     const content = typeof Elevation !== 'undefined'
-      ? Elevation.formatTooltip(point)
-      : fallbackTooltip(point);
+      ? Elevation.formatTooltip(point, meta)
+      : fallbackTooltip(point, meta);
     hoverMarker.bindTooltip(content, {
       permanent: true,
       direction: 'top',
@@ -268,9 +273,11 @@ const MapView = (() => {
     if (hoverMarker && map && map.hasLayer(hoverMarker)) hoverMarker.remove();
   }
 
-  function fallbackTooltip(p) {
+  function fallbackTooltip(p, meta) {
     const dist = p.dist >= 1000 ? (p.dist / 1000).toFixed(2) + ' km' : Math.round(p.dist) + ' m';
-    let s = `Dist: ${dist}`;
+    let s = '';
+    if (meta?.name) s += `<b>${escHtml(meta.name)}</b><br>`;
+    s += `Dist: ${dist}`;
     if (p.ele != null) s += `<br>Ele: ${Math.round(p.ele)} m`;
     return s;
   }