Add draggable compass and restore project fiducials
This commit is contained in:
1 parent
aa389f7581
commit
a15c8e0b36
4 files changed
+203
-31
No files matched your search
+48
-13
@@ -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
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user