diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor index daa71f0..5898f3f 100644 --- a/Components/Pages/Home.razor +++ b/Components/Pages/Home.razor @@ -472,6 +472,7 @@ private bool DrawToolsVisible { get; set; } private bool TrailEnabled { get; set; } private bool LayerPanelOpen { get; set; } + private DotNetObjectReference? MapCallbackReference { get; set; } private bool IncludeCadastralParcels { get; set; } = true; private bool IncludeKmlSheetLayer { get; set; } private bool ShowImportedSheetLayer { get; set; } @@ -570,7 +571,8 @@ { if (firstRender) { - await JS.InvokeVoidAsync("lmeMap.init"); + MapCallbackReference = DotNetObjectReference.Create(this); + await JS.InvokeVoidAsync("lmeMap.init", MapCallbackReference); await ApplyFlagPreferencesToMapAsync(); await CenterOnInitialLocationAsync(); await MigrateLegacyProjectAsync(); @@ -1047,6 +1049,21 @@ 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() { DrawToolsVisible = !DrawToolsVisible; @@ -1503,6 +1520,7 @@ LocationService.StopTracking(); GpsLookupCancellation?.Cancel(); GpsLookupCancellation?.Dispose(); + MapCallbackReference?.Dispose(); return ValueTask.CompletedTask; } } diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj index 97c028d..006a3ad 100644 --- a/LMEMappeCatastaliApp.csproj +++ b/LMEMappeCatastaliApp.csproj @@ -28,8 +28,8 @@ com.companyname.lmemappecatastaliapp - 0.2.21 - 25 + 0.2.22 + 26 None diff --git a/Services/AzureMapsService.cs b/Services/AzureMapsService.cs index c5ebc83..ca40236 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.21"); + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.22"); _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json"); } diff --git a/Services/NetworkLocationService.cs b/Services/NetworkLocationService.cs index e5b5d9a..ed34d02 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.21"); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.22"); } public async Task GetApproximateLocationAsync( diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index 9ef72b4..2b1d886 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -349,6 +349,27 @@ input[type="checkbox"] { 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 { opacity: 0.55; outline: 2px solid #2563eb; diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js index b03c146..3f7f708 100644 --- a/wwwroot/js/lme-map.js +++ b/wwwroot/js/lme-map.js @@ -54,9 +54,12 @@ const lmeState = { managerOutsideSelectionBound: false, managerHiddenObjects: new Map(), managerHiddenObjectIds: new Set(), + managerHiddenMeasurementSummaryIds: new Set(), + managerHiddenMeasurementSideIds: new Set(), managerHiddenCategories: new Set(), managerObjectOrder: {}, - managerExpandedRows: new Set() + managerExpandedRows: new Set(), + dotNetReference: null }; // AdE publishes the cadastral WMS in ETRS89. WMS 1.1.1 keeps the longitude, @@ -109,7 +112,10 @@ window.lmeMap = { showInfo: setInfo }; -function init() { +function init(dotNetReference) { + if (dotNetReference) { + lmeState.dotNetReference = dotNetReference; + } if (lmeState.map) { return; } @@ -1285,13 +1291,115 @@ function renderLayerManagerObject(record, index) { highlightManagedObject(record); }); 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); - details.innerHTML = rows.length + const info = document.createElement("div"); + info.className = "layer-manager-info"; + info.innerHTML = rows.length ? rows.map(value => `${escapeHtml(value)}`).join("") : "Nessuna misura disponibile"; + details.appendChild(info); return object; } +function renderLayerManagerVisibilityOption(label, checked, onChange) { + const option = document.createElement("label"); + option.className = "layer-manager-suboption"; + option.innerHTML = `${escapeHtml(label)}`; + 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) { container.querySelectorAll("details[data-expansion-key]").forEach(row => { if (row.open) { @@ -1659,6 +1767,7 @@ function applyImportedSheetVisibility() { function setImportedMeasurementsVisible(enabled) { lmeState.showImportedMeasurements = Boolean(enabled); refreshImportedMeasurements(); + refreshLayerManager(); setInfo(lmeState.showImportedMeasurements ? "Misure dei file GIS visibili." : "Misure dei file GIS nascoste."); @@ -1667,6 +1776,7 @@ function setImportedMeasurementsVisible(enabled) { function setDrawingMeasurementsVisible(enabled) { lmeState.showDrawingMeasurements = Boolean(enabled); refreshDrawingMeasurements(); + refreshLayerManager(); setInfo(lmeState.showDrawingMeasurements ? "Misure dei disegni visibili." : "Misure dei disegni nascoste."); @@ -1936,15 +2046,27 @@ function renderMeasurements(sourceGroup, targetGroup, visible, sourceType) { sourceType }; 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)) { return; } + const layerContext = { + ...context, + showSummary, + showSides: context.showSides && showSides + }; if (layer instanceof L.Circle) { - renderCircleMeasurement(layer, targetGroup, context); + renderCircleMeasurement(layer, targetGroup, layerContext); } else if (layer instanceof L.Polygon) { - renderPolygonMeasurement(layer, targetGroup, context); + renderPolygonMeasurement(layer, targetGroup, layerContext); } else if (layer instanceof L.Polyline) { - renderPolylineMeasurement(layer, targetGroup, context); + renderPolylineMeasurement(layer, targetGroup, layerContext); } }); } @@ -1971,6 +2093,9 @@ function layerIsInView(layer, mapBounds) { } function renderCircleMeasurement(layer, targetGroup, context) { + if (!context.showSummary) { + return; + } const radius = layer.getRadius(); const area = Math.PI * radius * radius; const circumference = 2 * Math.PI * radius; @@ -1999,12 +2124,14 @@ function renderPolygonMeasurement(layer, targetGroup, context) { const center = typeof layer.getCenter === "function" ? layer.getCenter() : layer.getBounds().getCenter(); - addMeasurementLabel( - targetGroup, - center, - `S: ${formatArea(Math.max(0, area))}
P: ${formatDistance(perimeter)}`, - "summary", - context); + if (context.showSummary) { + addMeasurementLabel( + targetGroup, + center, + `S: ${formatArea(Math.max(0, area))}
P: ${formatDistance(perimeter)}`, + "summary", + context); + } } function renderPolylineMeasurement(layer, targetGroup, context) { @@ -2014,7 +2141,7 @@ function renderPolylineMeasurement(layer, targetGroup, context) { lines.forEach(line => addSegmentLabels(line, false, targetGroup, context)); } const longest = lines.reduce((current, line) => line.length > current.length ? line : current, []); - if (longest.length) { + if (context.showSummary && longest.length) { addMeasurementLabel( targetGroup, longest[Math.floor(longest.length / 2)], @@ -2361,6 +2488,8 @@ function captureDisplayOptions() { layerOrder: [...lmeState.layerOrder], layerObjectOrder: { ...lmeState.managerObjectOrder }, hiddenLayerObjects: [...lmeState.managerHiddenObjectIds], + hiddenLayerMeasurementSummaries: [...lmeState.managerHiddenMeasurementSummaryIds], + hiddenLayerMeasurementSides: [...lmeState.managerHiddenMeasurementSideIds], hiddenLayerCategories: [...lmeState.managerHiddenCategories], trailEnabled: lmeState.gpsTrailEnabled }; @@ -2397,6 +2526,12 @@ function applyProjectDisplayOptions(options) { if (Array.isArray(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)) { lmeState.managerHiddenCategories = new Set(options.hiddenLayerCategories); } @@ -2480,12 +2615,21 @@ function managedGroupGeoJson(group, categoryKey) { function resetLayerManagerProjectState() { lmeState.managerHiddenObjects.clear(); lmeState.managerHiddenObjectIds.clear(); + lmeState.managerHiddenMeasurementSummaryIds.clear(); + lmeState.managerHiddenMeasurementSideIds.clear(); lmeState.managerHiddenCategories.clear(); lmeState.managerObjectOrder = {}; clearManagerHighlight(); } 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]) => { if (record.categoryKey === categoryKey) { lmeState.managerHiddenObjects.delete(id);