245 lines
No EOL
9.5 KiB
HTML
245 lines
No EOL
9.5 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
{% load i18n %}
|
|
|
|
{% block head %}
|
|
|
|
<!-- Mapbox GL Draw 1.5.0 (local) -->
|
|
<link rel="stylesheet" href="{% static 'common/mapbox-gl-draw.css' %}" />
|
|
|
|
<link href="https://cdn.maptiler.com/maptiler-geocoding-control/v2.1.4/style.css" rel="stylesheet">
|
|
|
|
{% endblock head%}
|
|
|
|
{% block content %}
|
|
<div class="container d-flex justify-content-center mt-5">
|
|
<div class="w-100" style="max-width: 700px;">
|
|
<h2 class="mb-4 text-center" style="color: var(--brand-color);">
|
|
{% if mode == "update" %}{% translate "Modifier le projet" %}{% else %}{% translate "Créer un nouveau projet" %}{% endif %}
|
|
</h2>
|
|
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
|
|
{% for field in form %}
|
|
<div class="mb-3">
|
|
{{ field.label_tag }}
|
|
{{ field }}
|
|
{% if field.errors %}
|
|
<div class="text-danger">
|
|
{{ field.errors }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<div id="map" style="height: 400px;" class="mb-3"></div>
|
|
<input type="hidden" name="geom_geojson" id="id_geom_geojson" value="{{ form.geom.value|default:'' }}">
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
{% if mode == "update" %}{% translate "Enregistrer les modifications" %}{% else %}{% translate "Créer le projet" %}{% endif %}
|
|
</button>
|
|
</form>
|
|
|
|
<div class="mt-3">
|
|
<small class="text-muted">
|
|
{% blocktranslate %}
|
|
Les champs avec une <span style="border-bottom: 1px dashed #6c757d;">bordure en pointillés</span> sont facultatifs.
|
|
{% endblocktranslate %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock content%}
|
|
|
|
{% block script %}
|
|
|
|
<!-- Mapbox GL Draw 1.5.0 (local) -->
|
|
<script src="{% static 'common/mapbox-gl-draw.js' %}"></script>
|
|
<script src="https://cdn.maptiler.com/maptiler-geocoding-control/v2.1.4/maplibregl.umd.js"></script>
|
|
|
|
<script src="{% static 'common/turf.min.js' %}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
function initCustomMultipleSelect(fieldId, placeholder, allSelectedMsg) {
|
|
const originalSelect = document.getElementById(fieldId);
|
|
if (!originalSelect) return;
|
|
|
|
// Copy original styles BEFORE hiding the element
|
|
const originalStyle = originalSelect.getAttribute('style') || '';
|
|
|
|
// Hide original select
|
|
originalSelect.style.display = 'none';
|
|
|
|
// Create wrapper container
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'custom-multiselect-wrapper';
|
|
originalSelect.parentNode.insertBefore(wrapper, originalSelect.nextSibling);
|
|
|
|
// Create container for selected items (badges)
|
|
const badgesContainer = document.createElement('div');
|
|
badgesContainer.className = 'd-flex flex-wrap gap-2 mb-2';
|
|
wrapper.appendChild(badgesContainer);
|
|
|
|
// Create the select dropdown for adding items
|
|
const adderSelect = document.createElement('select');
|
|
adderSelect.className = 'form-select';
|
|
|
|
// Copy original styles but strip out any display property
|
|
if (originalStyle) {
|
|
const cleanStyle = originalStyle.replace(/display\s*:\s*[^;]+;?/gi, '');
|
|
adderSelect.setAttribute('style', cleanStyle);
|
|
}
|
|
wrapper.appendChild(adderSelect);
|
|
|
|
// Function to rebuild options in the adder dropdown and the badge list
|
|
function updateWidget() {
|
|
badgesContainer.innerHTML = '';
|
|
adderSelect.innerHTML = '';
|
|
|
|
// Add placeholder option
|
|
const placeholderOpt = document.createElement('option');
|
|
placeholderOpt.value = '';
|
|
placeholderOpt.textContent = placeholder;
|
|
placeholderOpt.selected = true;
|
|
placeholderOpt.disabled = true;
|
|
adderSelect.appendChild(placeholderOpt);
|
|
|
|
let hasAvailableOptions = false;
|
|
|
|
Array.from(originalSelect.options).forEach(option => {
|
|
if (option.selected) {
|
|
// Create badge for selected item
|
|
const badge = document.createElement('span');
|
|
badge.className = 'badge bg-secondary d-inline-flex align-items-center gap-1 py-1.5 px-2.5';
|
|
badge.style.fontSize = '0.9em';
|
|
badge.style.fontWeight = 'normal';
|
|
badge.style.cursor = 'default';
|
|
|
|
badge.textContent = option.textContent;
|
|
|
|
const removeBtn = document.createElement('button');
|
|
removeBtn.type = 'button';
|
|
removeBtn.className = 'btn-close btn-close-white ms-1';
|
|
removeBtn.style.fontSize = '0.55em';
|
|
removeBtn.style.padding = '0';
|
|
removeBtn.ariaLabel = 'Remove';
|
|
|
|
removeBtn.addEventListener('click', () => {
|
|
option.selected = false;
|
|
// Trigger change event on original select for any listener
|
|
originalSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
updateWidget();
|
|
});
|
|
|
|
badge.appendChild(removeBtn);
|
|
badgesContainer.appendChild(badge);
|
|
} else {
|
|
// Add to adder dropdown
|
|
const opt = document.createElement('option');
|
|
opt.value = option.value;
|
|
opt.textContent = option.textContent;
|
|
adderSelect.appendChild(opt);
|
|
hasAvailableOptions = true;
|
|
}
|
|
});
|
|
|
|
// Hide or disable select dropdown if no options left
|
|
if (!hasAvailableOptions) {
|
|
const noOpt = document.createElement('option');
|
|
noOpt.value = '';
|
|
noOpt.textContent = allSelectedMsg;
|
|
noOpt.disabled = true;
|
|
adderSelect.appendChild(noOpt);
|
|
}
|
|
}
|
|
|
|
// Handle selection from dropdown
|
|
adderSelect.addEventListener('change', (e) => {
|
|
const val = e.target.value;
|
|
if (!val) return;
|
|
|
|
const optionToSelect = Array.from(originalSelect.options).find(opt => opt.value === val);
|
|
if (optionToSelect) {
|
|
optionToSelect.selected = true;
|
|
// Trigger change event on original select
|
|
originalSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
updateWidget();
|
|
}
|
|
});
|
|
|
|
// Initial load
|
|
updateWidget();
|
|
}
|
|
|
|
initCustomMultipleSelect('id_tags', "{% translate 'Ajouter un tag...' %}", "{% translate 'Tous les tags sont sélectionnés' %}");
|
|
initCustomMultipleSelect('id_thematics', "{% translate 'Ajouter une thématique...' %}", "{% translate 'Toutes les thématiques sont sélectionnées' %}");
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
let map, draw;
|
|
|
|
function updateArea(e) {
|
|
if (!draw) return;
|
|
const data = draw.getAll();
|
|
const geom = JSON.stringify(data);
|
|
document.getElementById('id_geom_geojson').value = geom;
|
|
}
|
|
|
|
try {
|
|
map = new maplibregl.Map({
|
|
container: 'map', // ID de l'élément HTML
|
|
style: window.MAP_STYLE_LIGHT, // Style Maplibre
|
|
center: [4.3517, 50.8503], // Coordonnées par défaut (Bruxelles)
|
|
zoom: 11
|
|
});
|
|
MapboxDraw.constants.classes.CANVAS = 'maplibregl-canvas';
|
|
MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl';
|
|
MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-';
|
|
MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group';
|
|
MapboxDraw.constants.classes.ATTRIBUTION = 'maplibregl-ctrl-attrib';
|
|
draw = new MapboxDraw({
|
|
displayControlsDefault: false,
|
|
controls: {
|
|
polygon: true,
|
|
trash: true
|
|
}
|
|
});
|
|
map.addControl(draw);
|
|
map.on('draw.create', updateArea);
|
|
map.on('draw.delete', updateArea);
|
|
map.on('draw.update', updateArea);
|
|
|
|
const maptilerApiKey = (function(styleUrl) { if (!styleUrl) return ''; try { const url = new URL(styleUrl); return url.searchParams.get('key') || ''; } catch(e) { return ''; } })(window.MAP_STYLE_LIGHT);
|
|
if (maptilerApiKey) {
|
|
const gc = new maplibreglMaptilerGeocoder.GeocodingControl({
|
|
apiKey: maptilerApiKey,
|
|
country: ["BE"]
|
|
});
|
|
map.addControl(gc, "top-left");
|
|
}
|
|
|
|
map.on('load', () => {
|
|
{% if geom_geojson %}
|
|
const existingGeom = {{ geom_geojson|safe }};
|
|
const feature = {
|
|
type: "Feature",
|
|
geometry: existingGeom,
|
|
properties: {}
|
|
};
|
|
draw.add(feature);
|
|
|
|
const bbox = turf.bbox(feature);
|
|
map.fitBounds(bbox, { padding: 20 });
|
|
|
|
updateArea();
|
|
{% endif %}
|
|
});
|
|
} catch (e) {
|
|
console.error("Map initialization failed safely:", e);
|
|
}
|
|
</script>
|
|
|
|
{% endblock script %} |