diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj
index 242c53d..3c823c8 100644
--- a/LMEMappeCatastaliApp.csproj
+++ b/LMEMappeCatastaliApp.csproj
@@ -28,8 +28,8 @@
com.companyname.lmemappecatastaliapp
- 0.2.18
- 22
+ 0.2.19
+ 23
None
diff --git a/Services/AzureMapsService.cs b/Services/AzureMapsService.cs
index 4ad80f3..734efb2 100644
--- a/Services/AzureMapsService.cs
+++ b/Services/AzureMapsService.cs
@@ -13,7 +13,7 @@ public sealed class AzureMapsService
public AzureMapsService()
{
- _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.18");
+ _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.19");
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json");
}
diff --git a/Services/NetworkLocationService.cs b/Services/NetworkLocationService.cs
index f573df6..c789f6f 100644
--- a/Services/NetworkLocationService.cs
+++ b/Services/NetworkLocationService.cs
@@ -13,7 +13,7 @@ public sealed class NetworkLocationService
public NetworkLocationService()
{
- httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.18");
+ httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.19");
}
public async Task GetApproximateLocationAsync(
diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css
index c5f0cb0..65f06da 100644
--- a/wwwroot/css/app.css
+++ b/wwwroot/css/app.css
@@ -274,7 +274,7 @@ input[type="checkbox"] {
.layer-manager-block > summary,
.layer-manager-object > summary {
display: grid;
- grid-template-columns: 28px 20px minmax(0, 1fr) auto;
+ grid-template-columns: 28px 20px minmax(0, 1fr) auto auto;
align-items: center;
gap: 6px;
min-height: 38px;
@@ -302,6 +302,29 @@ input[type="checkbox"] {
cursor: grab;
}
+.layer-move-buttons {
+ display: inline-grid;
+ grid-template-columns: 24px 24px;
+ gap: 2px;
+}
+
+.layer-move-buttons button {
+ width: 24px;
+ height: 28px;
+ padding: 0;
+ border: 1px solid #cbd5da;
+ border-radius: 4px;
+ background: #f6f8f9;
+ color: var(--ink);
+ font-size: 16px;
+ font-weight: 800;
+ line-height: 1;
+}
+
+.layer-move-buttons button:active {
+ background: #dce8ec;
+}
+
.layer-manager-title {
min-width: 0;
overflow-wrap: anywhere;
diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js
index 5db60b4..0ac3f42 100644
--- a/wwwroot/js/lme-map.js
+++ b/wwwroot/js/lme-map.js
@@ -49,6 +49,8 @@ const lmeState = {
azureMapsKey: null,
azureAttribution: null,
managerHighlight: null,
+ managerHighlightedObjectId: null,
+ managerHighlightedCategoryKey: null,
managerHiddenObjects: new Map(),
managerHiddenObjectIds: new Set(),
managerHiddenCategories: new Set(),
@@ -1181,6 +1183,7 @@ function refreshLayerManager() {
});
applyManagedVisualOrder(container);
bindLayerManagerDrag(container);
+ bindLayerManagerMoveButtons(container);
}
function renderLayerManagerCategory(category, records) {
@@ -1188,10 +1191,13 @@ function renderLayerManagerCategory(category, records) {
group.className = "layer-manager-group";
group.dataset.dragType = "category";
group.dataset.categoryKey = category.key;
- const visible = !lmeState.managerHiddenCategories.has(category.key)
- && (!category.roots.length || category.roots.every(layer => lmeState.map.hasLayer(layer)));
- group.innerHTML = `${escapeHtml(category.label)}${records.length}`;
+ const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
+ const categoryVisible = !lmeState.managerHiddenCategories.has(category.key)
+ && (!category.roots.length || category.roots.some(layer => lmeState.map.hasLayer(layer)));
+ const visible = categoryVisible;
+ group.innerHTML = `${escapeHtml(category.label)}${layerManagerMoveButtons("gruppo")}${records.length}`;
const checkbox = group.querySelector("input");
+ checkbox.indeterminate = categoryVisible && records.length > 0 && visibleCount < records.length;
checkbox.addEventListener("click", event => event.stopPropagation());
checkbox.addEventListener("change", () => setManagedCategoryVisible(category, checkbox.checked));
const children = group.querySelector(".layer-manager-children");
@@ -1217,7 +1223,7 @@ function renderLayerManagerBlock(category, source, records) {
block.dataset.blockKey = source;
const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
const visible = visibleCount === records.length;
- block.innerHTML = `${escapeHtml(source)}${records.length}`;
+ block.innerHTML = `${escapeHtml(source)}${layerManagerMoveButtons("blocco")}${records.length}`;
const checkbox = block.querySelector("input");
checkbox.indeterminate = visibleCount > 0 && !visible;
checkbox.addEventListener("click", event => event.stopPropagation());
@@ -1236,7 +1242,7 @@ function renderLayerManagerObject(record, index) {
object.dataset.dragType = "object";
object.dataset.objectId = record.id;
const visible = !lmeState.managerHiddenObjectIds.has(record.id);
- object.innerHTML = `${escapeHtml(managedObjectLabel(record, index))}›`;
+ object.innerHTML = `${escapeHtml(managedObjectLabel(record, index))}${layerManagerMoveButtons("oggetto")}›`;
const checkbox = object.querySelector("input");
checkbox.addEventListener("click", event => event.stopPropagation());
checkbox.addEventListener("change", () => setManagedObjectVisible(record, checkbox.checked, true));
@@ -1267,7 +1273,10 @@ function setManagedCategoryVisible(category, visible) {
if (visible) {
lmeState.managerHiddenCategories.delete(category.key);
category.roots.forEach(layer => {
- if (layer && !lmeState.map.hasLayer(layer)) {
+ const objectId = managedLayerId(layer, category.key);
+ if (layer
+ && !lmeState.managerHiddenObjectIds.has(objectId)
+ && !lmeState.map.hasLayer(layer)) {
layer.addTo(lmeState.map);
}
});
@@ -1278,15 +1287,23 @@ function setManagedCategoryVisible(category, visible) {
lmeState.map.removeLayer(layer);
}
});
+ if (lmeState.managerHighlightedCategoryKey === category.key) {
+ clearManagerHighlight();
+ }
}
saveLayerManagerState();
+ refreshImportedMeasurements();
+ refreshDrawingMeasurements();
+ refreshLayerManager();
}
function setManagedObjectVisible(record, visible, refresh = true) {
if (visible) {
const hidden = lmeState.managerHiddenObjects.get(record.id);
if (hidden) {
- hidden.parent.addLayer(hidden.layer);
+ if (hidden.parent !== lmeState.map || managedCategoryEnabled(record.categoryKey)) {
+ hidden.parent.addLayer(hidden.layer);
+ }
lmeState.managerHiddenObjects.delete(record.id);
}
lmeState.managerHiddenObjectIds.delete(record.id);
@@ -1294,6 +1311,9 @@ function setManagedObjectVisible(record, visible, refresh = true) {
lmeState.managerHiddenObjects.set(record.id, record);
lmeState.managerHiddenObjectIds.add(record.id);
record.parent.removeLayer(record.layer);
+ if (lmeState.managerHighlightedObjectId === record.id) {
+ clearManagerHighlight();
+ }
}
saveLayerManagerState();
refreshImportedMeasurements();
@@ -1303,8 +1323,29 @@ function setManagedObjectVisible(record, visible, refresh = true) {
}
}
+function managedCategoryEnabled(categoryKey) {
+ if (lmeState.managerHiddenCategories.has(categoryKey)) {
+ return false;
+ }
+ if (categoryKey === "gpsPosition") {
+ return lmeState.gpsPositionVisible;
+ }
+ if (categoryKey === "imported") {
+ return lmeState.importedVisible;
+ }
+ if (categoryKey === "importedSheets") {
+ return lmeState.importedSheetsVisible;
+ }
+ if (categoryKey === "selectedParcels") {
+ return lmeState.selectedParcelsVisible;
+ }
+ return true;
+}
+
function highlightManagedObject(record) {
lmeState.managerHighlight.clearLayers();
+ lmeState.managerHighlightedObjectId = record.id;
+ lmeState.managerHighlightedCategoryKey = record.categoryKey;
const geojson = record.layer.toGeoJSON?.();
if (geojson) {
L.geoJSON(geojson, {
@@ -1325,6 +1366,16 @@ function highlightManagedObject(record) {
fitLayer(record.layer);
}
+function clearManagerHighlight() {
+ lmeState.managerHighlight?.clearLayers();
+ lmeState.managerHighlightedObjectId = null;
+ lmeState.managerHighlightedCategoryKey = null;
+}
+
+function layerManagerMoveButtons(label) {
+ return ``;
+}
+
function bindLayerManagerDrag(container) {
container.querySelectorAll(".layer-drag-handle").forEach(handle => {
handle.addEventListener("pointerdown", event => {
@@ -1335,6 +1386,7 @@ function bindLayerManagerDrag(container) {
row.classList.add("layer-manager-dragging");
handle.setPointerCapture?.(event.pointerId);
const move = moveEvent => {
+ moveEvent.preventDefault();
const target = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY)?.closest(`[data-drag-type="${row.dataset.dragType}"]`);
if (!target || target === row || target.parentElement !== list) {
return;
@@ -1344,14 +1396,37 @@ function bindLayerManagerDrag(container) {
};
const end = () => {
row.classList.remove("layer-manager-dragging");
- handle.removeEventListener("pointermove", move);
- handle.removeEventListener("pointerup", end);
- handle.removeEventListener("pointercancel", end);
+ handle.releasePointerCapture?.(event.pointerId);
+ document.removeEventListener("pointermove", move);
+ document.removeEventListener("pointerup", end);
+ document.removeEventListener("pointercancel", end);
applyLayerManagerDomOrder(container);
};
- handle.addEventListener("pointermove", move);
- handle.addEventListener("pointerup", end);
- handle.addEventListener("pointercancel", end);
+ document.addEventListener("pointermove", move, { passive: false });
+ document.addEventListener("pointerup", end);
+ document.addEventListener("pointercancel", end);
+ });
+ });
+}
+
+function bindLayerManagerMoveButtons(container) {
+ container.querySelectorAll(".layer-move-up, .layer-move-down").forEach(button => {
+ button.addEventListener("click", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ const row = button.closest("[data-drag-type]");
+ const sibling = button.classList.contains("layer-move-up")
+ ? row.previousElementSibling
+ : row.nextElementSibling;
+ if (!sibling || sibling.dataset.dragType !== row.dataset.dragType) {
+ return;
+ }
+ if (button.classList.contains("layer-move-up")) {
+ row.parentElement.insertBefore(row, sibling);
+ } else {
+ row.parentElement.insertBefore(sibling, row);
+ }
+ applyLayerManagerDomOrder(container);
});
});
}
@@ -1785,7 +1860,7 @@ function renderMeasurements(sourceGroup, targetGroup, visible, sourceType) {
return;
}
targetGroup.clearLayers();
- if (!visible) {
+ if (!visible || !lmeState.map.hasLayer(sourceGroup)) {
return;
}
@@ -2343,7 +2418,7 @@ function resetLayerManagerProjectState() {
lmeState.managerHiddenObjectIds.clear();
lmeState.managerHiddenCategories.clear();
lmeState.managerObjectOrder = {};
- lmeState.managerHighlight?.clearLayers();
+ clearManagerHighlight();
}
function clearManagedCategoryState(categoryKey) {
@@ -2809,11 +2884,11 @@ function onGpsPosition(position) {
}
function updateGpsPositionLayers(latlng, accuracy) {
- if (!lmeState.gpsPositionVisible) {
- return;
- }
+ let created = false;
if (!lmeState.gpsMarker) {
lmeState.gpsMarker = L.marker(latlng, { pane: "gpsPositionPane", title: "La tua posizione" });
+ lmeState.gpsMarker.lmeManagerId = "gps-position-marker";
+ lmeState.gpsMarker.lmeManagerLabel = "Posizione GPS";
lmeState.gpsAccuracy = L.circle(latlng, {
pane: "gpsPositionPane",
radius: accuracy,
@@ -2822,15 +2897,33 @@ function updateGpsPositionLayers(latlng, accuracy) {
fillColor: "#60a5fa",
fillOpacity: 0.16
});
+ lmeState.gpsAccuracy.lmeManagerId = "gps-accuracy-circle";
+ lmeState.gpsAccuracy.lmeManagerLabel = "Cerchio precisione GPS";
+ created = true;
} else {
lmeState.gpsMarker.setLatLng(latlng);
lmeState.gpsAccuracy.setLatLng(latlng).setRadius(accuracy);
}
- if (!lmeState.map.hasLayer(lmeState.gpsMarker)) {
+ const categoryVisible = lmeState.gpsPositionVisible
+ && !lmeState.managerHiddenCategories.has("gpsPosition");
+ if (categoryVisible
+ && !lmeState.managerHiddenObjectIds.has("gps-position-marker")
+ && !lmeState.map.hasLayer(lmeState.gpsMarker)) {
lmeState.gpsMarker.addTo(lmeState.map);
+ } else if ((!categoryVisible || lmeState.managerHiddenObjectIds.has("gps-position-marker"))
+ && lmeState.map.hasLayer(lmeState.gpsMarker)) {
+ lmeState.map.removeLayer(lmeState.gpsMarker);
}
- if (!lmeState.map.hasLayer(lmeState.gpsAccuracy)) {
+ if (categoryVisible
+ && !lmeState.managerHiddenObjectIds.has("gps-accuracy-circle")
+ && !lmeState.map.hasLayer(lmeState.gpsAccuracy)) {
lmeState.gpsAccuracy.addTo(lmeState.map);
+ } else if ((!categoryVisible || lmeState.managerHiddenObjectIds.has("gps-accuracy-circle"))
+ && lmeState.map.hasLayer(lmeState.gpsAccuracy)) {
+ lmeState.map.removeLayer(lmeState.gpsAccuracy);
+ }
+ if (created) {
+ refreshLayerManager();
}
}