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,
|
||||
}).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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue