diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor
index 1aa5ca8..f3ecf41 100644
--- a/Components/Pages/Home.razor
+++ b/Components/Pages/Home.razor
@@ -680,8 +680,8 @@
@@ -889,7 +889,7 @@
private bool GpsOverlayCollapsed { get; set; }
private bool ShowGpsPosition { get; set; } = true;
private bool ShowCompass { get; set; }
- private string CompassMode { get; set; } = "heading";
+ private string CompassMode { get; set; } = "fixed";
private bool CompassSensorSubscribed { get; set; }
private bool ShowSelectedParcels { get; set; } = true;
private bool ShowFiducialPoints { get; set; } = true;
@@ -2322,7 +2322,7 @@
DrawToolsVisible = options.DrawToolsVisible;
ShowGpsPosition = options.GpsPositionVisible;
ShowCompass = options.CompassVisible;
- CompassMode = options.CompassMode is "fixed" ? "fixed" : "heading";
+ CompassMode = options.CompassMode is "heading" ? "heading" : "fixed";
ShowSelectedParcels = options.SelectedParcelsVisible;
TrailEnabled = options.TrailEnabled;
@@ -2745,7 +2745,7 @@
DrawToolsVisible = GetFlagPreference("draw_tools_visible", false);
ShowGpsPosition = GetFlagPreference("show_gps_position", true);
ShowCompass = GetFlagPreference("show_compass", false);
- CompassMode = Preferences.Default.Get($"{FlagPreferencePrefix}compass_mode", "heading") is "fixed" ? "fixed" : "heading";
+ CompassMode = Preferences.Default.Get($"{FlagPreferencePrefix}compass_mode", "fixed") is "heading" ? "heading" : "fixed";
ShowSelectedParcels = GetFlagPreference("show_selected_parcels", true);
ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true);
KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true);
@@ -2853,7 +2853,7 @@
private async Task ApplyCompassOptions()
{
- CompassMode = CompassMode is "fixed" ? "fixed" : "heading";
+ CompassMode = CompassMode is "heading" ? "heading" : "fixed";
SaveFlagPreferences();
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
await ConfigureCompassSensorAsync();
@@ -2988,7 +2988,7 @@
public bool DrawToolsVisible { get; init; }
public bool GpsPositionVisible { get; init; } = true;
public bool CompassVisible { get; init; }
- public string CompassMode { get; init; } = "heading";
+ public string CompassMode { get; init; } = "fixed";
public bool SelectedParcelsVisible { get; init; } = true;
public bool TrailEnabled { get; init; }
}
diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj
index 237af3a..21339fc 100644
--- a/LMEMappeCatastaliApp.csproj
+++ b/LMEMappeCatastaliApp.csproj
@@ -29,8 +29,8 @@
it.liviomerola.lmemappecatastali.pro
- 1.0.15
- 115
+ 1.0.16
+ 116
$(ApplicationDisplayVersion)
diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css
index 3e17b55..5651f42 100644
--- a/wwwroot/css/app.css
+++ b/wwwroot/css/app.css
@@ -1676,43 +1676,77 @@ label {
.map-compass {
--compass-rotation: 0deg;
+ --compass-radius: -31px;
position: absolute;
top: 50%;
left: 12px;
z-index: 810;
- width: 54px;
- height: 54px;
+ width: 82px;
+ height: 82px;
transform: translateY(-50%);
border: 2px solid rgba(255, 255, 255, 0.92);
border-radius: 50%;
background: rgba(22, 31, 38, 0.86);
box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3);
color: #fff;
- pointer-events: none;
+ cursor: grab;
+ pointer-events: auto;
+ touch-action: none;
+ user-select: none;
}
-.map-compass-north {
+.map-compass.is-dragging {
+ cursor: grabbing;
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42);
+}
+
+.map-compass-rose {
position: absolute;
- top: 2px;
+ inset: 0;
+ transform: rotate(var(--compass-rotation));
+ transition: transform 120ms linear;
+}
+
+.map-compass-direction {
+ position: absolute;
+ top: 50%;
left: 50%;
- transform: translateX(-50%);
+ width: 20px;
+ height: 12px;
+ transform: translate(-50%, -50%) rotate(var(--direction-angle)) translateY(var(--compass-radius));
+ color: #f8fafc;
+ font-size: 9px;
+ font-weight: 800;
+ line-height: 1;
+ text-align: center;
+}
+
+.map-compass-direction > span {
+ display: block;
+ transform: rotate(var(--label-counter));
+}
+
+.map-compass-direction.is-intermediate {
+ color: #cbd5e1;
+ font-size: 7px;
+}
+
+.map-compass-direction.is-north {
color: #fca5a5;
font-size: 11px;
font-weight: 900;
- line-height: 1;
}
.map-compass-needle {
position: absolute;
- bottom: 25px;
+ bottom: 50%;
left: 50%;
width: 4px;
- height: 18px;
- transform: translateX(-50%) rotate(var(--compass-rotation));
+ height: 23px;
+ transform: translateX(-50%);
transform-origin: 50% 100%;
border-radius: 4px 4px 1px 1px;
background: #ef4444;
- transition: transform 120ms linear;
}
.map-compass-needle::after {
@@ -2015,9 +2049,10 @@ footer {
}
.map-compass {
+ --compass-radius: -27px;
left: 8px;
- width: 48px;
- height: 48px;
+ width: 72px;
+ height: 72px;
}
.map-info-text {
diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js
index 5d0dd59..b5d7aa4 100644
--- a/wwwroot/js/lme-map.js
+++ b/wwwroot/js/lme-map.js
@@ -65,8 +65,11 @@ const lmeState = {
compassLayer: null,
compassElement: null,
compassVisible: false,
- compassMode: "heading",
+ compassMode: "fixed",
compassHeading: 0,
+ compassPosition: null,
+ compassDragCleanup: null,
+ compassResizeHandler: null,
infoTimeoutSeconds: 3,
infoTimer: null,
fileImportBound: false,
@@ -321,7 +324,7 @@ function init(dotNetReference) {
restoreLayerOrder();
Object.values(managedLayerPanes()).forEach(paneName => {
if (!lmeState.map.getPane(paneName)) {
- const noRotation = paneName === "gpsPositionPane" || paneName === "fiducialsPane";
+ const noRotation = paneName === "gpsPositionPane";
lmeState.map.createPane(paneName, noRotation
? (lmeState.map._norotatePane || lmeState.map._mapPane)
: (lmeState.map._rotatePane || lmeState.map._mapPane));
@@ -442,22 +445,150 @@ function createCompassLayer() {
const element = L.DomUtil.create("div", "map-compass");
element.setAttribute("role", "img");
element.setAttribute("aria-label", "Bussola: nord");
- element.innerHTML = `N`;
+ element.setAttribute("title", "Trascina per spostare la bussola");
+ element.innerHTML = `
+ N
+ NE
+ E
+ SE
+ S
+ SW
+ W
+ NW
+
+ `;
L.DomEvent.disableClickPropagation(element);
+ L.DomEvent.disableScrollPropagation(element);
map.getContainer().appendChild(element);
lmeState.compassElement = element;
+ restoreCompassPosition();
+ bindCompassDragging(element);
+ lmeState.compassResizeHandler = () => applyCompassPosition();
+ map.on("resize", lmeState.compassResizeHandler);
+ requestAnimationFrame(() => applyCompassPosition());
updateCompassDisplay();
},
- onRemove() {
+ onRemove(map) {
+ lmeState.compassDragCleanup?.();
+ lmeState.compassDragCleanup = null;
+ if (lmeState.compassResizeHandler) {
+ map.off("resize", lmeState.compassResizeHandler);
+ lmeState.compassResizeHandler = null;
+ }
lmeState.compassElement?.remove();
lmeState.compassElement = null;
}
}))();
}
-function setCompassOptions(visible, mode = "heading") {
+function restoreCompassPosition() {
+ if (lmeState.compassPosition) return;
+ try {
+ const saved = JSON.parse(localStorage.getItem("lmeCompassPosition") || "null");
+ const x = Number(saved?.x);
+ const y = Number(saved?.y);
+ if (Number.isFinite(x) && Number.isFinite(y)) {
+ lmeState.compassPosition = {
+ x: Math.max(0, Math.min(1, x)),
+ y: Math.max(0, Math.min(1, y))
+ };
+ }
+ } catch {
+ }
+}
+
+function applyCompassPosition() {
+ const element = lmeState.compassElement;
+ const container = lmeState.map?.getContainer();
+ if (!element || !container) return;
+ const width = container.clientWidth;
+ const height = container.clientHeight;
+ const elementWidth = element.offsetWidth;
+ const elementHeight = element.offsetHeight;
+ if (!width || !height || !elementWidth || !elementHeight) return;
+
+ const halfWidth = elementWidth / 2;
+ const halfHeight = elementHeight / 2;
+ const position = lmeState.compassPosition || {
+ x: (12 + halfWidth) / width,
+ y: 0.5
+ };
+ const centerX = Math.max(halfWidth + 4, Math.min(width - halfWidth - 4, position.x * width));
+ const centerY = Math.max(halfHeight + 4, Math.min(height - halfHeight - 4, position.y * height));
+ element.style.left = `${centerX - halfWidth}px`;
+ element.style.top = `${centerY - halfHeight}px`;
+ element.style.transform = "none";
+}
+
+function bindCompassDragging(element) {
+ let pointerId = null;
+ let offsetX = 0;
+ let offsetY = 0;
+
+ const move = event => {
+ if (event.pointerId !== pointerId) return;
+ event.preventDefault();
+ event.stopPropagation();
+ const container = lmeState.map?.getContainer();
+ if (!container) return;
+ const containerRect = container.getBoundingClientRect();
+ const elementWidth = element.offsetWidth;
+ const elementHeight = element.offsetHeight;
+ const left = event.clientX - containerRect.left - offsetX;
+ const top = event.clientY - containerRect.top - offsetY;
+ const centerX = Math.max(elementWidth / 2 + 4,
+ Math.min(containerRect.width - elementWidth / 2 - 4, left + elementWidth / 2));
+ const centerY = Math.max(elementHeight / 2 + 4,
+ Math.min(containerRect.height - elementHeight / 2 - 4, top + elementHeight / 2));
+ lmeState.compassPosition = {
+ x: centerX / containerRect.width,
+ y: centerY / containerRect.height
+ };
+ applyCompassPosition();
+ };
+
+ const finish = event => {
+ if (event.pointerId !== pointerId) return;
+ event.preventDefault();
+ event.stopPropagation();
+ element.classList.remove("is-dragging");
+ if (element.hasPointerCapture?.(pointerId)) {
+ element.releasePointerCapture(pointerId);
+ }
+ pointerId = null;
+ try {
+ localStorage.setItem("lmeCompassPosition", JSON.stringify(lmeState.compassPosition));
+ } catch {
+ }
+ };
+
+ const start = event => {
+ if (event.pointerType === "mouse" && event.button !== 0) return;
+ event.preventDefault();
+ event.stopPropagation();
+ const rect = element.getBoundingClientRect();
+ pointerId = event.pointerId;
+ offsetX = event.clientX - rect.left;
+ offsetY = event.clientY - rect.top;
+ element.classList.add("is-dragging");
+ element.setPointerCapture?.(pointerId);
+ };
+
+ element.addEventListener("pointerdown", start);
+ element.addEventListener("pointermove", move);
+ element.addEventListener("pointerup", finish);
+ element.addEventListener("pointercancel", finish);
+ lmeState.compassDragCleanup = () => {
+ element.removeEventListener("pointerdown", start);
+ element.removeEventListener("pointermove", move);
+ element.removeEventListener("pointerup", finish);
+ element.removeEventListener("pointercancel", finish);
+ };
+}
+
+function setCompassOptions(visible, mode = "fixed") {
lmeState.compassVisible = Boolean(visible);
- lmeState.compassMode = mode === "fixed" ? "fixed" : "heading";
+ lmeState.compassMode = mode === "heading" ? "heading" : "fixed";
if (!lmeState.map || !lmeState.compassLayer) return;
if (lmeState.compassVisible) {
lmeState.managerHiddenCategories.delete("compass");
@@ -5052,10 +5183,14 @@ function loadProjectSnapshot(raw, projectName) {
applyStyleFromProperties(layer);
}
}).addTo(lmeState.selectedParcelsLayer);
- L.geoJSON(data.fiducials || emptyFeatureCollection(), {
+ const restoredFiducials = L.geoJSON(data.fiducials || emptyFeatureCollection(), {
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
- onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer)
- }).addTo(lmeState.fiducials);
+ onEachFeature: (feature, layer) => {
+ bindFiducialPopup(feature, layer);
+ bindManagedMapSelection(layer, "fiducials", lmeState.fiducials);
+ }
+ });
+ restoredFiducials.eachLayer(layer => lmeState.fiducials.addLayer(layer));
if (lmeState.importedSheetsVisible) {
void ensureImportedSheetOutlines().then(() => {
applyImportedSheetVisibility();
@@ -6003,7 +6138,9 @@ function setFiducialPointsVisible(enabled) {
function setFiducialColor(color) {
if (!/^#[0-9a-f]{6}$/i.test(String(color))) return;
lmeState.fiducialColor = color;
- lmeState.fiducials?.eachLayer(layer => {
+ const category = layerManagerCategories().find(item => item.key === "fiducials");
+ managedRecordsForCategory(category).forEach(record => {
+ const layer = record.layer;
if (layer.feature?.properties) {
layer.feature.properties._lmeColor = color;
layer.feature.properties._lmeFillColor = color;