import { Controller } from '@hotwired/stimulus'; import L from 'leaflet'; import 'leaflet/dist/leaflet.min.css'; L.Icon.Default.mergeOptions({ iconUrl: '/leaflet/marker-icon.png', iconRetinaUrl: '/leaflet/marker-icon-2x.png', shadowUrl: '/leaflet/marker-shadow.png', }); export default class extends Controller { connect() { this.map = L.map(this.element).setView([51.0543, 3.7174], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19, }).addTo(this.map); this.popup = null; this.loadSubjects(); this.map.on('click', (e) => this.openPopup(e.latlng)); } loadSubjects() { fetch('/api/subjects') .then(r => r.json()) .then(data => { data.payload.subjects.forEach(s => { if (s.latitude && s.longitude) { this.addMarker(s.latitude, s.longitude, s.name); } }); }); } openPopup(latlng) { if (this.popup) { this.popup.remove(); } const content = this.buildPopupContent(latlng); this.popup = L.popup({ closeOnClick: false, autoClose: false, minWidth: 280 }) .setLatLng(latlng) .setContent(content) .openOn(this.map); // Autofocus after Leaflet inserts the DOM this.popup.once('add', () => { const input = this.popup.getElement()?.querySelector('.add-place-input'); if (input) input.focus(); }); } buildPopupContent(latlng) { const container = document.createElement('div'); container.className = 'add-place-popup'; container.innerHTML = `

Add a place

Name
`; const input = container.querySelector('.add-place-input'); const submitBtn = container.querySelector('.add-place-submit'); const cancelBtn = container.querySelector('.add-place-cancel'); const submit = () => { const name = input.value.trim(); if (!name) { input.focus(); return; } this.createSubject(name, latlng); }; submitBtn.addEventListener('click', submit); cancelBtn.addEventListener('click', () => this.popup.remove()); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') submit(); if (e.key === 'Escape') this.popup.remove(); }); return container; } createSubject(name, latlng) { fetch('/api/subjects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, latitude: latlng.lat, longitude: latlng.lng, }), }) .then(r => r.json()) .then(() => { this.addMarker(latlng.lat, latlng.lng, name); this.popup.remove(); }); } addMarker(lat, lng, name) { L.marker([lat, lng]).addTo(this.map).bindPopup(name); } }