Compare commits

..
4 Commits
Author SHA1 Message Date
livio 87304b1568 Rimuovi oggetti e gruppi dal progetto 2026-09-24 08:50:31 +02:00
livio a23eff6124 Rinomina ed evidenzia i gruppi mappa 2026-09-24 08:38:07 +02:00
livio 4da47a8835 Consenti di rinominare gli oggetti mappa 2026-09-24 08:15:58 +02:00
livio fc303e2555 Separa le misure per ogni oggetto 2026-09-22 16:56:17 +02:00
7 changed files with 643 additions and 45 deletions

No files matched your search

+19 -1
View File
@@ -472,6 +472,7 @@
private bool DrawToolsVisible { get; set; } private bool DrawToolsVisible { get; set; }
private bool TrailEnabled { get; set; } private bool TrailEnabled { get; set; }
private bool LayerPanelOpen { get; set; } private bool LayerPanelOpen { get; set; }
private DotNetObjectReference<Home>? MapCallbackReference { get; set; }
private bool IncludeCadastralParcels { get; set; } = true; private bool IncludeCadastralParcels { get; set; } = true;
private bool IncludeKmlSheetLayer { get; set; } private bool IncludeKmlSheetLayer { get; set; }
private bool ShowImportedSheetLayer { get; set; } private bool ShowImportedSheetLayer { get; set; }
@@ -570,7 +571,8 @@
{ {
if (firstRender) if (firstRender)
{ {
await JS.InvokeVoidAsync("lmeMap.init"); MapCallbackReference = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("lmeMap.init", MapCallbackReference);
await ApplyFlagPreferencesToMapAsync(); await ApplyFlagPreferencesToMapAsync();
await CenterOnInitialLocationAsync(); await CenterOnInitialLocationAsync();
await MigrateLegacyProjectAsync(); await MigrateLegacyProjectAsync();
@@ -1047,6 +1049,21 @@
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements); await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
} }
[JSInvokable]
public Task SetMeasurementsFromLayerManager(string category, bool enabled)
{
if (category == "imported")
{
ShowImportedMeasurements = enabled;
}
else if (category == "drawings")
{
ShowDrawingMeasurements = enabled;
}
SaveFlagPreferences();
return InvokeAsync(StateHasChanged);
}
private async Task ToggleDrawTools() private async Task ToggleDrawTools()
{ {
DrawToolsVisible = !DrawToolsVisible; DrawToolsVisible = !DrawToolsVisible;
@@ -1503,6 +1520,7 @@
LocationService.StopTracking(); LocationService.StopTracking();
GpsLookupCancellation?.Cancel(); GpsLookupCancellation?.Cancel();
GpsLookupCancellation?.Dispose(); GpsLookupCancellation?.Dispose();
MapCallbackReference?.Dispose();
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
} }
+2 -2
View File
@@ -28,8 +28,8 @@
<ApplicationId>com.companyname.lmemappecatastaliapp</ApplicationId> <ApplicationId>com.companyname.lmemappecatastaliapp</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>0.2.21</ApplicationDisplayVersion> <ApplicationDisplayVersion>0.2.25</ApplicationDisplayVersion>
<ApplicationVersion>25</ApplicationVersion> <ApplicationVersion>29</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged --> <!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType> <WindowsPackageType>None</WindowsPackageType>
+1 -1
View File
@@ -13,7 +13,7 @@ public sealed class AzureMapsService
public AzureMapsService() public AzureMapsService()
{ {
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.21"); _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.22");
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json"); _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json");
} }
+27 -1
View File
@@ -78,6 +78,8 @@ public sealed class MapExportService
BuildStyle("drawing", "ff1679f9", "333c92fb"), BuildStyle("drawing", "ff1679f9", "333c92fb"),
BuildStyle("poi", "ffff7a00", "33ff7a00"), BuildStyle("poi", "ffff7a00", "33ff7a00"),
BuildStyle("gps-trail", "ff00a5ff", "2200a5ff")); BuildStyle("gps-trail", "ff00a5ff", "2200a5ff"));
var categoryFolders = new Dictionary<string, XElement>(StringComparer.Ordinal);
var groupFolders = new Dictionary<(string Category, string Group), XElement>();
foreach (var feature in features.EnumerateArray()) foreach (var feature in features.EnumerateArray())
{ {
@@ -102,7 +104,31 @@ public sealed class MapExportService
placemark.Add(extendedData); placemark.Add(extendedData);
} }
placemark.Add(BuildGeometry(geometry)); placemark.Add(BuildGeometry(geometry));
document.Add(placemark); var categoryName = GetString(properties, "_lmeCategoryName")?.Trim();
var groupName = GetString(properties, "_lmeGroupName")?.Trim();
var parent = document;
if (!string.IsNullOrWhiteSpace(categoryName))
{
if (!categoryFolders.TryGetValue(categoryName, out var categoryFolder))
{
categoryFolder = new XElement(Kml + "Folder", new XElement(Kml + "name", categoryName));
categoryFolders[categoryName] = categoryFolder;
document.Add(categoryFolder);
}
parent = categoryFolder;
}
if (!string.IsNullOrWhiteSpace(groupName))
{
var key = (categoryName ?? string.Empty, groupName);
if (!groupFolders.TryGetValue(key, out var groupFolder))
{
groupFolder = new XElement(Kml + "Folder", new XElement(Kml + "name", groupName));
groupFolders[key] = groupFolder;
parent.Add(groupFolder);
}
parent = groupFolder;
}
parent.Add(placemark);
} }
return new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement(Kml + "kml", document)).ToString(); return new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement(Kml + "kml", document)).ToString();
+1 -1
View File
@@ -13,7 +13,7 @@ public sealed class NetworkLocationService
public NetworkLocationService() public NetworkLocationService()
{ {
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.21"); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.22");
} }
public async Task<NetworkLocationSnapshot?> GetApproximateLocationAsync( public async Task<NetworkLocationSnapshot?> GetApproximateLocationAsync(
+93 -2
View File
@@ -178,7 +178,7 @@ input[type="checkbox"] {
right: 12px; right: 12px;
z-index: 940; z-index: 940;
display: grid; display: grid;
width: min(320px, calc(100% - 24px)); width: min(760px, 62vw, calc(100% - 24px));
max-height: calc(100% - 152px); max-height: calc(100% - 152px);
overflow-y: auto; overflow-y: auto;
border: 1px solid var(--line); border: 1px solid var(--line);
@@ -283,6 +283,10 @@ input[type="checkbox"] {
list-style: none; list-style: none;
} }
.layer-manager-with-actions > summary {
grid-template-columns: 28px 20px minmax(0, 1fr) auto auto auto;
}
.layer-manager-group > summary::-webkit-details-marker, .layer-manager-group > summary::-webkit-details-marker,
.layer-manager-block > summary::-webkit-details-marker, .layer-manager-block > summary::-webkit-details-marker,
.layer-manager-object > summary::-webkit-details-marker { .layer-manager-object > summary::-webkit-details-marker {
@@ -302,6 +306,39 @@ input[type="checkbox"] {
cursor: grab; cursor: grab;
} }
.layer-row-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 4px;
}
.layer-rename-button,
.layer-remove-button {
min-height: 28px;
padding: 0 8px;
border: 1px solid #bfd4cd;
border-radius: 4px;
background: #eef8f5;
color: var(--accent-strong);
font-size: 10px;
font-weight: 800;
}
.layer-rename-button:active {
background: #d9eee7;
}
.layer-remove-button {
border-color: #fecaca;
background: #fff1f2;
color: #b42318;
}
.layer-remove-button:active {
background: #ffe4e6;
}
.layer-move-buttons { .layer-move-buttons {
display: inline-grid; display: inline-grid;
grid-template-columns: 24px 24px; grid-template-columns: 24px 24px;
@@ -349,6 +386,27 @@ input[type="checkbox"] {
font-size: 10px; font-size: 10px;
} }
.layer-manager-suboption {
display: grid;
grid-template-columns: 18px minmax(0, 1fr);
align-items: center;
gap: 6px;
min-height: 30px;
color: var(--ink);
font-size: 11px;
font-weight: 700;
}
.layer-manager-suboption input {
margin: 0;
}
.layer-manager-info {
display: grid;
gap: 2px;
padding: 3px 0 0 24px;
}
.layer-manager-dragging { .layer-manager-dragging {
opacity: 0.55; opacity: 0.55;
outline: 2px solid #2563eb; outline: 2px solid #2563eb;
@@ -359,6 +417,12 @@ input[type="checkbox"] {
box-shadow: inset 3px 0 #ff00d4; box-shadow: inset 3px 0 #ff00d4;
} }
.layer-manager-parent-highlighted > summary,
.layer-manager-parent-selected > summary {
background: #fff0fb;
box-shadow: inset 3px 0 #ff00d4;
}
.gps-overlay { .gps-overlay {
position: absolute; position: absolute;
top: 72px; top: 72px;
@@ -868,10 +932,31 @@ footer {
.map-layer-panel { .map-layer-panel {
top: 68px; top: 68px;
right: 8px; right: 8px;
width: min(320px, calc(100% - 16px)); width: calc(100% - 16px);
max-height: calc(100% - 144px); max-height: calc(100% - 144px);
} }
.layer-manager-with-actions > summary {
grid-template-columns: 28px 20px minmax(0, 1fr) auto;
}
.layer-manager-with-actions > summary > .layer-row-actions {
grid-row: 2;
grid-column: 3;
justify-content: flex-start;
}
.layer-manager-with-actions > summary > .layer-move-buttons {
grid-row: 1;
grid-column: 4;
}
.layer-manager-with-actions > summary > .layer-manager-count {
grid-row: 2;
grid-column: 4;
justify-self: end;
}
.tool-sheet { .tool-sheet {
top: 10%; top: 10%;
bottom: var(--nav-height); bottom: var(--nav-height);
@@ -930,6 +1015,12 @@ footer {
} }
} }
@media (min-width: 701px) and (max-width: 1100px) {
.map-layer-panel {
width: min(680px, calc(100% - 24px));
}
}
@media (max-width: 380px) { @media (max-width: 380px) {
.app-identity span { .app-identity span {
display: none; display: none;
+500 -37
View File
@@ -51,12 +51,21 @@ const lmeState = {
managerHighlight: null, managerHighlight: null,
managerHighlightedObjectId: null, managerHighlightedObjectId: null,
managerHighlightedCategoryKey: null, managerHighlightedCategoryKey: null,
managerHighlightedParentKey: null,
managerHighlightedParentRecords: null,
managerHoveredParentKey: null,
managerHoveredParentRecords: null,
managerOutsideSelectionBound: false, managerOutsideSelectionBound: false,
managerHiddenObjects: new Map(), managerHiddenObjects: new Map(),
managerHiddenObjectIds: new Set(), managerHiddenObjectIds: new Set(),
managerHiddenMeasurementSummaryIds: new Set(),
managerHiddenMeasurementSideIds: new Set(),
managerHiddenCategories: new Set(), managerHiddenCategories: new Set(),
managerRemovedCategories: new Set(),
managerObjectOrder: {}, managerObjectOrder: {},
managerExpandedRows: new Set() managerCategoryNames: {},
managerExpandedRows: new Set(),
dotNetReference: null
}; };
// AdE publishes the cadastral WMS in ETRS89. WMS 1.1.1 keeps the longitude, // AdE publishes the cadastral WMS in ETRS89. WMS 1.1.1 keeps the longitude,
@@ -109,7 +118,10 @@ window.lmeMap = {
showInfo: setInfo showInfo: setInfo
}; };
function init() { function init(dotNetReference) {
if (dotNetReference) {
lmeState.dotNetReference = dotNetReference;
}
if (lmeState.map) { if (lmeState.map) {
return; return;
} }
@@ -217,11 +229,13 @@ function init() {
}); });
if (!lmeState.managerOutsideSelectionBound) { if (!lmeState.managerOutsideSelectionBound) {
document.addEventListener("pointerdown", event => { document.addEventListener("pointerdown", event => {
if (!lmeState.managerHighlightedObjectId) { if (!lmeState.managerHighlightedObjectId && !lmeState.managerHighlightedParentKey) {
return; return;
} }
const selectedRow = event.target?.closest?.(".layer-manager-object"); const selectedObject = event.target?.closest?.(".layer-manager-object");
if (selectedRow?.dataset.objectId === lmeState.managerHighlightedObjectId) { const selectedParent = event.target?.closest?.("[data-highlight-key]");
if (selectedObject?.dataset.objectId === lmeState.managerHighlightedObjectId
|| selectedParent?.dataset.highlightKey === lmeState.managerHighlightedParentKey) {
return; return;
} }
clearManagerHighlight(); clearManagerHighlight();
@@ -1139,6 +1153,159 @@ function managedObjectLabel(record, index) {
return `Oggetto ${index + 1}`; return `Oggetto ${index + 1}`;
} }
function managedObjectEditableName(record) {
const properties = record?.layer?.feature?.properties || {};
return properties.name || properties.Name || properties.label || properties.LABEL || "";
}
function managedObjectCanRename(record) {
return ["drawings", "imported", "importedSheets"].includes(record.categoryKey)
&& record.layer?.feature?.properties;
}
function renameManagedObject(record, index) {
if (!managedObjectCanRename(record)) {
return;
}
const currentName = String(managedObjectEditableName(record) || managedObjectLabel(record, index)).trim();
const requestedName = window.prompt("Nuovo nome oggetto", currentName);
if (requestedName === null) {
return;
}
const name = requestedName.trim().slice(0, 120);
if (!name) {
setInfo("Nome non modificato: inserisci un testo.");
return;
}
const properties = record.layer.feature.properties;
properties.name = name;
record.layer.lmeManagerLabel = record.categoryKey === "drawings" && record.layer instanceof L.Marker
? `POI: ${name}`
: null;
updateStoredFeaturePopup(record.layer);
refreshImportedMeasurements();
refreshDrawingMeasurements();
refreshLayerManager();
setInfo(`Oggetto rinominato: ${name}.`);
}
function managedCategoryName(category, records) {
const storedName = lmeState.managerCategoryNames[category.key];
const featureName = records
.map(record => record.layer?.feature?.properties?._lmeCategoryName)
.find(Boolean);
const name = String(storedName || featureName || category.label).trim();
if (name !== category.label) {
lmeState.managerCategoryNames[category.key] = name;
}
return name;
}
function managedCategoryCanRename(category) {
return ["drawings", "imported", "importedSheets"].includes(category.key);
}
function renameManagedCategory(category, records) {
const currentName = managedCategoryName(category, records);
const requestedName = window.prompt("Nuovo nome gruppo", currentName);
if (requestedName === null) {
return;
}
const name = requestedName.trim().slice(0, 120);
if (!name) {
setInfo("Nome non modificato: inserisci un testo.");
return;
}
lmeState.managerCategoryNames[category.key] = name;
records.forEach(record => {
if (record.layer?.feature?.properties) {
record.layer.feature.properties._lmeCategoryName = name;
}
});
refreshLayerManager();
setInfo(`Gruppo rinominato: ${name}.`);
}
function managedBlockName(source, records) {
return String(records
.map(record => record.layer?.feature?.properties?._lmeGroupName)
.find(Boolean) || source);
}
function renameManagedBlock(category, source, records) {
const currentName = managedBlockName(source, records);
const requestedName = window.prompt("Nuovo nome gruppo", currentName);
if (requestedName === null) {
return;
}
const name = requestedName.trim().slice(0, 120);
if (!name) {
setInfo("Nome non modificato: inserisci un testo.");
return;
}
records.forEach(record => {
if (record.layer?.feature?.properties) {
record.layer.feature.properties._lmeGroupName = name;
}
});
refreshLayerManager();
setInfo(`Gruppo rinominato: ${name}.`);
}
function managedCategoryCanRemove(category, records) {
return records.length > 0
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(category.key);
}
function managedObjectCanRemove(record) {
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(record.categoryKey);
}
function layerManagerRowActions(renameButton, removeButton) {
if (!renameButton && !removeButton) {
return "";
}
return `<span class="layer-row-actions">${renameButton}${removeButton}</span>`;
}
function removeManagedRecords(records, subject) {
if (!records.length) {
return;
}
const message = records.length === 1
? `Rimuovere ${subject} dal progetto? Non verra incluso nel prossimo salvataggio.`
: `Rimuovere ${subject} e tutti i suoi ${records.length} oggetti dal progetto? Non verranno inclusi nel prossimo salvataggio.`;
if (!window.confirm(message)) {
return;
}
const removedIds = new Set(records.map(record => record.id));
const categoryKeys = new Set(records.map(record => record.categoryKey));
records.forEach(record => {
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);
});
categoryKeys.forEach(categoryKey => {
const order = lmeState.managerObjectOrder[categoryKey] || [];
lmeState.managerObjectOrder[categoryKey] = order.filter(id => !removedIds.has(id));
const category = layerManagerCategories().find(item => item.key === categoryKey);
if (category && managedRecordsForCategory(category).length === 0) {
lmeState.managerRemovedCategories.add(categoryKey);
delete lmeState.managerCategoryNames[categoryKey];
}
});
clearManagerHighlight();
refreshImportedMeasurements();
refreshDrawingMeasurements();
updateGpsInsideState();
refreshLayerManager();
setInfo(`${records.length === 1 ? "Oggetto rimosso" : `${records.length} oggetti rimossi`} dal progetto.`);
}
function managedObjectDetails(layer) { function managedObjectDetails(layer) {
const details = []; const details = [];
const properties = layer?.feature?.properties || {}; const properties = layer?.feature?.properties || {};
@@ -1194,6 +1361,11 @@ function refreshLayerManager() {
return; return;
} }
const records = managedRecordsForCategory(category); const records = managedRecordsForCategory(category);
if (records.length) {
lmeState.managerRemovedCategories.delete(category.key);
} else if (lmeState.managerRemovedCategories.has(category.key)) {
return;
}
const savedOrder = lmeState.managerObjectOrder[category.key] || []; const savedOrder = lmeState.managerObjectOrder[category.key] || [];
const positions = new Map(savedOrder.map((id, index) => [id, index])); const positions = new Map(savedOrder.map((id, index) => [id, index]));
records.sort((left, right) => records.sort((left, right) =>
@@ -1218,15 +1390,35 @@ function renderLayerManagerCategory(category, records) {
group.dataset.dragType = "category"; group.dataset.dragType = "category";
group.dataset.categoryKey = category.key; group.dataset.categoryKey = category.key;
bindLayerManagerExpansion(group, `category:${category.key}`); bindLayerManagerExpansion(group, `category:${category.key}`);
const categoryName = managedCategoryName(category, records);
const renameButton = managedCategoryCanRename(category)
? `<button type="button" class="layer-rename-button" aria-label="Rinomina gruppo" title="Rinomina">Rinomina</button>`
: "";
const removeButton = managedCategoryCanRemove(category, records)
? `<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`
: "";
const rowActions = layerManagerRowActions(renameButton, removeButton);
group.classList.toggle("layer-manager-with-actions", Boolean(rowActions));
const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length; const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
const categoryVisible = !lmeState.managerHiddenCategories.has(category.key) const categoryVisible = !lmeState.managerHiddenCategories.has(category.key)
&& (!category.roots.length || category.roots.some(layer => lmeState.map.hasLayer(layer))); && (!category.roots.length || category.roots.some(layer => lmeState.map.hasLayer(layer)));
const visible = categoryVisible; const visible = categoryVisible;
group.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina gruppo">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(category.label)}"><span class="layer-manager-title">${escapeHtml(category.label)}</span>${layerManagerMoveButtons("gruppo")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`; group.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina gruppo">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(categoryName)}"><span class="layer-manager-title">${escapeHtml(categoryName)}</span>${rowActions}${layerManagerMoveButtons("gruppo")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
const checkbox = group.querySelector("input"); const checkbox = group.querySelector("input");
checkbox.indeterminate = categoryVisible && records.length > 0 && visibleCount < records.length; checkbox.indeterminate = categoryVisible && records.length > 0 && visibleCount < records.length;
checkbox.addEventListener("click", event => event.stopPropagation()); checkbox.addEventListener("click", event => event.stopPropagation());
checkbox.addEventListener("change", () => setManagedCategoryVisible(category, checkbox.checked)); checkbox.addEventListener("change", () => setManagedCategoryVisible(category, checkbox.checked));
group.querySelector(".layer-rename-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
renameManagedCategory(category, records);
});
group.querySelector(".layer-remove-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
removeManagedRecords(records, `il gruppo '${categoryName}'`);
});
bindManagedParentHighlight(group, `category:${category.key}`, records);
const children = group.querySelector(".layer-manager-children"); const children = group.querySelector(".layer-manager-children");
if (category.blocks) { if (category.blocks) {
const blocks = new Map(); const blocks = new Map();
@@ -1251,7 +1443,12 @@ function renderLayerManagerBlock(category, source, records) {
bindLayerManagerExpansion(block, `block:${category.key}:${source}`); bindLayerManagerExpansion(block, `block:${category.key}:${source}`);
const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length; const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
const visible = visibleCount === records.length; const visible = visibleCount === records.length;
block.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra blocco"><span class="layer-manager-title">${escapeHtml(source)}</span>${layerManagerMoveButtons("blocco")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`; const blockName = managedBlockName(source, records);
const rowActions = layerManagerRowActions(
`<button type="button" class="layer-rename-button" aria-label="Rinomina gruppo" title="Rinomina">Rinomina</button>`,
`<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`);
block.classList.add("layer-manager-with-actions");
block.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(blockName)}"><span class="layer-manager-title">${escapeHtml(blockName)}</span>${rowActions}${layerManagerMoveButtons("blocco")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
const checkbox = block.querySelector("input"); const checkbox = block.querySelector("input");
checkbox.indeterminate = visibleCount > 0 && !visible; checkbox.indeterminate = visibleCount > 0 && !visible;
checkbox.addEventListener("click", event => event.stopPropagation()); checkbox.addEventListener("click", event => event.stopPropagation());
@@ -1259,6 +1456,17 @@ function renderLayerManagerBlock(category, source, records) {
records.forEach(record => setManagedObjectVisible(record, checkbox.checked, false)); records.forEach(record => setManagedObjectVisible(record, checkbox.checked, false));
refreshLayerManager(); refreshLayerManager();
}); });
block.querySelector(".layer-rename-button").addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
renameManagedBlock(category, source, records);
});
block.querySelector(".layer-remove-button").addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
removeManagedRecords(records, `il gruppo '${blockName}'`);
});
bindManagedParentHighlight(block, `block:${category.key}:${source}`, records);
const children = block.querySelector(".layer-manager-children"); const children = block.querySelector(".layer-manager-children");
records.forEach((record, index) => children.appendChild(renderLayerManagerObject(record, index))); records.forEach((record, index) => children.appendChild(renderLayerManagerObject(record, index)));
return block; return block;
@@ -1274,10 +1482,29 @@ function renderLayerManagerObject(record, index) {
object.dataset.objectId = record.id; object.dataset.objectId = record.id;
bindLayerManagerExpansion(object, `object:${record.id}`); bindLayerManagerExpansion(object, `object:${record.id}`);
const visible = !lmeState.managerHiddenObjectIds.has(record.id); const visible = !lmeState.managerHiddenObjectIds.has(record.id);
object.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina oggetto">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra oggetto"><span class="layer-manager-title">${escapeHtml(managedObjectLabel(record, index))}</span>${layerManagerMoveButtons("oggetto")}<span class="layer-manager-count">›</span></summary><div class="layer-manager-details"></div>`; const renameButton = managedObjectCanRename(record)
? `<button type="button" class="layer-rename-button" aria-label="Rinomina oggetto" title="Rinomina">Rinomina</button>`
: "";
const removeButton = managedObjectCanRemove(record)
? `<button type="button" class="layer-remove-button" aria-label="Rimuovi oggetto dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`
: "";
const rowActions = layerManagerRowActions(renameButton, removeButton);
object.classList.toggle("layer-manager-with-actions", Boolean(rowActions));
const objectName = managedObjectLabel(record, index);
object.innerHTML = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina oggetto">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra oggetto"><span class="layer-manager-title">${escapeHtml(objectName)}</span>${rowActions}${layerManagerMoveButtons("oggetto")}<span class="layer-manager-count">›</span></summary><div class="layer-manager-details"></div>`;
const checkbox = object.querySelector("input"); const checkbox = object.querySelector("input");
checkbox.addEventListener("click", event => event.stopPropagation()); checkbox.addEventListener("click", event => event.stopPropagation());
checkbox.addEventListener("change", () => setManagedObjectVisible(record, checkbox.checked, true)); checkbox.addEventListener("change", () => setManagedObjectVisible(record, checkbox.checked, true));
object.querySelector(".layer-rename-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
renameManagedObject(record, index);
});
object.querySelector(".layer-remove-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
removeManagedRecords([record], `'${objectName}'`);
});
object.querySelector(".layer-manager-title").addEventListener("click", event => { object.querySelector(".layer-manager-title").addEventListener("click", event => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -1285,13 +1512,115 @@ function renderLayerManagerObject(record, index) {
highlightManagedObject(record); highlightManagedObject(record);
}); });
const details = object.querySelector(".layer-manager-details"); const details = object.querySelector(".layer-manager-details");
details.appendChild(renderLayerManagerVisibilityOption(
geometryVisibilityLabel(record.layer),
visible,
checked => setManagedObjectVisible(record, checked, true)));
if (supportsManagedMeasurements(record)) {
managedMeasurementKinds(record).forEach(kind => {
const measurementsVisible = managedMeasurementsGloballyVisible(record.categoryKey)
&& !managedMeasurementHiddenSet(kind.key).has(record.id);
details.appendChild(renderLayerManagerVisibilityOption(
kind.label,
measurementsVisible,
checked => setManagedMeasurementKindVisible(record, kind.key, checked)));
});
}
const rows = managedObjectDetails(record.layer); const rows = managedObjectDetails(record.layer);
details.innerHTML = rows.length const info = document.createElement("div");
info.className = "layer-manager-info";
info.innerHTML = rows.length
? rows.map(value => `<span>${escapeHtml(value)}</span>`).join("") ? rows.map(value => `<span>${escapeHtml(value)}</span>`).join("")
: "<span>Nessuna misura disponibile</span>"; : "<span>Nessuna misura disponibile</span>";
details.appendChild(info);
return object; return object;
} }
function renderLayerManagerVisibilityOption(label, checked, onChange) {
const option = document.createElement("label");
option.className = "layer-manager-suboption";
option.innerHTML = `<input type="checkbox" ${checked ? "checked" : ""}><span>${escapeHtml(label)}</span>`;
const checkbox = option.querySelector("input");
checkbox.addEventListener("click", event => event.stopPropagation());
checkbox.addEventListener("change", () => onChange(checkbox.checked));
return option;
}
function geometryVisibilityLabel(layer) {
if (layer instanceof L.Polygon || layer instanceof L.Circle) {
return "Area/geometria sulla mappa";
}
if (layer instanceof L.Polyline) {
return "Linea sulla mappa";
}
return "Punto sulla mappa";
}
function supportsManagedMeasurements(record) {
return ["imported", "drawings"].includes(record.categoryKey)
&& (record.layer instanceof L.Circle
|| record.layer instanceof L.Polygon
|| record.layer instanceof L.Polyline);
}
function managedMeasurementsGloballyVisible(categoryKey) {
return categoryKey === "imported"
? lmeState.showImportedMeasurements
: lmeState.showDrawingMeasurements;
}
function managedMeasurementKinds(record) {
if (record.layer instanceof L.Polygon) {
return [
{ key: "summary", label: "Superficie e perimetro sulla mappa" },
{ key: "sides", label: "Misure dei lati sulla mappa" }
];
}
if (record.layer instanceof L.Circle) {
return [{ key: "summary", label: "Superficie, raggio e circonferenza sulla mappa" }];
}
return [
{ key: "summary", label: "Lunghezza totale sulla mappa" },
{ key: "sides", label: "Misure dei tratti sulla mappa" }
];
}
function managedMeasurementHiddenSet(kind) {
return kind === "sides"
? lmeState.managerHiddenMeasurementSideIds
: lmeState.managerHiddenMeasurementSummaryIds;
}
function setManagedMeasurementKindVisible(record, kind, visible) {
const hiddenSet = managedMeasurementHiddenSet(kind);
if (visible) {
if (!managedMeasurementsGloballyVisible(record.categoryKey)) {
lmeState.managerRecords.forEach(candidate => {
if (candidate.categoryKey === record.categoryKey
&& supportsManagedMeasurements(candidate)) {
lmeState.managerHiddenMeasurementSummaryIds.add(candidate.id);
lmeState.managerHiddenMeasurementSideIds.add(candidate.id);
}
});
if (record.categoryKey === "imported") {
lmeState.showImportedMeasurements = true;
} else {
lmeState.showDrawingMeasurements = true;
}
void lmeState.dotNetReference?.invokeMethodAsync(
"SetMeasurementsFromLayerManager",
record.categoryKey,
true);
}
hiddenSet.delete(record.id);
} else {
hiddenSet.add(record.id);
}
refreshImportedMeasurements();
refreshDrawingMeasurements();
refreshLayerManager();
}
function captureLayerManagerExpansion(container) { function captureLayerManagerExpansion(container) {
container.querySelectorAll("details[data-expansion-key]").forEach(row => { container.querySelectorAll("details[data-expansion-key]").forEach(row => {
if (row.open) { if (row.open) {
@@ -1407,33 +1736,107 @@ function highlightManagedObject(record) {
document.querySelectorAll(".layer-manager-object").forEach(row => { document.querySelectorAll(".layer-manager-object").forEach(row => {
row.classList.toggle("layer-manager-object-selected", row.dataset.objectId === record.id); row.classList.toggle("layer-manager-object-selected", row.dataset.objectId === record.id);
}); });
const geojson = record.layer.toGeoJSON?.(); renderManagerHighlight([record]);
if (geojson) {
L.geoJSON(geojson, {
pane: "managerHighlightPane",
interactive: false,
style: { color: "#ff00d4", weight: 8, opacity: 1, fillColor: "#00fff0", fillOpacity: 0.16 },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
pane: "managerHighlightPane",
radius: 12,
color: "#ff00d4",
weight: 5,
fillColor: "#00fff0",
fillOpacity: 0.9,
interactive: false
})
}).addTo(lmeState.managerHighlight);
}
fitLayer(record.layer); fitLayer(record.layer);
} }
function bindManagedParentHighlight(row, key, records) {
const summary = row.querySelector(":scope > summary");
row.dataset.highlightKey = key;
row.classList.toggle("layer-manager-parent-selected", lmeState.managerHighlightedParentKey === key);
if (lmeState.managerHighlightedParentKey === key) {
lmeState.managerHighlightedParentRecords = records;
}
row.querySelector(":scope > summary > .layer-manager-title").addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
row.open = true;
highlightManagedParent(row, key, records);
});
summary.addEventListener("pointerenter", () => {
lmeState.managerHoveredParentKey = key;
lmeState.managerHoveredParentRecords = records;
row.classList.add("layer-manager-parent-highlighted");
renderManagerHighlight(records);
});
summary.addEventListener("pointerleave", () => {
if (lmeState.managerHoveredParentKey !== key) {
return;
}
lmeState.managerHoveredParentKey = null;
lmeState.managerHoveredParentRecords = null;
row.classList.remove("layer-manager-parent-highlighted");
renderSelectedManagerHighlight();
});
}
function highlightManagedParent(row, key, records) {
if (lmeState.managerHighlightedParentKey === key) {
clearManagerHighlight();
return;
}
clearManagerHighlight();
lmeState.managerHighlightedParentKey = key;
lmeState.managerHighlightedParentRecords = records;
lmeState.managerHighlightedCategoryKey = records[0]?.categoryKey || null;
row.classList.add("layer-manager-parent-selected");
renderManagerHighlight(records);
}
function renderSelectedManagerHighlight() {
if (lmeState.managerHighlightedParentRecords?.length) {
renderManagerHighlight(lmeState.managerHighlightedParentRecords);
return;
}
const selected = lmeState.managerRecords.get(lmeState.managerHighlightedObjectId);
renderManagerHighlight(selected ? [selected] : []);
}
function renderManagerHighlight(records) {
lmeState.managerHighlight?.clearLayers();
const features = records.flatMap(record => {
const geojson = record.layer?.toGeoJSON?.();
if (!geojson) {
return [];
}
return geojson.type === "FeatureCollection" ? geojson.features : [geojson];
});
if (!features.length) {
return;
}
L.geoJSON({ type: "FeatureCollection", features }, {
pane: "managerHighlightPane",
interactive: false,
style: { color: "#ff00d4", weight: 8, opacity: 1, fillColor: "#00fff0", fillOpacity: 0.16 },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
pane: "managerHighlightPane",
radius: 12,
color: "#ff00d4",
weight: 5,
fillColor: "#00fff0",
fillOpacity: 0.9,
interactive: false
})
}).addTo(lmeState.managerHighlight);
}
function clearManagerHighlight() { function clearManagerHighlight() {
lmeState.managerHighlight?.clearLayers(); lmeState.managerHighlight?.clearLayers();
lmeState.managerHighlightedObjectId = null; lmeState.managerHighlightedObjectId = null;
lmeState.managerHighlightedCategoryKey = null; lmeState.managerHighlightedCategoryKey = null;
lmeState.managerHighlightedParentKey = null;
lmeState.managerHighlightedParentRecords = null;
lmeState.managerHoveredParentKey = null;
lmeState.managerHoveredParentRecords = null;
document.querySelectorAll(".layer-manager-object-selected").forEach(row => { document.querySelectorAll(".layer-manager-object-selected").forEach(row => {
row.classList.remove("layer-manager-object-selected"); row.classList.remove("layer-manager-object-selected");
}); });
document.querySelectorAll(".layer-manager-parent-highlighted").forEach(row => {
row.classList.remove("layer-manager-parent-highlighted");
});
document.querySelectorAll(".layer-manager-parent-selected").forEach(row => {
row.classList.remove("layer-manager-parent-selected");
});
} }
function layerManagerMoveButtons(label) { function layerManagerMoveButtons(label) {
@@ -1659,6 +2062,7 @@ function applyImportedSheetVisibility() {
function setImportedMeasurementsVisible(enabled) { function setImportedMeasurementsVisible(enabled) {
lmeState.showImportedMeasurements = Boolean(enabled); lmeState.showImportedMeasurements = Boolean(enabled);
refreshImportedMeasurements(); refreshImportedMeasurements();
refreshLayerManager();
setInfo(lmeState.showImportedMeasurements setInfo(lmeState.showImportedMeasurements
? "Misure dei file GIS visibili." ? "Misure dei file GIS visibili."
: "Misure dei file GIS nascoste."); : "Misure dei file GIS nascoste.");
@@ -1667,6 +2071,7 @@ function setImportedMeasurementsVisible(enabled) {
function setDrawingMeasurementsVisible(enabled) { function setDrawingMeasurementsVisible(enabled) {
lmeState.showDrawingMeasurements = Boolean(enabled); lmeState.showDrawingMeasurements = Boolean(enabled);
refreshDrawingMeasurements(); refreshDrawingMeasurements();
refreshLayerManager();
setInfo(lmeState.showDrawingMeasurements setInfo(lmeState.showDrawingMeasurements
? "Misure dei disegni visibili." ? "Misure dei disegni visibili."
: "Misure dei disegni nascoste."); : "Misure dei disegni nascoste.");
@@ -1936,15 +2341,27 @@ function renderMeasurements(sourceGroup, targetGroup, visible, sourceType) {
sourceType sourceType
}; };
visitMeasurableLayers(sourceGroup, layer => { visitMeasurableLayers(sourceGroup, layer => {
const categoryKey = sourceType === "imported" ? "imported" : "drawings";
const objectId = managedLayerId(layer, categoryKey);
const showSummary = !lmeState.managerHiddenMeasurementSummaryIds.has(objectId);
const showSides = !lmeState.managerHiddenMeasurementSideIds.has(objectId);
if (!showSummary && !showSides) {
return;
}
if (context.count >= context.max || !layerIsInView(layer, context.bounds)) { if (context.count >= context.max || !layerIsInView(layer, context.bounds)) {
return; return;
} }
const layerContext = {
...context,
showSummary,
showSides: context.showSides && showSides
};
if (layer instanceof L.Circle) { if (layer instanceof L.Circle) {
renderCircleMeasurement(layer, targetGroup, context); renderCircleMeasurement(layer, targetGroup, layerContext);
} else if (layer instanceof L.Polygon) { } else if (layer instanceof L.Polygon) {
renderPolygonMeasurement(layer, targetGroup, context); renderPolygonMeasurement(layer, targetGroup, layerContext);
} else if (layer instanceof L.Polyline) { } else if (layer instanceof L.Polyline) {
renderPolylineMeasurement(layer, targetGroup, context); renderPolylineMeasurement(layer, targetGroup, layerContext);
} }
}); });
} }
@@ -1971,6 +2388,9 @@ function layerIsInView(layer, mapBounds) {
} }
function renderCircleMeasurement(layer, targetGroup, context) { function renderCircleMeasurement(layer, targetGroup, context) {
if (!context.showSummary) {
return;
}
const radius = layer.getRadius(); const radius = layer.getRadius();
const area = Math.PI * radius * radius; const area = Math.PI * radius * radius;
const circumference = 2 * Math.PI * radius; const circumference = 2 * Math.PI * radius;
@@ -1999,12 +2419,14 @@ function renderPolygonMeasurement(layer, targetGroup, context) {
const center = typeof layer.getCenter === "function" const center = typeof layer.getCenter === "function"
? layer.getCenter() ? layer.getCenter()
: layer.getBounds().getCenter(); : layer.getBounds().getCenter();
addMeasurementLabel( if (context.showSummary) {
targetGroup, addMeasurementLabel(
center, targetGroup,
`S: ${formatArea(Math.max(0, area))}<br>P: ${formatDistance(perimeter)}`, center,
"summary", `S: ${formatArea(Math.max(0, area))}<br>P: ${formatDistance(perimeter)}`,
context); "summary",
context);
}
} }
function renderPolylineMeasurement(layer, targetGroup, context) { function renderPolylineMeasurement(layer, targetGroup, context) {
@@ -2014,7 +2436,7 @@ function renderPolylineMeasurement(layer, targetGroup, context) {
lines.forEach(line => addSegmentLabels(line, false, targetGroup, context)); lines.forEach(line => addSegmentLabels(line, false, targetGroup, context));
} }
const longest = lines.reduce((current, line) => line.length > current.length ? line : current, []); const longest = lines.reduce((current, line) => line.length > current.length ? line : current, []);
if (longest.length) { if (context.showSummary && longest.length) {
addMeasurementLabel( addMeasurementLabel(
targetGroup, targetGroup,
longest[Math.floor(longest.length / 2)], longest[Math.floor(longest.length / 2)],
@@ -2360,7 +2782,11 @@ function captureDisplayOptions() {
selectedParcelsVisible: lmeState.selectedParcelsVisible, selectedParcelsVisible: lmeState.selectedParcelsVisible,
layerOrder: [...lmeState.layerOrder], layerOrder: [...lmeState.layerOrder],
layerObjectOrder: { ...lmeState.managerObjectOrder }, layerObjectOrder: { ...lmeState.managerObjectOrder },
layerCategoryNames: { ...lmeState.managerCategoryNames },
removedLayerCategories: [...lmeState.managerRemovedCategories],
hiddenLayerObjects: [...lmeState.managerHiddenObjectIds], hiddenLayerObjects: [...lmeState.managerHiddenObjectIds],
hiddenLayerMeasurementSummaries: [...lmeState.managerHiddenMeasurementSummaryIds],
hiddenLayerMeasurementSides: [...lmeState.managerHiddenMeasurementSideIds],
hiddenLayerCategories: [...lmeState.managerHiddenCategories], hiddenLayerCategories: [...lmeState.managerHiddenCategories],
trailEnabled: lmeState.gpsTrailEnabled trailEnabled: lmeState.gpsTrailEnabled
}; };
@@ -2394,9 +2820,21 @@ function applyProjectDisplayOptions(options) {
if (options.layerObjectOrder && typeof options.layerObjectOrder === "object") { if (options.layerObjectOrder && typeof options.layerObjectOrder === "object") {
lmeState.managerObjectOrder = options.layerObjectOrder; lmeState.managerObjectOrder = options.layerObjectOrder;
} }
if (options.layerCategoryNames && typeof options.layerCategoryNames === "object") {
lmeState.managerCategoryNames = { ...options.layerCategoryNames };
}
if (Array.isArray(options.removedLayerCategories)) {
lmeState.managerRemovedCategories = new Set(options.removedLayerCategories);
}
if (Array.isArray(options.hiddenLayerObjects)) { if (Array.isArray(options.hiddenLayerObjects)) {
lmeState.managerHiddenObjectIds = new Set(options.hiddenLayerObjects); lmeState.managerHiddenObjectIds = new Set(options.hiddenLayerObjects);
} }
if (Array.isArray(options.hiddenLayerMeasurementSummaries)) {
lmeState.managerHiddenMeasurementSummaryIds = new Set(options.hiddenLayerMeasurementSummaries);
}
if (Array.isArray(options.hiddenLayerMeasurementSides)) {
lmeState.managerHiddenMeasurementSideIds = new Set(options.hiddenLayerMeasurementSides);
}
if (Array.isArray(options.hiddenLayerCategories)) { if (Array.isArray(options.hiddenLayerCategories)) {
lmeState.managerHiddenCategories = new Set(options.hiddenLayerCategories); lmeState.managerHiddenCategories = new Set(options.hiddenLayerCategories);
} }
@@ -2474,18 +2912,35 @@ function managedGroupGeoJson(group, categoryKey) {
} }
}); });
}); });
const categoryName = lmeState.managerCategoryNames[categoryKey];
if (categoryName) {
features.forEach(feature => {
feature.properties = { ...(feature.properties || {}), _lmeCategoryName: categoryName };
});
}
return { type: "FeatureCollection", features }; return { type: "FeatureCollection", features };
} }
function resetLayerManagerProjectState() { function resetLayerManagerProjectState() {
lmeState.managerHiddenObjects.clear(); lmeState.managerHiddenObjects.clear();
lmeState.managerHiddenObjectIds.clear(); lmeState.managerHiddenObjectIds.clear();
lmeState.managerHiddenMeasurementSummaryIds.clear();
lmeState.managerHiddenMeasurementSideIds.clear();
lmeState.managerHiddenCategories.clear(); lmeState.managerHiddenCategories.clear();
lmeState.managerRemovedCategories.clear();
lmeState.managerObjectOrder = {}; lmeState.managerObjectOrder = {};
lmeState.managerCategoryNames = {};
clearManagerHighlight(); clearManagerHighlight();
} }
function clearManagedCategoryState(categoryKey) { function clearManagedCategoryState(categoryKey) {
const category = layerManagerCategories().find(item => item.key === categoryKey);
if (category) {
managedRecordsForCategory(category).forEach(record => {
lmeState.managerHiddenMeasurementSummaryIds.delete(record.id);
lmeState.managerHiddenMeasurementSideIds.delete(record.id);
});
}
[...lmeState.managerHiddenObjects.entries()].forEach(([id, record]) => { [...lmeState.managerHiddenObjects.entries()].forEach(([id, record]) => {
if (record.categoryKey === categoryKey) { if (record.categoryKey === categoryKey) {
lmeState.managerHiddenObjects.delete(id); lmeState.managerHiddenObjects.delete(id);
@@ -2497,10 +2952,18 @@ function clearManagedCategoryState(categoryKey) {
} }
function bindStoredFeaturePopup(feature, layer) { function bindStoredFeaturePopup(feature, layer) {
const properties = feature?.properties || {}; updateStoredFeaturePopup(layer, feature?.properties);
}
function updateStoredFeaturePopup(layer, sourceProperties) {
const properties = sourceProperties || layer?.feature?.properties || {};
const label = properties.name || properties.Name || properties.label || properties.LABEL; const label = properties.name || properties.Name || properties.label || properties.LABEL;
if (label) { if (label) {
layer.bindPopup(`<strong>${escapeHtml(label)}</strong>`); layer.bindPopup(`<strong>${escapeHtml(label)}</strong>`);
return;
}
if (typeof layer?.unbindPopup === "function") {
layer.unbindPopup();
} }
} }