Compare commits

...
2 Commits
Author SHA1 Message Date
livio 28a982abed Modifica ed elimina il disegno selezionato 2026-09-24 14:20:46 +02:00
livio 0ffcc7ae31 Distingui visivamente la gerarchia dei layer 2026-09-24 09:21:08 +02:00
3 changed files with 320 additions and 25 deletions

No files matched your search

+2 -2
View File
@@ -28,8 +28,8 @@
<ApplicationId>com.companyname.lmemappecatastaliapp</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>0.2.26</ApplicationDisplayVersion>
<ApplicationVersion>30</ApplicationVersion>
<ApplicationDisplayVersion>0.2.28</ApplicationDisplayVersion>
<ApplicationVersion>32</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>
+70 -6
View File
@@ -266,16 +266,25 @@ input[type="checkbox"] {
.layer-manager-group,
.layer-manager-block,
.layer-manager-object {
border: 1px solid #dce3e6;
border: 1px solid var(--layer-group-border, #dce3e6);
border-radius: 6px;
background: #fff;
background: var(--layer-group-surface, #fff);
}
.layer-manager-group > summary,
.layer-manager-block > summary {
background: var(--layer-group-parent, #f6f8f9);
}
.layer-manager-object > summary {
background: var(--layer-group-surface, #fff);
}
.layer-manager-group > summary,
.layer-manager-block > summary,
.layer-manager-object > summary {
display: grid;
grid-template-columns: 28px minmax(0, 1fr) 24px;
grid-template-columns: 28px minmax(0, 1fr) 50px;
grid-template-areas:
"handle title count"
". visibility moves"
@@ -359,7 +368,7 @@ input[type="checkbox"] {
.layer-move-buttons {
grid-area: moves;
display: inline-grid;
grid-template-columns: 24px;
grid-template-columns: 24px 24px;
gap: 2px;
}
@@ -384,8 +393,24 @@ input[type="checkbox"] {
grid-area: title;
min-width: 0;
overflow-wrap: anywhere;
color: var(--layer-group-accent, var(--ink));
font-size: 12px;
font-weight: 700;
}
.layer-manager-group > summary > .layer-manager-title {
font-size: 13px;
font-weight: 800;
}
.layer-manager-block > summary > .layer-manager-title {
font-size: 12px;
font-weight: 800;
}
.layer-manager-object > summary > .layer-manager-title {
font-size: 11px;
font-style: italic;
font-weight: 400;
}
.layer-manager-count {
@@ -396,7 +421,46 @@ input[type="checkbox"] {
}
.layer-manager-children {
padding: 0 4px 5px 6px;
margin-left: 4px;
padding: 4px 3px 5px 6px;
border-left: 2px solid var(--layer-group-border, #dce3e6);
}
.layer-manager-block > .layer-manager-children {
margin-left: 5px;
padding-left: 5px;
}
.layer-manager-object {
margin-left: 3px;
border-radius: 4px;
}
.layer-manager-object > summary {
grid-template-columns: 24px minmax(0, 1fr) 46px;
gap: 4px;
padding: 3px 4px;
}
.layer-manager-object > summary > .layer-drag-handle {
width: 24px;
height: 24px;
font-size: 15px;
}
.layer-manager-object > summary > .layer-move-buttons {
grid-template-columns: 22px 22px;
}
.layer-manager-object > summary > .layer-move-buttons button {
width: 22px;
height: 24px;
}
.layer-manager-object > summary .layer-rename-button,
.layer-manager-object > summary .layer-remove-button {
min-height: 24px;
font-size: 9px;
}
.layer-manager-details {
+248 -17
View File
@@ -22,8 +22,12 @@ const lmeState = {
importedSheetsVisible: false,
importedMeasurements: null,
drawings: null,
drawingsRenderer: null,
drawingSelection: null,
drawingMeasurements: null,
drawControl: null,
drawingDeleteButton: null,
ignoreMapClickUntil: 0,
activeDrawHandler: null,
drawEventsBound: false,
includeKmlSheetLayer: false,
@@ -178,10 +182,12 @@ function init(dotNetReference) {
});
const managerHighlightPane = lmeState.map.createPane("managerHighlightPane");
managerHighlightPane.style.zIndex = "650";
lmeState.drawingsRenderer = L.canvas({ pane: "drawingsPane", tolerance: 14 });
setLayerOrder(lmeState.layerOrder);
lmeState.imported = L.featureGroup().addTo(lmeState.map);
lmeState.importedSheets = L.featureGroup();
lmeState.drawings = L.featureGroup().addTo(lmeState.map);
lmeState.drawingSelection = L.featureGroup();
lmeState.importedMeasurements = L.layerGroup().addTo(lmeState.map);
lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map);
lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map);
@@ -205,6 +211,9 @@ function init(dotNetReference) {
}
lmeState.map.on("click", async event => {
if (event.originalEvent?._lmeDrawingSelected || Date.now() < lmeState.ignoreMapClickUntil) {
return;
}
clearManagerHighlight();
const info = await queryFeatureInfo(event.latlng);
if (info) {
@@ -232,6 +241,9 @@ function init(dotNetReference) {
if (!lmeState.managerHighlightedObjectId && !lmeState.managerHighlightedParentKey) {
return;
}
if (event.target?.closest?.(".leaflet-draw")) {
return;
}
const selectedObject = event.target?.closest?.(".layer-manager-object");
const selectedParent = event.target?.closest?.("[data-highlight-key]");
if (selectedObject?.dataset.objectId === lmeState.managerHighlightedObjectId
@@ -1268,6 +1280,37 @@ function layerManagerRowActions(renameButton, removeButton) {
return `<span class="layer-row-actions">${renameButton}${removeButton}</span>`;
}
const layerManagerPalettes = [
{ surface: "#f0fdf4", parent: "#dcfce7", border: "#86efac", accent: "#166534" },
{ surface: "#eff6ff", parent: "#dbeafe", border: "#93c5fd", accent: "#1d4ed8" },
{ surface: "#fffbeb", parent: "#fef3c7", border: "#fcd34d", accent: "#92400e" },
{ surface: "#fff1f2", parent: "#ffe4e6", border: "#fda4af", accent: "#be123c" },
{ surface: "#f5f3ff", parent: "#ede9fe", border: "#c4b5fd", accent: "#6d28d9" },
{ surface: "#ecfeff", parent: "#cffafe", border: "#67e8f9", accent: "#0e7490" },
{ surface: "#f7fee7", parent: "#ecfccb", border: "#bef264", accent: "#3f6212" }
];
function applyLayerManagerPalette(row, key) {
const hash = [...String(key)].reduce((value, character) =>
((value * 31) + character.charCodeAt(0)) >>> 0, 0);
const palette = layerManagerPalettes[hash % layerManagerPalettes.length];
row.style.setProperty("--layer-group-surface", palette.surface);
row.style.setProperty("--layer-group-parent", palette.parent);
row.style.setProperty("--layer-group-border", palette.border);
row.style.setProperty("--layer-group-accent", palette.accent);
}
function clearManagedObjectState(id) {
lmeState.managerHiddenObjects.delete(id);
lmeState.managerHiddenObjectIds.delete(id);
lmeState.managerHiddenMeasurementSummaryIds.delete(id);
lmeState.managerHiddenMeasurementSideIds.delete(id);
Object.keys(lmeState.managerObjectOrder).forEach(categoryKey => {
lmeState.managerObjectOrder[categoryKey] = lmeState.managerObjectOrder[categoryKey]
.filter(objectId => objectId !== id);
});
}
function removeManagedRecords(records, subject) {
if (!records.length) {
return;
@@ -1284,10 +1327,7 @@ function removeManagedRecords(records, subject) {
if (record.parent?.hasLayer?.(record.layer)) {
record.parent.removeLayer(record.layer);
}
lmeState.managerHiddenObjects.delete(record.id);
lmeState.managerHiddenObjectIds.delete(record.id);
lmeState.managerHiddenMeasurementSummaryIds.delete(record.id);
lmeState.managerHiddenMeasurementSideIds.delete(record.id);
clearManagedObjectState(record.id);
});
categoryKeys.forEach(categoryKey => {
const order = lmeState.managerObjectOrder[categoryKey] || [];
@@ -1388,7 +1428,9 @@ function renderLayerManagerCategory(category, records) {
const group = document.createElement("details");
group.className = "layer-manager-group";
group.dataset.dragType = "category";
group.dataset.layerDepth = "0";
group.dataset.categoryKey = category.key;
applyLayerManagerPalette(group, `category:${category.key}`);
bindLayerManagerExpansion(group, `category:${category.key}`);
const categoryName = managedCategoryName(category, records);
const renameButton = managedCategoryCanRename(category)
@@ -1438,8 +1480,10 @@ function renderLayerManagerBlock(category, source, records) {
const block = document.createElement("details");
block.className = "layer-manager-block";
block.dataset.dragType = "block";
block.dataset.layerDepth = "1";
block.dataset.categoryKey = category.key;
block.dataset.blockKey = source;
applyLayerManagerPalette(block, `block:${category.key}:${source}`);
bindLayerManagerExpansion(block, `block:${category.key}:${source}`);
const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
const visible = visibleCount === records.length;
@@ -1479,6 +1523,7 @@ function renderLayerManagerObject(record, index) {
object.classList.add("layer-manager-object-selected");
}
object.dataset.dragType = "object";
object.dataset.layerDepth = "2";
object.dataset.objectId = record.id;
bindLayerManagerExpansion(object, `object:${record.id}`);
const visible = !lmeState.managerHiddenObjectIds.has(record.id);
@@ -1736,10 +1781,60 @@ function highlightManagedObject(record) {
document.querySelectorAll(".layer-manager-object").forEach(row => {
row.classList.toggle("layer-manager-object-selected", row.dataset.objectId === record.id);
});
setSelectedDrawing(record.categoryKey === "drawings" ? record.layer : null);
renderManagerHighlight([record]);
fitLayer(record.layer);
}
function drawingEditToolbar() {
return lmeState.drawControl?._toolbars?.[L.EditToolbar.TYPE] || null;
}
function drawingEditActive() {
return Boolean(drawingEditToolbar()?.enabled?.());
}
function updateDrawingSelectionActions() {
const hasSelection = Boolean(lmeState.drawingSelection?.getLayers?.().length);
lmeState.drawingDeleteButton?.classList.toggle("leaflet-disabled", !hasSelection);
if (lmeState.drawingDeleteButton) {
lmeState.drawingDeleteButton.setAttribute("aria-disabled", String(!hasSelection));
}
}
function setSelectedDrawing(layer) {
if (!lmeState.drawingSelection || drawingEditActive()) {
return;
}
lmeState.drawingSelection.clearLayers();
if (layer) {
lmeState.drawingSelection.addLayer(layer);
}
updateDrawingSelectionActions();
}
function selectDrawingLayer(layer) {
if (drawingEditActive()) {
return;
}
const record = {
id: managedLayerId(layer, "drawings"),
categoryKey: "drawings",
layer,
parent: lmeState.drawings,
source: String(layer?.feature?.properties?._lmeSource || "Progetto corrente")
};
clearManagerHighlight();
lmeState.managerHighlightedObjectId = record.id;
lmeState.managerHighlightedCategoryKey = "drawings";
document.querySelectorAll(".layer-manager-object").forEach(row => {
row.classList.toggle("layer-manager-object-selected", row.dataset.objectId === record.id);
});
setSelectedDrawing(layer);
renderManagerHighlight([record]);
setInfo(`Selezione: ${managedObjectLabel(record, 0)}. Usa la matita per modificare o il cestino per eliminare.`);
}
function bindManagedParentHighlight(row, key, records) {
const summary = row.querySelector(":scope > summary");
row.dataset.highlightKey = key;
@@ -1779,6 +1874,7 @@ function highlightManagedParent(row, key, records) {
lmeState.managerHighlightedParentKey = key;
lmeState.managerHighlightedParentRecords = records;
lmeState.managerHighlightedCategoryKey = records[0]?.categoryKey || null;
setSelectedDrawing(null);
row.classList.add("layer-manager-parent-selected");
renderManagerHighlight(records);
}
@@ -1828,6 +1924,7 @@ function clearManagerHighlight() {
lmeState.managerHighlightedParentRecords = null;
lmeState.managerHoveredParentKey = null;
lmeState.managerHoveredParentRecords = null;
setSelectedDrawing(null);
document.querySelectorAll(".layer-manager-object-selected").forEach(row => {
row.classList.remove("layer-manager-object-selected");
});
@@ -2101,13 +2198,14 @@ function clearImported() {
function addProjectMarker(name) {
const center = lmeState.map.getCenter();
const label = (name || "Nuovo POI").trim();
const marker = L.marker(center, { pane: "drawingsPane", draggable: true }).addTo(lmeState.drawings);
const marker = L.marker(center, { pane: "drawingsPane", draggable: false }).addTo(lmeState.drawings);
marker.lmeManagerLabel = `POI: ${label}`;
marker.feature = {
type: "Feature",
properties: { name: label }
};
marker.bindPopup(`<strong>${escapeHtml(label)}</strong><br>${center.lat.toFixed(6)}, ${center.lng.toFixed(6)}`).openPopup();
bindDrawingLayerSelection(marker);
refreshDrawingMeasurements();
setInfo(`POI aggiunto: ${label}`);
refreshLayerManager();
@@ -2119,8 +2217,8 @@ function configureDrawLabels() {
L.drawLocal.draw.toolbar.buttons.circle = "Misura cerchio";
L.drawLocal.draw.toolbar.buttons.rectangle = "Disegna rettangolo";
L.drawLocal.draw.toolbar.buttons.marker = "Aggiungi POI";
L.drawLocal.edit.toolbar.buttons.edit = "Modifica disegni";
L.drawLocal.edit.toolbar.buttons.remove = "Elimina disegni";
L.drawLocal.edit.toolbar.buttons.edit = "Modifica oggetto selezionato";
L.drawLocal.edit.toolbar.buttons.remove = "Elimina oggetto selezionato";
L.drawLocal.draw.handlers.polygon.tooltip.start = "Tocca il primo vertice dell'area.";
L.drawLocal.draw.handlers.polygon.tooltip.cont = "Tocca per aggiungere un vertice.";
L.drawLocal.draw.handlers.polygon.tooltip.end = "Tocca il primo punto per chiudere l'area.";
@@ -2148,6 +2246,7 @@ function bindDrawingEvents() {
};
setInfo(completedLabels[event.layerType] || "Disegno completato.");
refreshLayerManager();
selectDrawingLayer(event.layer);
});
lmeState.map.on(L.Draw.Event.EDITED, event => {
event.layers.eachLayer(layer => annotateDrawingLayer(layer));
@@ -2155,7 +2254,9 @@ function bindDrawingEvents() {
updateGpsInsideState();
refreshLayerManager();
});
lmeState.map.on(L.Draw.Event.DELETED, () => {
lmeState.map.on(L.Draw.Event.DELETED, event => {
event.layers?.eachLayer(layer => lmeState.drawings.removeLayer(layer));
clearManagerHighlight();
refreshDrawingMeasurements();
updateGpsInsideState();
refreshLayerManager();
@@ -2173,10 +2274,28 @@ function enableDrawTools() {
}
configureDrawLabels();
const orangeArea = { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 };
const orangeArea = {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 4,
fillColor: "#fb923c",
fillOpacity: 0.2
};
lmeState.drawControl = new L.Control.Draw({
position: "topright",
edit: { featureGroup: lmeState.drawings },
edit: {
featureGroup: lmeState.drawingSelection,
remove: false,
edit: {
selectedPathOptions: {
color: "#ff00d4",
fillColor: "#00fff0",
fillOpacity: 0.16,
maintainColor: false
}
}
},
draw: {
circle: { metric: true, feet: false, showRadius: true, shapeOptions: orangeArea },
circlemarker: false,
@@ -2192,14 +2311,72 @@ function enableDrawTools() {
metric: true,
feet: false,
showLength: true,
shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, opacity: 0.95 }
shapeOptions: {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 4,
opacity: 0.95
}
},
rectangle: { metric: true, shapeOptions: orangeArea }
}
});
lmeState.map.addControl(lmeState.drawControl);
installSelectedDrawingDeleteButton();
bindDrawingEvents();
setInfo("Barra strumenti attiva: misure, disegni, POI, modifica e cancellazione.");
setInfo("Barra strumenti attiva: seleziona un disegno sulla mappa, poi usa matita o cestino.");
}
function installSelectedDrawingDeleteButton() {
const toolbar = drawingEditToolbar();
const container = toolbar?._toolbarContainer;
if (!container) {
return;
}
const button = L.DomUtil.create("a", "leaflet-draw-edit-remove", container);
button.href = "#";
button.title = L.drawLocal.edit.toolbar.buttons.remove;
button.setAttribute("role", "button");
button.setAttribute("aria-label", L.drawLocal.edit.toolbar.buttons.remove);
const accessibleLabel = L.DomUtil.create("span", "sr-only", button);
accessibleLabel.textContent = L.drawLocal.edit.toolbar.buttons.remove;
L.DomEvent.on(button, "click", L.DomEvent.stop)
.on(button, "mousedown", L.DomEvent.stopPropagation)
.on(button, "touchstart", L.DomEvent.stopPropagation)
.on(button, "click", removeSelectedDrawing);
lmeState.drawingDeleteButton = button;
updateDrawingSelectionActions();
}
function removeSelectedDrawing() {
const layer = lmeState.drawingSelection?.getLayers?.()[0];
if (!layer) {
setInfo("Seleziona prima il disegno da eliminare toccandolo sulla mappa.");
return;
}
if (drawingEditActive()) {
setInfo("Salva o annulla la modifica prima di eliminare il disegno.");
return;
}
const record = {
id: managedLayerId(layer, "drawings"),
categoryKey: "drawings",
layer,
parent: lmeState.drawings
};
const name = managedObjectLabel(record, 0);
if (!window.confirm(`Eliminare solo '${name}' dal progetto?`)) {
return;
}
lmeState.drawingSelection.clearLayers();
lmeState.drawings.removeLayer(layer);
clearManagedObjectState(record.id);
clearManagerHighlight();
refreshDrawingMeasurements();
updateGpsInsideState();
refreshLayerManager();
setInfo(`'${name}' eliminato dal progetto.`);
}
function startMeasurementDrawing(mode) {
@@ -2217,19 +2394,39 @@ function startMeasurementDrawing(mode) {
showArea: true,
metric: true,
feet: false,
shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
shapeOptions: {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 4,
fillColor: "#fb923c",
fillOpacity: 0.2
}
}),
distance: () => new L.Draw.Polyline(lmeState.map, {
metric: true,
feet: false,
showLength: true,
shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, opacity: 0.95 }
shapeOptions: {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 4,
opacity: 0.95
}
}),
circle: () => new L.Draw.Circle(lmeState.map, {
metric: true,
feet: false,
showRadius: true,
shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
shapeOptions: {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 4,
fillColor: "#fb923c",
fillOpacity: 0.2
}
})
};
const createHandler = handlers[mode];
@@ -2250,6 +2447,9 @@ function startMeasurementDrawing(mode) {
function annotateDrawingLayer(layer, layerType) {
layer.options.pane = "drawingsPane";
if (layer instanceof L.Path) {
layer.options.renderer = lmeState.drawingsRenderer;
}
layer.feature = layer.feature || { type: "Feature", properties: {} };
layer.feature.properties = layer.feature.properties || {};
if (layer instanceof L.Circle) {
@@ -2259,6 +2459,25 @@ function annotateDrawingLayer(layer, layerType) {
layer.feature.properties._lmeShape = layerType;
}
layer.lmeManagerLabel = drawingLayerLabel(layer, layerType);
bindDrawingLayerSelection(layer);
}
function bindDrawingLayerSelection(layer) {
if (layer.lmeDrawingSelectionBound) {
return;
}
layer.lmeDrawingSelectionBound = true;
layer.on("click", event => {
if (drawingEditActive()) {
return;
}
lmeState.ignoreMapClickUntil = Date.now() + 1000;
if (event.originalEvent) {
event.originalEvent._lmeDrawingSelected = true;
L.DomEvent.stopPropagation(event.originalEvent);
}
selectDrawingLayer(layer);
});
}
function setDrawToolsVisible(visible) {
@@ -2278,6 +2497,7 @@ function setDrawToolsVisible(visible) {
});
lmeState.map.removeControl(lmeState.drawControl);
lmeState.drawControl = null;
lmeState.drawingDeleteButton = null;
setInfo("Strumenti di disegno nascosti.");
}
@@ -2292,6 +2512,9 @@ function clearDrawings() {
return;
}
drawingEditToolbar()?.disable?.();
lmeState.drawingSelection?.clearLayers();
clearManagerHighlight();
lmeState.drawings.clearLayers();
clearManagedCategoryState("drawings");
lmeState.drawingMeasurements.clearLayers();
@@ -2674,12 +2897,18 @@ function loadProjectSnapshot(raw, projectName) {
const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), {
pane: "drawingsPane",
style: { color: "#f97316", weight: 3, fillOpacity: 0.16 },
style: {
renderer: lmeState.drawingsRenderer,
color: "#f97316",
weight: 3,
fillOpacity: 0.16
},
pointToLayer: (feature, latlng) => {
const properties = feature?.properties || {};
if (properties._lmeShape === "circle" && Number(properties._lmeRadius) > 0) {
return L.circle(latlng, {
pane: "drawingsPane",
renderer: lmeState.drawingsRenderer,
radius: Number(properties._lmeRadius),
color: "#f97316",
weight: 3,
@@ -2687,7 +2916,7 @@ function loadProjectSnapshot(raw, projectName) {
fillOpacity: 0.2
});
}
return L.marker(latlng, { pane: "drawingsPane", draggable: true });
return L.marker(latlng, { pane: "drawingsPane", draggable: false });
},
onEachFeature: (feature, layer) => {
bindStoredFeaturePopup(feature, layer);
@@ -2922,6 +3151,8 @@ function managedGroupGeoJson(group, categoryKey) {
}
function resetLayerManagerProjectState() {
drawingEditToolbar()?.disable?.();
lmeState.drawingSelection?.clearLayers();
lmeState.managerHiddenObjects.clear();
lmeState.managerHiddenObjectIds.clear();
lmeState.managerHiddenMeasurementSummaryIds.clear();