4 - Replace prompt() with anchored Leaflet popup, themed to aqua

This commit is contained in:
myrmidex 2026-06-05 21:56:56 +00:00
parent 02fae2737d
commit 6c02f9b4c1
4 changed files with 113 additions and 58 deletions

View file

@ -17,22 +17,11 @@ export default class extends Controller {
maxZoom: 19,
}).addTo(this.map);
this.pendingLatlng = 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.popup = null;
this.loadSubjects();
this.map.on('click', (e) => this.openModal(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());
this.map.on('click', (e) => this.openPopup(e.latlng));
}
loadSubjects() {
@ -47,28 +36,69 @@ export default class extends Controller {
});
}
openModal(latlng) {
this.pendingLatlng = latlng;
this.nameInput.value = '';
this.dialog.showModal();
this.nameInput.focus();
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();
});
}
closeModal() {
this.dialog.close();
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;
}
resetModal() {
this.pendingLatlng = null;
this.nameInput.value = '';
}
handleSubmit() {
const name = this.nameInput.value.trim();
if (!name || !this.pendingLatlng) return;
const latlng = this.pendingLatlng;
createSubject(name, latlng) {
fetch('/api/subjects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@ -81,7 +111,7 @@ export default class extends Controller {
.then(r => r.json())
.then(() => {
this.addMarker(latlng.lat, latlng.lng, name);
this.closeModal();
this.popup.remove();
});
}

View file

@ -5,3 +5,52 @@
html, body { height: 100%; margin: 0; }
.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;
}

View file

@ -22,7 +22,7 @@
{% block navbar %}
<nav class="navbar bg-base-100 text-base-content shrink-0">
<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 class="flex-none gap-2">
{% if app.user %}

View file

@ -1,29 +1,5 @@
{% extends 'base.html.twig' %}
{% block body %}
<div {{ stimulus_controller('map') }} class="map-container"
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>
<div {{ stimulus_controller('map') }} class="map-container"></div>
{% endblock %}