121 lines
3.8 KiB
JavaScript
121 lines
3.8 KiB
JavaScript
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 = `
|
|
<p class="add-place-heading">Add a place</p>
|
|
<fieldset class="fieldset">
|
|
<legend class="fieldset-legend">Name</legend>
|
|
<input
|
|
type="text"
|
|
class="input input-bordered w-full add-place-input"
|
|
placeholder="e.g. Frituur De Leeuw"
|
|
autocomplete="off"
|
|
>
|
|
</fieldset>
|
|
<div class="add-place-actions">
|
|
<button type="button" class="btn btn-ghost btn-sm add-place-cancel">Cancel</button>
|
|
<button type="button" class="btn btn-primary btn-sm add-place-submit">Add</button>
|
|
</div>
|
|
`;
|
|
|
|
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);
|
|
}
|
|
}
|