Skip to content

Commit 2cef167

Browse files
ndbroadbentclaude
andcommitted
Add activity list modal to map HTML
- Add "View Activity List →" link in the info box - Modal shows all activities in a scrollable list - Each row has thumbnail, title, location, sender, date - Includes Google Maps and source links - Click outside or press Escape to close 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5b90ef2 commit 2cef167

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

src/export/map-html.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,40 @@ function generateMarkersJS(points: readonly MapPoint[]): string {
155155
.join('\n')
156156
}
157157

158+
/**
159+
* Generate activity list HTML for modal.
160+
*/
161+
function generateActivityListHTML(points: readonly MapPoint[]): string {
162+
return points
163+
.map((p) => {
164+
const senderName = p.sender.split(' ')[0] ?? p.sender
165+
const mapsUrl = p.placeId
166+
? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(p.activity)}&query_place_id=${p.placeId}`
167+
: null
168+
const thumbnail = p.imagePath
169+
? `<img src="${escapeJS(p.imagePath)}" class="activity-thumb" alt="" />`
170+
: '<div class="activity-thumb-placeholder"></div>'
171+
172+
return `
173+
<div class="activity-row">
174+
${thumbnail}
175+
<div class="activity-content">
176+
<div class="activity-title">${escapeJS(p.activity)}</div>
177+
<div class="activity-meta">
178+
${p.location ? `<span class="activity-location">${escapeJS(p.location)}</span> · ` : ''}
179+
${escapeJS(senderName)} · ${p.date}
180+
</div>
181+
<div class="activity-links">
182+
${mapsUrl ? `<a href="${escapeJS(mapsUrl)}" target="_blank">Google Maps</a>` : ''}
183+
${p.url ? `<a href="${escapeJS(p.url)}" target="_blank">Source</a>` : ''}
184+
</div>
185+
</div>
186+
</div>
187+
`
188+
})
189+
.join('')
190+
}
191+
158192
/**
159193
* Generate legend HTML.
160194
*/
@@ -330,6 +364,28 @@ export function exportToMapHTML(
330364
border: 2px solid white;
331365
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
332366
}
367+
.view-list-link { display:inline-block; margin-top:10px; color:#2563eb; cursor:pointer; font-weight:500; }
368+
.view-list-link:hover { text-decoration:underline; }
369+
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:2000; justify-content:center; align-items:center; }
370+
.modal-overlay.open { display:flex; }
371+
.modal { background:#fff; border-radius:12px; width:90%; max-width:700px; max-height:85vh; display:flex; flex-direction:column; box-shadow:0 20px 50px rgba(0,0,0,0.3); }
372+
.modal-header { padding:20px 24px; border-bottom:1px solid #e5e7eb; display:flex; justify-content:space-between; align-items:center; }
373+
.modal-header h2 { margin:0; font-size:20px; font-weight:600; }
374+
.modal-close { background:none; border:none; font-size:24px; cursor:pointer; color:#6b7280; padding:4px 8px; }
375+
.modal-close:hover { color:#111; }
376+
.modal-body { padding:16px 24px; overflow-y:auto; flex:1; }
377+
.activity-row { display:flex; gap:16px; padding:12px 0; border-bottom:1px solid #f3f4f6; }
378+
.activity-row:last-child { border-bottom:none; }
379+
.activity-thumb, .activity-thumb-placeholder { width:64px; height:64px; border-radius:8px; flex-shrink:0; }
380+
.activity-thumb { object-fit:cover; }
381+
.activity-thumb-placeholder { background:#f3f4f6; }
382+
.activity-content { flex:1; min-width:0; }
383+
.activity-title { font-weight:500; color:#111; margin-bottom:4px; }
384+
.activity-meta { font-size:13px; color:#6b7280; margin-bottom:6px; }
385+
.activity-location { color:#2563eb; }
386+
.activity-links { display:flex; gap:12px; font-size:13px; }
387+
.activity-links a { color:#2563eb; text-decoration:none; }
388+
.activity-links a:hover { text-decoration:underline; }
333389
</style>
334390
</head>
335391
<body>
@@ -342,12 +398,25 @@ export function exportToMapHTML(
342398
Click markers to see details.<br>
343399
Zoom in to see individual pins.
344400
</p>
401+
<a class="view-list-link" onclick="openModal()">View Activity List →</a>
345402
</div>
346403
347404
<div class="legend">
348405
${legendHTML}
349406
</div>
350407
408+
<div class="modal-overlay" id="activityModal" onclick="closeModal(event)">
409+
<div class="modal" onclick="event.stopPropagation()">
410+
<div class="modal-header">
411+
<h2>Activity List</h2>
412+
<button class="modal-close" onclick="closeModal()">&times;</button>
413+
</div>
414+
<div class="modal-body">
415+
${generateActivityListHTML(points)}
416+
</div>
417+
</div>
418+
</div>
419+
351420
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
352421
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
353422
<script>
@@ -375,6 +444,21 @@ export function exportToMapHTML(
375444
if (markersLayer.getLayers().length > 0) {
376445
map.fitBounds(markersLayer.getBounds(), { padding: [50, 50] });
377446
}
447+
448+
// Modal functions
449+
function openModal() {
450+
document.getElementById('activityModal').classList.add('open');
451+
document.body.style.overflow = 'hidden';
452+
}
453+
function closeModal(e) {
454+
if (!e || e.target === e.currentTarget) {
455+
document.getElementById('activityModal').classList.remove('open');
456+
document.body.style.overflow = '';
457+
}
458+
}
459+
document.addEventListener('keydown', function(e) {
460+
if (e.key === 'Escape') closeModal();
461+
});
378462
</script>
379463
</body>
380464
</html>`

0 commit comments

Comments
 (0)