Browse Source

Cache track point data in memory and show loading state on click

- Track segment data is cached after first fetch; re-toggling a track
  on/off no longer triggers a network request
- While fetching, the track item pulses and ignores further clicks
  (pointer-events:none) so repeated clicking has no effect
- Cache is invalidated on edit (name/type change) and delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
k4be 7 hours ago
parent
commit
9ffe4d72d3
2 changed files with 38 additions and 1 deletions
  1. 12 0
      gpx-vis-frontend/css/style.css
  2. 26 1
      gpx-vis-frontend/js/browser.js

+ 12 - 0
gpx-vis-frontend/css/style.css

@@ -522,6 +522,18 @@ form button[type="submit"]:hover {
   cursor: default;
 }
 
+/* ===== Track loading state ===== */
+@keyframes track-loading-pulse {
+  0%, 100% { opacity: 0.45; }
+  50%       { opacity: 0.9; }
+}
+
+.track-item.loading-track {
+  cursor: wait;
+  animation: track-loading-pulse 1s ease-in-out infinite;
+  pointer-events: none;
+}
+
 /* ===== Local (guest) active track ===== */
 .track-item.local-active {
   background: #e8f4fd;

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

@@ -24,6 +24,10 @@ const Browser = (() => {
   let autoScrollRaf = null;
   let autoScrollDir = 0;  // -1 up, +1 down, 0 stopped
 
+  // ===== Track data cache =====
+  let trackDataCache = {};   // trackId → API response (segments + meta)
+  let loadingTrackIds = new Set();
+
   async function init() {
     document.getElementById('new-dir-btn').addEventListener('click', createDirPrompt);
     document.getElementById('upload-btn').addEventListener('click', () => {
@@ -517,7 +521,13 @@ const Browser = (() => {
 
   // ===== Track Actions =====
 
+  function setTrackItemLoading(trackId, on) {
+    const el = document.querySelector(`.track-item[data-id="${trackId}"]`);
+    if (el) el.classList.toggle('loading-track', on);
+  }
+
   async function openTrack(trackId) {
+    if (loadingTrackIds.has(trackId)) return;
     try {
       if (MapView.hasTrack(trackId)) {
         MapView.removeTrack(trackId);
@@ -526,7 +536,20 @@ const Browser = (() => {
         if (typeof Elevation !== 'undefined') Elevation.clear();
         return;
       }
-      const data = await API.getTrackPoints(trackId);
+
+      let data = trackDataCache[trackId];
+      if (!data) {
+        loadingTrackIds.add(trackId);
+        setTrackItemLoading(trackId, true);
+        try {
+          data = await API.getTrackPoints(trackId);
+          trackDataCache[trackId] = data;
+        } finally {
+          loadingTrackIds.delete(trackId);
+          setTrackItemLoading(trackId, false);
+        }
+      }
+
       MapView.addTrack(data, trackId);
       MapView.fitTrack(trackId);
       MapView.setCurrentTrack(trackId);
@@ -573,6 +596,7 @@ const Browser = (() => {
       modal.style.display = 'none';
       try {
         await API.updateTrack(trackId, { name, trackType });
+        delete trackDataCache[trackId];
         await reload();
         showToast('Track updated', 'success');
       } catch (e) {
@@ -584,6 +608,7 @@ const Browser = (() => {
   async function deleteTrack(id) {
     try {
       MapView.removeTrack(id);
+      delete trackDataCache[id];
       await API.deleteTrack(id);
       await reload();
     } catch (e) {