Add draggable compass and restore project fiducials

This commit is contained in:
livio committed 2026-10-04 23:46:22 +02:00
1 parent aa389f7581
commit a15c8e0b36
4 files changed
+203 -31

No files matched your search

+48 -13
View File
@@ -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 {
+147 -10
View File
@@ -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 = `<span class="map-compass-north">N</span><span class="map-compass-needle" aria-hidden="true"></span><span class="map-compass-center" aria-hidden="true"></span>`;
element.setAttribute("title", "Trascina per spostare la bussola");
element.innerHTML = `<span class="map-compass-rose" aria-hidden="true">
<span class="map-compass-direction is-north" style="--direction-angle:0deg;--label-counter:0deg"><span>N</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:45deg;--label-counter:-45deg"><span>NE</span></span>
<span class="map-compass-direction" style="--direction-angle:90deg;--label-counter:-90deg"><span>E</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:135deg;--label-counter:-135deg"><span>SE</span></span>
<span class="map-compass-direction" style="--direction-angle:180deg;--label-counter:-180deg"><span>S</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:225deg;--label-counter:-225deg"><span>SW</span></span>
<span class="map-compass-direction" style="--direction-angle:270deg;--label-counter:-270deg"><span>W</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:315deg;--label-counter:-315deg"><span>NW</span></span>
<span class="map-compass-needle"></span>
</span><span class="map-compass-center" aria-hidden="true"></span>`;
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;