4 - Replace prompt() with anchored Leaflet popup, themed to aqua
This commit is contained in:
parent
02fae2737d
commit
6c02f9b4c1
4 changed files with 113 additions and 58 deletions
|
|
@ -17,22 +17,11 @@ export default class extends Controller {
|
||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
}).addTo(this.map);
|
}).addTo(this.map);
|
||||||
|
|
||||||
this.pendingLatlng = null;
|
this.popup = null;
|
||||||
|
|
||||||
this.dialog = document.getElementById('add-subject-dialog');
|
|
||||||
this.nameInput = document.getElementById('subject-name');
|
|
||||||
this.submitBtn = document.getElementById('subject-submit');
|
|
||||||
this.cancelBtn = document.getElementById('subject-cancel');
|
|
||||||
|
|
||||||
this.loadSubjects();
|
this.loadSubjects();
|
||||||
|
|
||||||
this.map.on('click', (e) => this.openModal(e.latlng));
|
this.map.on('click', (e) => this.openPopup(e.latlng));
|
||||||
|
|
||||||
this.submitBtn.addEventListener('click', () => this.handleSubmit());
|
|
||||||
this.cancelBtn.addEventListener('click', () => this.closeModal());
|
|
||||||
|
|
||||||
// Esc key / backdrop close — clear pending state
|
|
||||||
this.dialog.addEventListener('close', () => this.resetModal());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSubjects() {
|
loadSubjects() {
|
||||||
|
|
@ -47,28 +36,69 @@ export default class extends Controller {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openModal(latlng) {
|
openPopup(latlng) {
|
||||||
this.pendingLatlng = latlng;
|
if (this.popup) {
|
||||||
this.nameInput.value = '';
|
this.popup.remove();
|
||||||
this.dialog.showModal();
|
|
||||||
this.nameInput.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeModal() {
|
const content = this.buildPopupContent(latlng);
|
||||||
this.dialog.close();
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resetModal() {
|
buildPopupContent(latlng) {
|
||||||
this.pendingLatlng = null;
|
const container = document.createElement('div');
|
||||||
this.nameInput.value = '';
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
createSubject(name, latlng) {
|
||||||
const name = this.nameInput.value.trim();
|
|
||||||
if (!name || !this.pendingLatlng) return;
|
|
||||||
|
|
||||||
const latlng = this.pendingLatlng;
|
|
||||||
|
|
||||||
fetch('/api/subjects', {
|
fetch('/api/subjects', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -81,7 +111,7 @@ export default class extends Controller {
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.addMarker(latlng.lat, latlng.lng, name);
|
this.addMarker(latlng.lat, latlng.lng, name);
|
||||||
this.closeModal();
|
this.popup.remove();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,52 @@
|
||||||
|
|
||||||
html, body { height: 100%; margin: 0; }
|
html, body { height: 100%; margin: 0; }
|
||||||
.map-container { height: calc(100vh - 4rem); width: 100%; }
|
.map-container { height: calc(100vh - 4rem); width: 100%; }
|
||||||
|
|
||||||
|
/* Leaflet popup chrome — match aqua theme */
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
background: oklch(37% 0.146 265.522) !important;
|
||||||
|
color: oklch(90% 0.058 230.902) !important;
|
||||||
|
border-radius: 0.75rem !important;
|
||||||
|
box-shadow: 0 4px 24px oklch(0% 0 0 / 0.4) !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content {
|
||||||
|
margin: 0 !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
background: oklch(37% 0.146 265.522) !important;
|
||||||
|
}
|
||||||
|
.leaflet-popup-close-button {
|
||||||
|
color: oklch(90% 0.058 230.902) !important;
|
||||||
|
top: 0.5rem !important;
|
||||||
|
right: 0.5rem !important;
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.leaflet-popup-close-button:hover {
|
||||||
|
color: oklch(85.661% 0.144 198.645) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Add-place popup layout */
|
||||||
|
.add-place-popup {
|
||||||
|
padding: 0.25rem 1rem 1rem;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.add-place-heading {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: oklch(85.661% 0.144 198.645);
|
||||||
|
}
|
||||||
|
.add-place-popup .fieldset-legend {
|
||||||
|
padding-left: 2px !important;
|
||||||
|
}
|
||||||
|
.add-place-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
{% block navbar %}
|
{% block navbar %}
|
||||||
<nav class="navbar bg-base-100 text-base-content shrink-0">
|
<nav class="navbar bg-base-100 text-base-content shrink-0">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<a href="{{ path('app_index_index') }}" class="btn btn-ghost text-xl font-bold">Rater</a>
|
<a href="{{ path('app_index_index') }}" class="btn btn-ghost text-xl font-bold text-primary">Rater</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none gap-2">
|
<div class="flex-none gap-2">
|
||||||
{% if app.user %}
|
{% if app.user %}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,5 @@
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div {{ stimulus_controller('map') }} class="map-container"
|
<div {{ stimulus_controller('map') }} class="map-container"></div>
|
||||||
data-map-dialog-outlet="#add-subject-dialog">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dialog id="add-subject-dialog" class="modal">
|
|
||||||
<div class="modal-box">
|
|
||||||
<h3 class="text-lg font-bold mb-4">Add a place</h3>
|
|
||||||
|
|
||||||
<fieldset class="fieldset">
|
|
||||||
<legend class="fieldset-legend">Name</legend>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="subject-name"
|
|
||||||
class="input w-full"
|
|
||||||
placeholder="e.g. Frituur De Leeuw"
|
|
||||||
autocomplete="off"
|
|
||||||
>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<button id="subject-cancel" class="btn">Cancel</button>
|
|
||||||
<button id="subject-submit" class="btn btn-primary">Add</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue