RoyAalekh commited on
Commit
4c36de1
·
1 Parent(s): 8eb6d71

Implement preemptive data loading and remove image loading from tooltips

Browse files

🚀 Performance Optimizations:
- Added BackgroundDataService for preemptive tree data loading
- Background loading starts immediately after user authentication
- Map uses cached data when available (instant loading)
- Removed image loading from tree tooltips for better performance
- Optimized batch processing (100 trees/batch, 10ms delays)
- Auto-clear cache on logout for fresh sessions

📱 Map Improvements:
- ES6 module imports for better code organization
- Fixed module loading and exports
- Reduced popup size (removed images, cleaner UI)
- Faster map initialization with cached data
- Progress indicators during background loading

🔧 Technical Changes:
- Background loading happens parallel to user interaction
- Smart caching prevents redundant API calls
- Graceful fallback if background loading fails
- Console logging with emojis for better debugging

static/index.html CHANGED
@@ -947,7 +947,7 @@
947
  // Force refresh if we detect cached version
948
  (function() {
949
  const currentVersion = '5.1.1';
950
- const timestamp = '1761507058'; // Cache-busting bump
951
  const lastVersion = sessionStorage.getItem('treetrack_version');
952
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
953
 
@@ -1193,7 +1193,7 @@
1193
  </div>
1194
  </div>
1195
 
1196
- <script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1761507058"></script>
1197
 
1198
  <script>
1199
  // Idle-time prefetch of map assets to speed up first navigation
 
947
  // Force refresh if we detect cached version
948
  (function() {
949
  const currentVersion = '5.1.1';
950
+ const timestamp = '1761507500'; // Cache-busting bump
951
  const lastVersion = sessionStorage.getItem('treetrack_version');
952
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
953
 
 
1193
  </div>
1194
  </div>
1195
 
1196
+ <script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1761507500"></script>
1197
 
1198
  <script>
1199
  // Idle-time prefetch of map assets to speed up first navigation
static/js/modules/auth-manager.js CHANGED
@@ -2,6 +2,8 @@
2
  * Authentication Manager Module
3
  * Handles user authentication, session management, and permissions
4
  */
 
 
5
  export class AuthManager {
6
  constructor() {
7
  this.currentUser = null;
@@ -25,6 +27,17 @@ export class AuthManager {
25
  const data = await response.json();
26
  this.currentUser = data.user;
27
  this.authToken = token;
 
 
 
 
 
 
 
 
 
 
 
28
  return true;
29
  } else {
30
  this.clearAuthData();
@@ -57,6 +70,13 @@ export class AuthManager {
57
  localStorage.removeItem('user_info');
58
  this.currentUser = null;
59
  this.authToken = null;
 
 
 
 
 
 
 
60
  }
61
 
62
  canEditTree(createdBy) {
 
2
  * Authentication Manager Module
3
  * Handles user authentication, session management, and permissions
4
  */
5
+ import { backgroundDataService } from './background-data.js';
6
+
7
  export class AuthManager {
8
  constructor() {
9
  this.currentUser = null;
 
27
  const data = await response.json();
28
  this.currentUser = data.user;
29
  this.authToken = token;
30
+
31
+ // Start background data loading immediately after successful auth
32
+ try {
33
+ backgroundDataService.setAuthToken(token);
34
+ backgroundDataService.startPreemptiveLoading().catch(error => {
35
+ console.warn('Background data preloading failed:', error);
36
+ });
37
+ } catch (error) {
38
+ console.warn('Failed to start background data loading:', error);
39
+ }
40
+
41
  return true;
42
  } else {
43
  this.clearAuthData();
 
70
  localStorage.removeItem('user_info');
71
  this.currentUser = null;
72
  this.authToken = null;
73
+
74
+ // Clear background data cache when user logs out
75
+ try {
76
+ backgroundDataService.clearCache();
77
+ } catch (error) {
78
+ console.warn('Failed to clear background data cache:', error);
79
+ }
80
  }
81
 
82
  canEditTree(createdBy) {
static/js/modules/background-data.js ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Background Data Service
3
+ * Handles preemptive loading of tree data for better performance
4
+ */
5
+ export class BackgroundDataService {
6
+ constructor() {
7
+ this.treeDataCache = null;
8
+ this.isLoading = false;
9
+ this.loadPromise = null;
10
+ this.authToken = null;
11
+ this.batchSize = 1000;
12
+ }
13
+
14
+ setAuthToken(token) {
15
+ this.authToken = token;
16
+ }
17
+
18
+ async authenticatedFetch(url, options = {}) {
19
+ if (!this.authToken) {
20
+ throw new Error('No auth token available');
21
+ }
22
+
23
+ const headers = {
24
+ 'Content-Type': 'application/json',
25
+ 'Authorization': `Bearer ${this.authToken}`,
26
+ ...options.headers
27
+ };
28
+
29
+ const response = await fetch(url, {
30
+ ...options,
31
+ headers
32
+ });
33
+
34
+ if (response.status === 401) {
35
+ // Token expired or invalid
36
+ localStorage.removeItem('auth_token');
37
+ localStorage.removeItem('user_info');
38
+ window.location.href = '/login';
39
+ return null;
40
+ }
41
+
42
+ return response;
43
+ }
44
+
45
+ /**
46
+ * Start preemptive loading of all tree data
47
+ */
48
+ async startPreemptiveLoading() {
49
+ if (this.isLoading || this.treeDataCache) {
50
+ return this.loadPromise;
51
+ }
52
+
53
+ console.log('🚀 Starting preemptive tree data loading...');
54
+ this.isLoading = true;
55
+
56
+ this.loadPromise = this.loadAllTreesInBackground();
57
+ return this.loadPromise;
58
+ }
59
+
60
+ /**
61
+ * Load all trees in background with batching
62
+ */
63
+ async loadAllTreesInBackground() {
64
+ try {
65
+ let allTrees = [];
66
+ let offset = 0;
67
+ let hasMoreTrees = true;
68
+
69
+ console.log('📦 Loading trees in background batches...');
70
+
71
+ while (hasMoreTrees && allTrees.length < 3000) { // Safety limit
72
+ console.log(`📥 Background batch: offset=${offset}, limit=${this.batchSize}`);
73
+
74
+ const response = await this.authenticatedFetch(`/api/trees?limit=${this.batchSize}&offset=${offset}`);
75
+ if (!response) {
76
+ console.error('❌ Failed to fetch background batch');
77
+ break;
78
+ }
79
+
80
+ const batchTrees = await response.json();
81
+ console.log(`✅ Background batch loaded: ${batchTrees.length} trees`);
82
+
83
+ if (batchTrees.length === 0) {
84
+ hasMoreTrees = false;
85
+ break;
86
+ }
87
+
88
+ allTrees = allTrees.concat(batchTrees);
89
+ offset += this.batchSize;
90
+
91
+ // If we got less than the batch size, we've reached the end
92
+ if (batchTrees.length < this.batchSize) {
93
+ hasMoreTrees = false;
94
+ }
95
+
96
+ // Small delay to prevent overwhelming the server
97
+ await this.delay(100);
98
+ }
99
+
100
+ // Filter out problematic trees
101
+ const filteredTrees = allTrees.filter(tree => {
102
+ // Exclude tree ID 18
103
+ if (tree.id === 18) {
104
+ console.log('🔄 Background: Excluding tree ID 18 from cache');
105
+ return false;
106
+ }
107
+ return true;
108
+ });
109
+
110
+ this.treeDataCache = filteredTrees;
111
+ this.isLoading = false;
112
+
113
+ console.log(`🎉 Background loading complete: ${filteredTrees.length} trees cached`);
114
+
115
+ // Dispatch custom event to notify that data is ready
116
+ window.dispatchEvent(new CustomEvent('treeDataReady', {
117
+ detail: { count: filteredTrees.length }
118
+ }));
119
+
120
+ return filteredTrees;
121
+
122
+ } catch (error) {
123
+ console.error('❌ Background tree loading failed:', error);
124
+ this.isLoading = false;
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get cached tree data (returns immediately if available)
131
+ */
132
+ getCachedTreeData() {
133
+ return this.treeDataCache;
134
+ }
135
+
136
+ /**
137
+ * Check if data is ready
138
+ */
139
+ isDataReady() {
140
+ return this.treeDataCache !== null;
141
+ }
142
+
143
+ /**
144
+ * Get tree data - uses cache if available, otherwise loads
145
+ */
146
+ async getTreeData() {
147
+ if (this.treeDataCache) {
148
+ console.log('⚡ Using cached tree data');
149
+ return this.treeDataCache;
150
+ }
151
+
152
+ if (this.isLoading) {
153
+ console.log('⏳ Waiting for background loading to complete...');
154
+ return await this.loadPromise;
155
+ }
156
+
157
+ console.log('🔄 Cache miss, loading trees now...');
158
+ return await this.startPreemptiveLoading();
159
+ }
160
+
161
+ /**
162
+ * Clear cached data (useful for refresh scenarios)
163
+ */
164
+ clearCache() {
165
+ this.treeDataCache = null;
166
+ this.isLoading = false;
167
+ this.loadPromise = null;
168
+ console.log('🗑️ Tree data cache cleared');
169
+ }
170
+
171
+ /**
172
+ * Simple delay utility
173
+ */
174
+ delay(ms) {
175
+ return new Promise(resolve => setTimeout(resolve, ms));
176
+ }
177
+
178
+ /**
179
+ * Get loading status for UI feedback
180
+ */
181
+ getLoadingStatus() {
182
+ return {
183
+ isLoading: this.isLoading,
184
+ isReady: this.isDataReady(),
185
+ cacheSize: this.treeDataCache ? this.treeDataCache.length : 0
186
+ };
187
+ }
188
+ }
189
+
190
+ // Export singleton instance
191
+ export const backgroundDataService = new BackgroundDataService();
static/map.html CHANGED
@@ -928,7 +928,7 @@
928
  // Force refresh if we detect cached version
929
  (function() {
930
  const currentVersion = '5.1.1';
931
- const timestamp = '1761507058'; // Current timestamp for cache busting
932
  const lastVersion = sessionStorage.getItem('treetrack_version');
933
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
934
 
@@ -1081,27 +1081,13 @@ const timestamp = '1761507058'; // Current timestamp for cache busting
1081
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
1082
  <!-- Leaflet MarkerCluster JS for performance and grouping -->
1083
  <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
1084
- <script src="/static/map.js?v=5.1.1&t=1761507058">
1085
-
1086
- "default-state": {
1087
- gradients: [
1088
- ['#0f172a', '#1e293b', '#16a085'],
1089
- ['#1a202c', '#2d3748', '#27ae60'],
1090
- ['#2d3748', '#4a5568', '#2ecc71'],
1091
- ['#1a365d', '#2c5282', '#138d75'],
1092
- ['#0f172a', '#2d3748', '#16a085']
1093
- ],
1094
- transitionSpeed: 12000
1095
- }
1096
- }
1097
- });
1098
-
1099
- // Defer any heavy data fetch; ensure map shell is visible first
1100
- if (window.requestAnimationFrame) {
1101
- requestAnimationFrame(() => setTimeout(() => {
1102
- try { if (window.MapApp && window.MapApp.loadVisibleTrees) { window.MapApp.loadVisibleTrees(); } } catch(_) {}
1103
- }, 0));
1104
- }
1105
  });
1106
  </script>
1107
  </body>
 
928
  // Force refresh if we detect cached version
929
  (function() {
930
  const currentVersion = '5.1.1';
931
+ const timestamp = '1761507500'; // Current timestamp for cache busting
932
  const lastVersion = sessionStorage.getItem('treetrack_version');
933
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
934
 
 
1081
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
1082
  <!-- Leaflet MarkerCluster JS for performance and grouping -->
1083
  <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
1084
+ <script type="module">
1085
+ // Import and initialize the map with background data service
1086
+ import('./map.js?v=5.1.1&t=1761507058').then(module => {
1087
+ // Map initialization is handled in the module
1088
+ console.log('🗺️ Map module loaded with background data service');
1089
+ }).catch(error => {
1090
+ console.error(' Failed to load map module:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1091
  });
1092
  </script>
1093
  </body>
static/map.js CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  // TreeTrack Enhanced Map with Authentication and Tree Management
2
  class TreeTrackMap {
3
  constructor() {
@@ -61,6 +64,13 @@ class TreeTrackMap {
61
  const data = await response.json();
62
  this.currentUser = data.user;
63
  this.authToken = token;
 
 
 
 
 
 
 
64
  return true;
65
  } else {
66
  // Token invalid, remove it
@@ -590,108 +600,80 @@ class TreeTrackMap {
590
  }
591
 
592
  async loadTrees() {
593
- console.log('Loading trees...');
594
 
595
  try {
596
- // Load trees in batches to work around API limits
597
- let allTrees = [];
598
- let offset = 0;
599
- const batchSize = 1000; // API batch size (Supabase limit)
600
- let hasMoreTrees = true;
601
-
602
  // Clear existing tree markers
603
  this.clearTreeMarkers();
604
 
605
- console.log('Loading trees in batches to handle large dataset...');
 
 
606
 
607
- while (hasMoreTrees && allTrees.length < 3000) { // Safety limit
608
- console.log(`Loading batch: offset=${offset}, limit=${batchSize}`);
609
-
610
- const response = await this.authenticatedFetch(`/api/trees?limit=${batchSize}&offset=${offset}`);
611
- if (!response) {
612
- console.error('Failed to fetch batch');
613
- break;
614
- }
615
-
616
- const batchTrees = await response.json();
617
- console.log(`Loaded batch: ${batchTrees.length} trees`);
618
-
619
- if (batchTrees.length === 0) {
620
- hasMoreTrees = false;
621
- break;
622
- }
623
-
624
- allTrees = allTrees.concat(batchTrees);
625
- offset += batchSize;
626
-
627
- // If we got less than the batch size, we've reached the end
628
- if (batchTrees.length < batchSize) {
629
- hasMoreTrees = false;
630
- }
631
 
632
- // Update progress
633
- document.getElementById('treeCount').textContent = `${allTrees.length} trees loaded...`;
 
 
634
  }
635
 
636
- console.log(`Total trees loaded: ${allTrees.length}`);
637
-
638
- // Filter out specific trees (e.g., ID 18 as requested)
639
- const filteredTrees = allTrees.filter(tree => {
640
- // Exclude tree ID 18
641
- if (tree.id === 18) {
642
- console.log('Excluding tree ID 18 from display');
643
- return false;
644
- }
645
- return true;
646
- });
647
 
648
- console.log(`Filtered trees: ${filteredTrees.length} (excluded ${allTrees.length - filteredTrees.length})`);
649
-
650
- // Add tree markers with progress tracking and UI batch processing
651
  let loadedCount = 0;
652
- const uiBatchSize = 50; // UI processing batch size for smooth rendering
653
 
 
 
654
  for (let i = 0; i < filteredTrees.length; i += uiBatchSize) {
655
- const batch = filteredTrees.slice(i, i + uiBatchSize);
 
 
 
 
 
656
 
657
- // Process batch with small delay to prevent UI blocking
658
- setTimeout(() => {
659
- batch.forEach((tree) => {
660
- try {
661
- this.addTreeMarker(tree);
662
- loadedCount++;
663
- } catch (error) {
664
- console.warn(`Failed to add tree marker for tree ${tree.id}:`, error);
665
- }
666
- });
667
-
668
- // Update progress
669
- document.getElementById('treeCount').textContent = `${loadedCount} trees`;
670
-
671
- // If this is the last batch, finalize
672
- if (i + uiBatchSize >= filteredTrees.length) {
673
- console.log(`Map loading complete: ${loadedCount} tree markers added`);
674
- this.showMessage(`Loaded ${loadedCount} trees successfully`, 'success');
675
- }
676
- }, (i / uiBatchSize) * 50); // Stagger UI batches by 50ms
677
  }
678
 
679
- // Auto-center on trees with clustering-friendly zoom after loading completes
 
 
 
680
  setTimeout(() => {
681
  if (loadedCount > 0) {
682
- // Get bounds of all tree markers
683
  const group = new L.featureGroup(this.treeMarkers);
684
  const bounds = group.getBounds();
685
-
686
- // Center on trees with clustering-optimized zoom level
687
  this.map.fitBounds(bounds, { padding: [20, 20], maxZoom: 15 });
688
- console.log('Map auto-centered on trees with clustering view');
689
  }
690
- }, 2000); // Wait for most UI batches to complete
691
 
692
  } catch (error) {
693
- console.error('Error loading trees:', error);
694
- this.showMessage('Failed to load trees', 'error');
695
  }
696
  }
697
 
@@ -850,41 +832,12 @@ class TreeTrackMap {
850
  const canEdit = this.canEditTree(tree.created_by);
851
  const canDelete = this.canDeleteTree(tree.created_by);
852
 
853
- // Process tree images for display
854
- let imageHtml = '';
855
- if (tree.photographs && typeof tree.photographs === 'object') {
856
- const photos = tree.photographs;
857
- const photoTypes = ['full tree', 'leaf', 'bark', 'flower', 'fruit', 'seed'];
858
-
859
- // Find the best photo to display (prioritize full tree, then others)
860
- let displayPhoto = null;
861
- let displayType = null;
862
-
863
- for (const type of photoTypes) {
864
- if (photos[type]) {
865
- displayPhoto = photos[type];
866
- displayType = type;
867
- break;
868
- }
869
- }
870
-
871
- if (displayPhoto) {
872
- imageHtml = `
873
- <div style="margin-bottom: 16px; text-align: center;">
874
- <img src="${displayPhoto}"
875
- alt="${treeName} - ${displayType}"
876
- style="width: 100%; max-width: 200px; height: 120px; object-fit: cover; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"
877
- onerror="this.style.display='none'"/>
878
- <div style="font-size: 11px; color: #6b7280; margin-top: 4px; text-transform: capitalize;">${displayType}</div>
879
- </div>
880
- `;
881
- }
882
- }
883
 
884
  const popupContent = `
885
- <div style="width: 320px; max-width: 90vw; font-family: 'Segoe UI', sans-serif; position: relative;">
886
- <div style="padding: 20px; padding-bottom: 16px;">
887
- ${imageHtml}
888
  <div style="display: flex; justify-content: between; align-items: flex-start; margin-bottom: 16px;">
889
  <div style="flex: 1;">
890
  <h3 style="margin: 0 0 8px 0; color: #059669; font-size: 18px; font-weight: 600; line-height: 1.2; word-wrap: break-word;">
@@ -1059,6 +1012,12 @@ ${tree.height ? `<strong style="color: #374151;">Height:</strong><span>${tree.he
1059
  // Initialize map when DOM is loaded
1060
  let mapApp;
1061
  document.addEventListener('DOMContentLoaded', () => {
1062
- console.log('DOM loaded, initializing TreeTrack Map...');
1063
  mapApp = new TreeTrackMap();
 
 
 
1064
  });
 
 
 
 
1
+ // Import background data service
2
+ import { backgroundDataService } from './js/modules/background-data.js';
3
+
4
  // TreeTrack Enhanced Map with Authentication and Tree Management
5
  class TreeTrackMap {
6
  constructor() {
 
64
  const data = await response.json();
65
  this.currentUser = data.user;
66
  this.authToken = token;
67
+
68
+ // Start preemptive data loading in background
69
+ backgroundDataService.setAuthToken(token);
70
+ backgroundDataService.startPreemptiveLoading().catch(error => {
71
+ console.warn('Background data loading failed:', error);
72
+ });
73
+
74
  return true;
75
  } else {
76
  // Token invalid, remove it
 
600
  }
601
 
602
  async loadTrees() {
603
+ console.log('🌳 Loading trees for map visualization...');
604
 
605
  try {
 
 
 
 
 
 
606
  // Clear existing tree markers
607
  this.clearTreeMarkers();
608
 
609
+ // Try to use cached data first
610
+ let filteredTrees;
611
+ const cachedTrees = backgroundDataService.getCachedTreeData();
612
 
613
+ if (cachedTrees) {
614
+ console.log('⚡ Using preloaded tree data from cache');
615
+ filteredTrees = cachedTrees;
616
+ } else {
617
+ // Show loading message
618
+ document.getElementById('treeCount').textContent = 'Loading trees...';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
 
620
+ // Wait for background loading or load now
621
+ console.log(' Getting tree data (background loading)...');
622
+ const allTrees = await backgroundDataService.getTreeData();
623
+ filteredTrees = allTrees;
624
  }
625
 
626
+ console.log(`📊 Processing ${filteredTrees.length} trees for map display`);
 
 
 
 
 
 
 
 
 
 
627
 
628
+ // Add tree markers with optimized batch processing
 
 
629
  let loadedCount = 0;
630
+ const uiBatchSize = 100; // Increased batch size for faster processing
631
 
632
+ // Process all batches immediately for faster loading
633
+ const batches = [];
634
  for (let i = 0; i < filteredTrees.length; i += uiBatchSize) {
635
+ batches.push(filteredTrees.slice(i, i + uiBatchSize));
636
+ }
637
+
638
+ // Process batches with minimal delays
639
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
640
+ const batch = batches[batchIndex];
641
 
642
+ // Use requestAnimationFrame for smoother UI updates
643
+ await new Promise(resolve => {
644
+ setTimeout(() => {
645
+ batch.forEach((tree) => {
646
+ try {
647
+ this.addTreeMarker(tree);
648
+ loadedCount++;
649
+ } catch (error) {
650
+ console.warn(`Failed to add marker for tree ${tree.id}:`, error);
651
+ }
652
+ });
653
+
654
+ // Update progress
655
+ document.getElementById('treeCount').textContent = `${loadedCount} trees`;
656
+ resolve();
657
+ }, batchIndex * 10); // Minimal 10ms delays
658
+ });
 
 
 
659
  }
660
 
661
+ console.log(`🎉 Map loading complete: ${loadedCount} tree markers added`);
662
+ this.showMessage(`Loaded ${loadedCount} trees successfully`, 'success');
663
+
664
+ // Auto-center on trees with clustering-friendly zoom
665
  setTimeout(() => {
666
  if (loadedCount > 0) {
 
667
  const group = new L.featureGroup(this.treeMarkers);
668
  const bounds = group.getBounds();
 
 
669
  this.map.fitBounds(bounds, { padding: [20, 20], maxZoom: 15 });
670
+ console.log('🎯 Map auto-centered with clustering view');
671
  }
672
+ }, 100); // Reduced delay since loading is faster
673
 
674
  } catch (error) {
675
+ console.error('Error loading trees:', error);
676
+ this.showMessage('Failed to load trees. Please refresh the page.', 'error');
677
  }
678
  }
679
 
 
832
  const canEdit = this.canEditTree(tree.created_by);
833
  const canDelete = this.canDeleteTree(tree.created_by);
834
 
835
+ // Note: Image loading removed for better performance
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
 
837
  const popupContent = `
838
+ <div style="width: 300px; max-width: 90vw; font-family: 'Segoe UI', sans-serif; position: relative;">
839
+ <div style="padding: 16px;">
840
+ <!-- Image loading removed for better performance -->
841
  <div style="display: flex; justify-content: between; align-items: flex-start; margin-bottom: 16px;">
842
  <div style="flex: 1;">
843
  <h3 style="margin: 0 0 8px 0; color: #059669; font-size: 18px; font-weight: 600; line-height: 1.2; word-wrap: break-word;">
 
1012
  // Initialize map when DOM is loaded
1013
  let mapApp;
1014
  document.addEventListener('DOMContentLoaded', () => {
1015
+ console.log('🗺️ DOM loaded, initializing TreeTrack Map with background data service...');
1016
  mapApp = new TreeTrackMap();
1017
+
1018
+ // Expose globally for popup button callbacks
1019
+ window.mapApp = mapApp;
1020
  });
1021
+
1022
+ // Export for ES6 module usage
1023
+ export { TreeTrackMap };
static/sw.js CHANGED
@@ -1,5 +1,5 @@
1
  // TreeTrack Service Worker - PWA and Offline Support
2
- const VERSION = 1761507058; // Cache busting bump - force clients to fetch new static assets and header image change
3
  const CACHE_NAME = `treetrack-v${VERSION}`;
4
  const STATIC_CACHE = `static-v${VERSION}`;
5
  const API_CACHE = `api-v${VERSION}`;
 
1
  // TreeTrack Service Worker - PWA and Offline Support
2
+ const VERSION = 1761507500; // Cache busting bump - force clients to fetch new static assets and header image change
3
  const CACHE_NAME = `treetrack-v${VERSION}`;
4
  const STATIC_CACHE = `static-v${VERSION}`;
5
  const API_CACHE = `api-v${VERSION}`;
version.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
  "version": "5.1.1",
3
- "timestamp": 1761507058
4
  }
 
1
  {
2
  "version": "5.1.1",
3
+ "timestamp": 1761507500
4
  }