Pārlūkot izejas kodu

Add GPX export button to track list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
k4be 2 nedēļas atpakaļ
vecāks
revīzija
412c7f06f2
1 mainītis faili ar 50 papildinājumiem un 0 dzēšanām
  1. 50 0
      gpx-vis-frontend/js/browser.js

+ 50 - 0
gpx-vis-frontend/js/browser.js

@@ -200,6 +200,7 @@ const Browser = (() => {
           <button class="item-btn view-track-btn" data-id="${track.id}" title="View on map">👁️</button>
           <button class="item-btn edit-track-btn" data-id="${track.id}" data-name="${escAttr(track.name)}" data-type="${escAttr(track.trackType || '')}" title="Edit">✏️</button>
           <button class="item-btn share-track-btn" data-id="${track.id}" title="Share">🔗</button>
+          <button class="item-btn export-track-btn" data-id="${track.id}" data-name="${escAttr(track.name)}" title="Export GPX">⬇️</button>
           <button class="item-btn move-track-btn" data-id="${track.id}" data-name="${escAttr(track.name)}" title="Move">📂</button>
           <button class="item-btn delete-track-btn" data-id="${track.id}" data-name="${escAttr(track.name)}" title="Delete">🗑️</button>
         </span>
@@ -296,6 +297,13 @@ const Browser = (() => {
       });
     });
 
+    container.querySelectorAll('.export-track-btn').forEach(btn => {
+      btn.addEventListener('click', (e) => {
+        e.stopPropagation();
+        exportTrack(parseInt(btn.dataset.id), btn.dataset.name);
+      });
+    });
+
     container.querySelectorAll('.move-track-btn').forEach(btn => {
       btn.addEventListener('click', (e) => {
         e.stopPropagation();
@@ -616,6 +624,48 @@ const Browser = (() => {
     }
   }
 
+  async function exportTrack(trackId, trackName) {
+    try {
+      let data = trackDataCache[trackId];
+      if (!data) {
+        data = await API.getTrackPoints(trackId);
+        trackDataCache[trackId] = data;
+      }
+      const gpx = buildGpx(data);
+      const blob = new Blob([gpx], { type: 'application/gpx+xml' });
+      const url = URL.createObjectURL(blob);
+      const a = document.createElement('a');
+      a.href = url;
+      a.download = (trackName || 'track').replace(/[/\\:*?"<>|]/g, '_') + '.gpx';
+      document.body.appendChild(a);
+      a.click();
+      document.body.removeChild(a);
+      URL.revokeObjectURL(url);
+    } catch (e) {
+      showToast('Export failed: ' + e.message, 'error');
+    }
+  }
+
+  function buildGpx(data) {
+    const name = data.meta?.name || 'Track';
+    const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
+    let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
+    xml += '<gpx version="1.1" creator="gpx-vis" xmlns="http://www.topografix.com/GPX/1/1">\n';
+    xml += `  <trk>\n    <name>${esc(name)}</name>\n`;
+    for (const seg of (data.segments || [])) {
+      xml += '    <trkseg>\n';
+      for (const [lat, lon, ele, time] of seg) {
+        xml += `      <trkpt lat="${lat}" lon="${lon}">`;
+        if (ele != null) xml += `<ele>${ele}</ele>`;
+        if (time) xml += `<time>${time}</time>`;
+        xml += '</trkpt>\n';
+      }
+      xml += '    </trkseg>\n';
+    }
+    xml += '  </trk>\n</gpx>\n';
+    return xml;
+  }
+
   // ===== Dir Actions =====
 
   async function createDirPrompt() {