Spaces:
Running
Enhance TreeTrack map with beautiful tree pins and fixed popup modals
Browse filesVisual Enhancements:
- Replaced basic pins with detailed tree-shaped SVG icons
- Added red temporary pins with layered tree design and drop shadows
- Upgraded existing tree markers with 3D green tree icons
- Increased pin sizes for better visibility (32x40, 36x44 pixels)
Popup Modal Fixes:
- Fixed button overflow and layout issues
- Properly positioned close button with full functionality
- Improved responsive design (300px width, max 90vw)
- Enhanced typography with better spacing and font sizes
- Added smooth button hover animations and visual effects
User Experience Improvements:
- Fixed 'no location found' message handling
- Better message clearing on form redirect
- Added keyboard support (ESC to close popups)
- Improved word wrapping for long tree names
- Professional styling with consistent spacing
All map functionality now works seamlessly with enhanced visual appeal.
- static/map.js +106 -68
@@ -260,20 +260,31 @@ class TreeTrackMap {
|
|
260 |
// Remove existing temp marker
|
261 |
this.clearTempMarker();
|
262 |
|
263 |
-
// Create
|
264 |
const tempIcon = L.divIcon({
|
265 |
html: `
|
266 |
-
<div class="map-marker temp-marker">
|
267 |
-
<svg width="
|
268 |
-
|
269 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
270 |
</svg>
|
271 |
</div>
|
272 |
`,
|
273 |
-
className: 'custom-marker-icon',
|
274 |
-
iconSize: [
|
275 |
-
iconAnchor: [
|
276 |
-
popupAnchor: [0, -
|
277 |
});
|
278 |
|
279 |
this.tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: tempIcon }).addTo(this.map);
|
@@ -322,6 +333,9 @@ class TreeTrackMap {
|
|
322 |
lng: this.selectedLocation.lng
|
323 |
}));
|
324 |
|
|
|
|
|
|
|
325 |
this.showMessage('Location saved! Redirecting to form...', 'success');
|
326 |
|
327 |
setTimeout(() => {
|
@@ -471,17 +485,32 @@ class TreeTrackMap {
|
|
471 |
addTreeMarker(tree) {
|
472 |
const treeIcon = L.divIcon({
|
473 |
html: `
|
474 |
-
<div class="map-marker tree-marker">
|
475 |
-
<svg width="
|
476 |
-
|
477 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
478 |
</svg>
|
479 |
</div>
|
480 |
`,
|
481 |
-
className: 'custom-marker-icon',
|
482 |
-
iconSize: [
|
483 |
-
iconAnchor: [
|
484 |
-
popupAnchor: [0, -
|
485 |
});
|
486 |
|
487 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon }).addTo(this.map);
|
@@ -508,64 +537,73 @@ class TreeTrackMap {
|
|
508 |
const canDelete = this.canDeleteTree(tree.created_by);
|
509 |
|
510 |
const popupContent = `
|
511 |
-
<div style="
|
512 |
-
<div style="
|
513 |
-
<
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
<div style="margin-bottom: 16px;">
|
522 |
-
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 16px; font-size: 14px;">
|
523 |
-
${tree.local_name ? `<strong>Local Name:</strong><span>${tree.local_name}</span>` : ''}
|
524 |
-
${tree.scientific_name ? `<strong>Scientific:</strong><span><em>${tree.scientific_name}</em></span>` : ''}
|
525 |
-
${tree.common_name ? `<strong>Common:</strong><span>${tree.common_name}</span>` : ''}
|
526 |
-
<strong>Location:</strong><span>${tree.latitude.toFixed(6)}, ${tree.longitude.toFixed(6)}</span>
|
527 |
-
${tree.height ? `<strong>Height:</strong><span>${tree.height} meters</span>` : ''}
|
528 |
-
${tree.width ? `<strong>Girth:</strong><span>${tree.width} cm</span>` : ''}
|
529 |
-
<strong>Added by:</strong><span>${tree.created_by || 'Unknown'}</span>
|
530 |
-
<strong>Date:</strong><span>${new Date(tree.created_at).toLocaleDateString()}</span>
|
531 |
-
</div>
|
532 |
-
</div>
|
533 |
-
|
534 |
-
${tree.notes ? `
|
535 |
-
<div style="margin-bottom: 16px; padding: 12px; background: #f9fafb; border-radius: 8px;">
|
536 |
-
<strong style="color: #374151; font-size: 14px; display: block; margin-bottom: 8px;">Notes:</strong>
|
537 |
-
<div style="color: #6b7280; font-size: 13px; line-height: 1.5;">
|
538 |
-
${tree.notes}
|
539 |
</div>
|
540 |
</div>
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
</
|
552 |
-
|
553 |
-
${canDelete ? `
|
554 |
-
<button onclick="mapApp.deleteTree(${tree.id})"
|
555 |
-
style="flex: 1; background: #ef4444; color: white; border: none; padding: 10px 16px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: background-color 0.2s;"
|
556 |
-
onmouseover="this.style.backgroundColor='#dc2626'"
|
557 |
-
onmouseout="this.style.backgroundColor='#ef4444'">
|
558 |
-
Delete Tree
|
559 |
-
</button>
|
560 |
-
` : ''}
|
561 |
</div>
|
562 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
563 |
</div>
|
564 |
`;
|
565 |
|
566 |
marker.bindPopup(popupContent, {
|
567 |
-
maxWidth:
|
568 |
-
|
|
|
|
|
|
|
|
|
|
|
569 |
});
|
570 |
|
571 |
this.treeMarkers.push(marker);
|
|
|
260 |
// Remove existing temp marker
|
261 |
this.clearTempMarker();
|
262 |
|
263 |
+
// Create beautiful tree-shaped temp marker
|
264 |
const tempIcon = L.divIcon({
|
265 |
html: `
|
266 |
+
<div class="map-marker temp-marker" style="filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.3));">
|
267 |
+
<svg width="32" height="40" viewBox="0 0 32 40" fill="none">
|
268 |
+
<!-- Tree Pin Base -->
|
269 |
+
<path d="M16 0C9.37 0 4 5.37 4 12C4 21 16 40 16 40S28 21 28 12C28 5.37 22.63 0 16 0Z" fill="#dc2626" stroke="#991b1b" stroke-width="1"/>
|
270 |
+
<!-- Tree Icon Inside -->
|
271 |
+
<g transform="translate(16, 12)">
|
272 |
+
<!-- Tree trunk -->
|
273 |
+
<rect x="-1" y="3" width="2" height="4" fill="#8b5a2b"/>
|
274 |
+
<!-- Tree crown layers -->
|
275 |
+
<circle cx="0" cy="0" r="4" fill="#22c55e"/>
|
276 |
+
<circle cx="-1" cy="-1" r="3" fill="#16a34a"/>
|
277 |
+
<circle cx="1" cy="1" r="2.5" fill="#15803d"/>
|
278 |
+
<!-- Highlight -->
|
279 |
+
<circle cx="-1.5" cy="-2" r="1" fill="#4ade80" opacity="0.6"/>
|
280 |
+
</g>
|
281 |
</svg>
|
282 |
</div>
|
283 |
`,
|
284 |
+
className: 'custom-marker-icon tree-pin-temp',
|
285 |
+
iconSize: [32, 40],
|
286 |
+
iconAnchor: [16, 40],
|
287 |
+
popupAnchor: [0, -40]
|
288 |
});
|
289 |
|
290 |
this.tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: tempIcon }).addTo(this.map);
|
|
|
333 |
lng: this.selectedLocation.lng
|
334 |
}));
|
335 |
|
336 |
+
// Clear the message and redirect
|
337 |
+
const messageElement = document.getElementById('message');
|
338 |
+
if(messageElement) messageElement.classList.remove('show');
|
339 |
this.showMessage('Location saved! Redirecting to form...', 'success');
|
340 |
|
341 |
setTimeout(() => {
|
|
|
485 |
addTreeMarker(tree) {
|
486 |
const treeIcon = L.divIcon({
|
487 |
html: `
|
488 |
+
<div class="map-marker tree-marker" style="filter: drop-shadow(1px 1px 3px rgba(0,0,0,0.4));">
|
489 |
+
<svg width="36" height="44" viewBox="0 0 36 44" fill="none">
|
490 |
+
<!-- Tree Pin Base -->
|
491 |
+
<path d="M18 0C11.37 0 6 5.37 6 12C6 21 18 44 18 44S30 21 30 12C30 5.37 24.63 0 18 0Z" fill="#16a34a" stroke="#15803d" stroke-width="1"/>
|
492 |
+
<!-- Tree Icon Inside -->
|
493 |
+
<g transform="translate(18, 14)">
|
494 |
+
<!-- Tree trunk -->
|
495 |
+
<rect x="-1.5" y="4" width="3" height="5" fill="#8b5a2b" rx="1"/>
|
496 |
+
<!-- Tree crown - main layer -->
|
497 |
+
<circle cx="0" cy="0" r="5" fill="#22c55e"/>
|
498 |
+
<!-- Tree crown - shadow layer -->
|
499 |
+
<circle cx="-1" cy="-1" r="4" fill="#16a34a"/>
|
500 |
+
<!-- Tree crown - highlight layer -->
|
501 |
+
<circle cx="1" cy="1" r="3" fill="#15803d"/>
|
502 |
+
<!-- Small highlight for 3D effect -->
|
503 |
+
<circle cx="-2" cy="-2.5" r="1.2" fill="#4ade80" opacity="0.7"/>
|
504 |
+
<!-- Tiny highlight -->
|
505 |
+
<circle cx="-2.5" cy="-3" r="0.5" fill="#86efac" opacity="0.8"/>
|
506 |
+
</g>
|
507 |
</svg>
|
508 |
</div>
|
509 |
`,
|
510 |
+
className: 'custom-marker-icon tree-pin',
|
511 |
+
iconSize: [36, 44],
|
512 |
+
iconAnchor: [18, 44],
|
513 |
+
popupAnchor: [0, -44]
|
514 |
});
|
515 |
|
516 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon }).addTo(this.map);
|
|
|
537 |
const canDelete = this.canDeleteTree(tree.created_by);
|
538 |
|
539 |
const popupContent = `
|
540 |
+
<div style="width: 300px; max-width: 90vw; font-family: 'Segoe UI', sans-serif; position: relative;">
|
541 |
+
<div style="padding: 20px; padding-bottom: 16px;">
|
542 |
+
<div style="display: flex; justify-content: between; align-items: flex-start; margin-bottom: 16px;">
|
543 |
+
<div style="flex: 1;">
|
544 |
+
<h3 style="margin: 0 0 8px 0; color: #1e40af; font-size: 18px; font-weight: 600; line-height: 1.2; word-wrap: break-word;">
|
545 |
+
${treeName}
|
546 |
+
</h3>
|
547 |
+
<div style="color: #6b7280; font-size: 13px;">
|
548 |
+
<strong>Tree ID:</strong> #${tree.id}${tree.tree_code ? ' (' + tree.tree_code + ')' : ''}
|
549 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
</div>
|
551 |
</div>
|
552 |
+
|
553 |
+
<div style="margin-bottom: 16px;">
|
554 |
+
<div style="display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; font-size: 13px;">
|
555 |
+
${tree.local_name ? `<strong style="color: #374151;">Local:</strong><span style="word-wrap: break-word;">${tree.local_name}</span>` : ''}
|
556 |
+
${tree.scientific_name ? `<strong style="color: #374151;">Scientific:</strong><span style="word-wrap: break-word;"><em>${tree.scientific_name}</em></span>` : ''}
|
557 |
+
${tree.common_name ? `<strong style="color: #374151;">Common:</strong><span style="word-wrap: break-word;">${tree.common_name}</span>` : ''}
|
558 |
+
<strong style="color: #374151;">Location:</strong><span style="font-family: monospace; font-size: 12px;">${tree.latitude.toFixed(4)}, ${tree.longitude.toFixed(4)}</span>
|
559 |
+
${tree.height ? `<strong style="color: #374151;">Height:</strong><span>${tree.height}m</span>` : ''}
|
560 |
+
${tree.width ? `<strong style="color: #374151;">Girth:</strong><span>${tree.width}cm</span>` : ''}
|
561 |
+
<strong style="color: #374151;">Added by:</strong><span>${tree.created_by || 'Unknown'}</span>
|
562 |
+
<strong style="color: #374151;">Date:</strong><span>${new Date(tree.created_at).toLocaleDateString()}</span>
|
563 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
564 |
</div>
|
565 |
+
|
566 |
+
${tree.notes ? `
|
567 |
+
<div style="margin-bottom: 16px; padding: 12px; background: #f8fafc; border-radius: 6px; border-left: 4px solid #e2e8f0;">
|
568 |
+
<strong style="color: #374151; font-size: 13px; display: block; margin-bottom: 6px;">Notes:</strong>
|
569 |
+
<div style="color: #6b7280; font-size: 12px; line-height: 1.4; word-wrap: break-word;">
|
570 |
+
${tree.notes}
|
571 |
+
</div>
|
572 |
+
</div>
|
573 |
+
` : ''}
|
574 |
+
|
575 |
+
${canEdit || canDelete ? `
|
576 |
+
<div style="display: flex; gap: 8px; margin-top: 16px; padding-top: 16px; border-top: 1px solid #e5e7eb;">
|
577 |
+
${canEdit ? `
|
578 |
+
<button onclick="mapApp.editTree(${tree.id})"
|
579 |
+
style="flex: 1; background: #3b82f6; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; transition: all 0.2s; min-height: 36px;"
|
580 |
+
onmouseover="this.style.backgroundColor='#2563eb'; this.style.transform='translateY(-1px)';"
|
581 |
+
onmouseout="this.style.backgroundColor='#3b82f6'; this.style.transform='translateY(0)';">
|
582 |
+
✏️ Edit
|
583 |
+
</button>
|
584 |
+
` : ''}
|
585 |
+
${canDelete ? `
|
586 |
+
<button onclick="mapApp.deleteTree(${tree.id})"
|
587 |
+
style="flex: 1; background: #ef4444; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; transition: all 0.2s; min-height: 36px;"
|
588 |
+
onmouseover="this.style.backgroundColor='#dc2626'; this.style.transform='translateY(-1px)';"
|
589 |
+
onmouseout="this.style.backgroundColor='#ef4444'; this.style.transform='translateY(0)';">
|
590 |
+
🗑️ Delete
|
591 |
+
</button>
|
592 |
+
` : ''}
|
593 |
+
</div>
|
594 |
+
` : ''}
|
595 |
+
</div>
|
596 |
</div>
|
597 |
`;
|
598 |
|
599 |
marker.bindPopup(popupContent, {
|
600 |
+
maxWidth: 320,
|
601 |
+
minWidth: 280,
|
602 |
+
className: 'tree-popup',
|
603 |
+
closeButton: true,
|
604 |
+
autoClose: true,
|
605 |
+
autoPan: true,
|
606 |
+
closeOnEscapeKey: true
|
607 |
});
|
608 |
|
609 |
this.treeMarkers.push(marker);
|