diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj
index a7a3dfe..e65523d 100644
--- a/LMEMappeCatastaliApp.csproj
+++ b/LMEMappeCatastaliApp.csproj
@@ -28,8 +28,8 @@
com.companyname.lmemappecatastaliapp
- 0.4.1
- 36
+ 0.4.2
+ 37
$(ApplicationDisplayVersion)
diff --git a/README.md b/README.md
index 621c3da..3b5f82f 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,13 @@
App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata per consultare mappe catastali e lavorare sul campo con GPS.
+## Versione 0.4.2
+
+- Nuovo comando `Sposta nel gruppo` visibile accanto a Rinomina e Rimuovi per padri, sottogruppi e singoli oggetti.
+- Scelta della categoria e del sottogruppo di destinazione, con possibilita di creare un nuovo gruppo.
+- I disegni possono essere organizzati in sottogruppi come i dati importati.
+- `Sposta geometria` resta separato e continua a traslare gli oggetti in metri verso nord, sud, est o ovest.
+
## Versione 0.4.1
- Pannelli operativi allineati a destra, con ricerca lasciata nella posizione precedente.
diff --git a/Services/MapExportService.cs b/Services/MapExportService.cs
index 8411c1e..f18cfc9 100644
--- a/Services/MapExportService.cs
+++ b/Services/MapExportService.cs
@@ -85,7 +85,7 @@ public sealed class MapExportService
{
var root = new XElement("gpx",
new XAttribute("version", "1.1"),
- new XAttribute("creator", "LME Mappe Catastali 0.4.1"),
+ new XAttribute("creator", "LME Mappe Catastali 0.4.2"),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
new XElement("metadata", new XElement("name", name)));
foreach (var feature in features.EnumerateArray())
diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css
index dc2e4e4..c9975ea 100644
--- a/wwwroot/css/app.css
+++ b/wwwroot/css/app.css
@@ -573,6 +573,7 @@ input[type="checkbox"] {
}
.layer-rename-button,
+.layer-membership-button,
.layer-remove-button {
width: 100%;
min-height: 28px;
@@ -592,6 +593,16 @@ input[type="checkbox"] {
background: #d9eee7;
}
+.layer-membership-button {
+ border-color: #bfdbfe;
+ background: #eff6ff;
+ color: #1d4ed8;
+}
+
+.layer-membership-button:active {
+ background: #dbeafe;
+}
+
.layer-remove-button {
border-color: #fecaca;
background: #fff1f2;
@@ -827,6 +838,7 @@ input[type="checkbox"] {
}
.layer-manager-object > summary .layer-rename-button,
+.layer-manager-object > summary .layer-membership-button,
.layer-manager-object > summary .layer-remove-button {
min-height: 24px;
font-size: 9px;
diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js
index 16b2633..eac5242 100644
--- a/wwwroot/js/lme-map.js
+++ b/wwwroot/js/lme-map.js
@@ -1184,7 +1184,7 @@ function drawingLayerLabel(layer, layerType) {
function layerManagerCategories() {
return [
{ key: "gpsPosition", label: "Posizione GPS", roots: [lmeState.gpsMarker, lmeState.gpsAccuracy].filter(Boolean) },
- { key: "drawings", label: "Disegni e POI", roots: [lmeState.drawings].filter(Boolean) },
+ { key: "drawings", label: "Disegni e POI", roots: [lmeState.drawings].filter(Boolean), blocks: true },
{ key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) },
{ key: "gpsParcels", label: "Particella rilevata dal GPS", roots: [lmeState.gpsParcelLayer].filter(Boolean) },
{ key: "selectedParcels", label: "Particelle catastali evidenziate", roots: [lmeState.selectedParcelsLayer].filter(Boolean) },
@@ -1382,11 +1382,11 @@ function managedObjectCanRemove(record) {
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(record.categoryKey);
}
-function layerManagerRowActions(renameButton, removeButton) {
- if (!renameButton && !removeButton) {
+function layerManagerRowActions(renameButton, moveButton, removeButton) {
+ if (!renameButton && !moveButton && !removeButton) {
return "";
}
- return `${renameButton}${removeButton}`;
+ return `${renameButton}${moveButton}${removeButton}`;
}
const layerManagerPalettes = [
@@ -1472,7 +1472,87 @@ function removeManagedRecords(records, subject) {
setInfo(`${records.length === 1 ? "Oggetto rimosso" : `${records.length} oggetti rimossi`} dal progetto.`);
}
-function moveManagedRecordsToCategory(records, targetCategory) {
+function managedMoveDestinations() {
+ return layerManagerCategories()
+ .filter(category => ["drawings", "imported"].includes(category.key))
+ .map(category => {
+ const records = managedRecordsForCategory(category);
+ const groups = new Map();
+ records.forEach(record => {
+ if (!groups.has(record.source)) {
+ groups.set(record.source, managedBlockName(record.source, [record]));
+ }
+ });
+ if (!groups.size) {
+ groups.set("Progetto corrente", "Gruppo principale");
+ }
+ return {
+ key: category.key,
+ name: managedCategoryName(category, records),
+ groups: [...groups].map(([source, name]) => ({ source, name }))
+ };
+ });
+}
+
+function showMoveManagedRecordsDialog(records, subject) {
+ if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return; }
+ if (!records.length || records.some(record => !["drawings", "imported"].includes(record.categoryKey))) {
+ setInfo("Questo oggetto o gruppo non puo essere spostato.");
+ return;
+ }
+ if (records.some(record => record.layer?.feature?.properties?._lmeLocked)) {
+ setInfo("Sblocca prima tutti gli oggetti da spostare.");
+ return;
+ }
+ const destinations = managedMoveDestinations();
+ const categoryOptions = destinations.map(destination =>
+ ``).join("");
+ const dialog = showAppDialog("Sposta nel gruppo", `
`);
+ const form = dialog.querySelector("form");
+ const categorySelect = form.elements.category;
+ const groupSelect = form.elements.group;
+ const newGroupInput = form.elements.newGroup;
+ const currentCategory = records.every(record => record.categoryKey === records[0].categoryKey)
+ ? records[0].categoryKey
+ : "drawings";
+ categorySelect.value = currentCategory;
+ const refreshGroups = () => {
+ const destination = destinations.find(item => item.key === categorySelect.value);
+ groupSelect.innerHTML = (destination?.groups || []).map(group =>
+ ``).join("");
+ const currentSource = records.every(record => record.source === records[0].source)
+ ? records[0].source
+ : "";
+ if (destination?.key === currentCategory && currentSource
+ && destination.groups.some(group => group.source === currentSource)) {
+ groupSelect.value = currentSource;
+ }
+ };
+ categorySelect.addEventListener("change", refreshGroups);
+ refreshGroups();
+ dialog.querySelector("[data-cancel]").addEventListener("click", () => dialog.remove());
+ form.addEventListener("submit", event => {
+ event.preventDefault();
+ const destination = destinations.find(item => item.key === categorySelect.value);
+ if (!destination) return;
+ const requestedName = newGroupInput.value.trim().slice(0, 120);
+ const existingByName = requestedName
+ ? destination.groups.find(group => group.name.toLocaleLowerCase("it") === requestedName.toLocaleLowerCase("it"))
+ : null;
+ const selectedGroup = destination.groups.find(group => group.source === groupSelect.value);
+ const target = existingByName || (requestedName
+ ? { source: `Gruppo ${requestedName}`, name: requestedName }
+ : selectedGroup);
+ if (!target) {
+ setInfo("Scegli un gruppo di destinazione o inserisci il nome di un nuovo gruppo.");
+ return;
+ }
+ dialog.remove();
+ moveManagedRecordsToGroup(records, destination.key, target.source, target.name);
+ });
+}
+
+function moveManagedRecordsToGroup(records, targetCategory, targetSource, targetGroupName) {
if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return; }
const movable = records.filter(record => ["drawings", "imported"].includes(record.categoryKey));
if (!movable.length || !["drawings", "imported"].includes(targetCategory)) {
@@ -1488,8 +1568,10 @@ function moveManagedRecordsToCategory(records, targetCategory) {
const feature = record.layer.toGeoJSON();
feature.properties = {
...(feature.properties || {}),
- _lmeSource: feature.properties?._lmeSource || "Progetto corrente"
+ _lmeSource: targetSource,
+ _lmeGroupName: targetGroupName
};
+ delete feature.properties._lmeManagerId;
return feature;
});
movable.forEach(record => {
@@ -1505,8 +1587,9 @@ function moveManagedRecordsToCategory(records, targetCategory) {
refreshImportedMeasurements();
refreshDrawingMeasurements();
refreshLayerManager();
- finishMapChange(`Spostati ${features.length} oggetti in ${targetCategory === "drawings" ? "Disegni e POI" : "Progetti e dati importati"}.`);
- setInfo(`${features.length} oggetti spostati in ${targetCategory === "drawings" ? "Disegni e POI" : "Progetti e dati importati"}.`);
+ const categoryName = targetCategory === "drawings" ? "Disegni e POI" : "Progetti e dati importati";
+ finishMapChange(`Spostati ${features.length} oggetti in ${categoryName} > ${targetGroupName}.`);
+ setInfo(`${features.length} oggetti spostati in ${categoryName} > ${targetGroupName}.`);
}
function managedObjectDetails(layer) {
@@ -1614,7 +1697,10 @@ function renderLayerManagerCategory(category, records) {
const removeButton = managedCategoryCanRemove(category, records)
? ``
: "";
- const rowActions = layerManagerRowActions(renameButton, removeButton);
+ const moveButton = records.length && ["drawings", "imported"].includes(category.key)
+ ? ``
+ : "";
+ const rowActions = layerManagerRowActions(renameButton, moveButton, removeButton);
group.classList.toggle("layer-manager-with-actions", Boolean(rowActions));
const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
const categoryVisible = !lmeState.managerHiddenCategories.has(category.key)
@@ -1641,6 +1727,11 @@ function renderLayerManagerCategory(category, records) {
event.stopPropagation();
removeManagedRecords(records, `il gruppo '${categoryName}'`);
});
+ group.querySelector(".layer-membership-button")?.addEventListener("click", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ showMoveManagedRecordsDialog(records, `il gruppo '${categoryName}'`);
+ });
bindManagedParentHighlight(group, `category:${category.key}`, records);
const children = group.querySelector(".layer-manager-children");
if (records.length) children.appendChild(renderManagedGroupTools(records));
@@ -1674,6 +1765,7 @@ function renderLayerManagerBlock(category, source, records) {
const blockName = managedBlockName(source, records);
const rowActions = layerManagerRowActions(
``,
+ ``,
``);
block.classList.add("layer-manager-with-actions");
const blockLocked = records.length > 0 && records.every(record => Boolean(record.layer?.feature?.properties?._lmeLocked));
@@ -1700,6 +1792,11 @@ function renderLayerManagerBlock(category, source, records) {
event.stopPropagation();
removeManagedRecords(records, `il gruppo '${blockName}'`);
});
+ block.querySelector(".layer-membership-button").addEventListener("click", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ showMoveManagedRecordsDialog(records, `il gruppo '${blockName}'`);
+ });
bindManagedParentHighlight(block, `block:${category.key}:${source}`, records);
const children = block.querySelector(".layer-manager-children");
children.appendChild(renderManagedGroupTools(records));
@@ -1724,7 +1821,10 @@ function renderLayerManagerObject(record, index) {
const removeButton = managedObjectCanRemove(record)
? ``
: "";
- const rowActions = layerManagerRowActions(renameButton, removeButton);
+ const moveButton = ["drawings", "imported"].includes(record.categoryKey)
+ ? ``
+ : "";
+ const rowActions = layerManagerRowActions(renameButton, moveButton, removeButton);
object.classList.toggle("layer-manager-with-actions", Boolean(rowActions));
const objectName = managedObjectLabel(record, index);
const locked = Boolean(record.layer?.feature?.properties?._lmeLocked);
@@ -1743,6 +1843,11 @@ function renderLayerManagerObject(record, index) {
event.stopPropagation();
removeManagedRecords([record], `'${objectName}'`);
});
+ object.querySelector(".layer-membership-button")?.addEventListener("click", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ showMoveManagedRecordsDialog([record], `'${objectName}'`);
+ });
object.querySelector(".layer-inline-lock")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
@@ -1786,7 +1891,7 @@ function renderLayerSelectionToolbar() {
toolbar.innerHTML = `
${selectedCount} selezionati
-
+
`;
@@ -1818,17 +1923,11 @@ function renderManagedGroupTools(records) {
const tools = document.createElement("div");
tools.className = "layer-group-tools";
const properties = records[0]?.layer?.feature?.properties || {};
- const sourceCategory = records[0]?.categoryKey;
- const membershipAction = sourceCategory === "drawings"
- ? ``
- : sourceCategory === "imported"
- ? ``
- : "";
tools.innerHTML = `Azioni gruppo${escapeHtml(managedGroupTotals(records))}
- ${membershipAction}
`;
+ `;
const inputs = tools.querySelectorAll("input");
inputs.forEach(input => input.addEventListener("change", () => {
applyManagedStyle(records, { color: inputs[0].value, weight: Number(inputs[1].value), opacity: Number(inputs[2].value) });
@@ -1845,8 +1944,6 @@ function renderManagedGroupTools(records) {
const visible = !records.every(record => Boolean(record.layer?.feature?.properties?._lmeLabelVisible));
updateManagedProperties(records, properties => { properties._lmeLabelVisible = visible; });
});
- tools.querySelector("[data-group-action='membership']")?.addEventListener("click", () =>
- moveManagedRecordsToCategory(records, sourceCategory === "drawings" ? "imported" : "drawings"));
return tools;
}
@@ -1869,11 +1966,6 @@ function renderManagedObjectTools(record) {
const properties = record.layer.feature?.properties || (record.layer.feature = { type: "Feature", properties: {} }).properties;
const selected = lmeState.managerSelectedObjectIds.has(record.id);
const locked = Boolean(properties._lmeLocked);
- const membershipAction = record.categoryKey === "drawings"
- ? ``
- : record.categoryKey === "imported"
- ? ``
- : "";
const tools = document.createElement("section");
tools.className = "layer-object-tools";
tools.innerHTML = `
@@ -1888,7 +1980,7 @@ function renderManagedObjectTools(record) {
${properties._lmePhoto ? `
` : ""}
- ${membershipAction}
`;
+ `;
tools.querySelector("[data-field='selected']").addEventListener("change", event => {
if (event.target.checked) lmeState.managerSelectedObjectIds.add(record.id);
else lmeState.managerSelectedObjectIds.delete(record.id);
@@ -1918,8 +2010,6 @@ function renderManagedObjectTools(record) {
tools.querySelector("[data-action='inspect']").addEventListener("click", () => showCoordinateInspectorForRecord(record));
tools.querySelector("[data-action='move']").addEventListener("click", () => moveManagedRecords([record]));
tools.querySelector("[data-action='buffer']").addEventListener("click", () => bufferManagedRecords([record]));
- tools.querySelector("[data-action='membership']")?.addEventListener("click", () =>
- moveManagedRecordsToCategory([record], record.categoryKey === "drawings" ? "imported" : "drawings"));
return tools;
}