// Editor delle piantine (pagina /piantine/editor, solo amministratore). // // Mappa Leaflet su tessere OpenStreetMap: mostra l'area del KML, le piantine // già salvate e permette di disegnarne di nuove con leaflet-draw. Parla con il // componente Livewire attraverso eventi: // JS → Livewire: 'poligono-disegnato', 'piantina-selezionata-mappa' // Livewire → JS: 'piantine-dati-aggiornati' import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import 'leaflet-draw'; import 'leaflet-draw/dist/leaflet.draw.css'; const STILE_AREA = { color: '#4a45a8', weight: 2, dashArray: '8 6', fill: false, interactive: false }; // Una piantina collegata a un territorio è "a posto"; una scollegata è un // lavoro ancora aperto, e si vede senza dover aprire il pannello. const STILE_COLLEGATA = { color: '#8a8578', weight: 2, fillColor: '#8a8578', fillOpacity: 0.1 }; const STILE_SCOLLEGATA = { color: '#b4762a', weight: 2, dashArray: '6 5', fillColor: '#c98b3a', fillOpacity: 0.1 }; const STILE_SELEZIONATA = { color: '#4a45a8', weight: 3, fillColor: '#605cb2', fillOpacity: 0.18 }; const STILE_IN_ATTESA = { color: '#b3352c', weight: 3, fillColor: '#d0504a', fillOpacity: 0.15 }; function initPiantine() { const contenitore = document.getElementById('piantine-map'); if (!contenitore || contenitore.dataset.inizializzato) { return; } contenitore.dataset.inizializzato = '1'; let dati = { area: null, piantine: [], selezionata: null }; try { dati = JSON.parse(document.getElementById('piantine-dati').textContent); } catch { // si parte comunque con la mappa vuota } // Etichette in italiano per la barra di disegno. L.drawLocal.draw.toolbar.buttons.polygon = 'Disegna il confine di una piantina'; L.drawLocal.draw.toolbar.actions = { title: 'Annulla il disegno', text: 'Annulla' }; L.drawLocal.draw.toolbar.finish = { title: 'Concludi il disegno', text: 'Concludi' }; L.drawLocal.draw.toolbar.undo = { title: 'Rimuovi l\'ultimo punto', text: 'Ultimo punto' }; L.drawLocal.draw.handlers.polygon.tooltip = { start: 'Clicca sulla mappa per iniziare il confine.', cont: 'Continua a cliccare lungo il confine.', end: 'Clicca sul primo punto per chiudere il poligono.', }; const mappa = L.map(contenitore, { zoomControl: true }); L.control.scale({ imperial: false }).addTo(mappa); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors', }).addTo(mappa); let layerArea = null; const layerSalvate = L.featureGroup().addTo(mappa); let layerInAttesa = null; mappa.addControl(new L.Control.Draw({ draw: { polygon: { allowIntersection: false, showArea: false, shapeOptions: STILE_IN_ATTESA }, polyline: false, rectangle: false, circle: false, circlemarker: false, marker: false, }, edit: false, })); function stileDi(piantina, selezionata) { if (selezionata) return STILE_SELEZIONATA; return piantina.collegata ? STILE_COLLEGATA : STILE_SCOLLEGATA; } function ridisegna(nuoviDati, adattaVista) { dati = nuoviDati; if (layerArea) { mappa.removeLayer(layerArea); layerArea = null; } if (dati.area) { layerArea = L.geoJSON(dati.area, { style: STILE_AREA, interactive: false }).addTo(mappa); } layerSalvate.clearLayers(); let layerSelezionata = null; for (const piantina of dati.piantine) { const selezionata = dati.selezionata === piantina.mappaId; const layer = L.geoJSON( { type: 'Feature', properties: {}, geometry: piantina.geometry }, { style: stileDi(piantina, selezionata) } ); layer.bindTooltip(piantina.etichetta, { sticky: true }); layer.on('click', () => { window.Livewire.dispatch('piantina-selezionata-mappa', { mappaId: piantina.mappaId }); }); layerSalvate.addLayer(layer); if (selezionata) { layerSelezionata = layer; } } // Aprire una piantina dall'elenco deve portarci sopra; un salvataggio // no, altrimenti la vista salta via mentre si sta ancora lavorando. if (adattaVista && layerSelezionata) { mappa.fitBounds(layerSelezionata.getBounds().pad(0.25)); return; } if (!adattaVista) { return; } const riferimento = layerArea ?? (dati.piantine.length ? layerSalvate : null); if (riferimento) { mappa.fitBounds(riferimento.getBounds().pad(0.08)); } else { mappa.setView([41.9, 12.5], 6); // Italia: nessun dato ancora caricato } } ridisegna(dati, true); mappa.on(L.Draw.Event.CREATED, (evento) => { if (layerInAttesa) { mappa.removeLayer(layerInAttesa); } layerInAttesa = evento.layer; layerInAttesa.setStyle(STILE_IN_ATTESA); layerInAttesa.addTo(mappa); window.Livewire.dispatch('poligono-disegnato', { geometry: evento.layer.toGeoJSON().geometry }); }); let registrato = false; document.addEventListener('livewire:init', registraEventi); if (window.Livewire) { registraEventi(); } function registraEventi() { if (registrato) { return; } registrato = true; window.Livewire.on('piantine-dati-aggiornati', ({ dati: nuovi, inquadra }) => { // Dopo un salvataggio il poligono provvisorio è diventato // definitivo (o è stato scartato): via il tracciato rosso. if (layerInAttesa) { mappa.removeLayer(layerInAttesa); layerInAttesa = null; } ridisegna(nuovi, Boolean(inquadra)); }); } // La mappa nasce dentro un contenitore che il pannello laterale può // coprire: senza questo, Leaflet resta convinto delle misure di partenza e // le tessere si disallineano dal puntatore. window.addEventListener('resize', () => mappa.invalidateSize()); setTimeout(() => mappa.invalidateSize(), 60); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initPiantine); } else { initPiantine(); }