feat: add continuous GPS tracking, pulsing location marker, and distance calculation to inspection maps
This commit is contained in:
parent
97ecd8d9fb
commit
c8349a3171
3 changed files with 255 additions and 31 deletions
|
|
@ -290,7 +290,7 @@
|
||||||
'match', ['coalesce', ['get', 'structure_type'], ''],
|
'match', ['coalesce', ['get', 'structure_type'], ''],
|
||||||
['massif drainant', 'tranchee drainante', 'drain', 'collecteur en beton poreux', 'reseau egout separatif', 'oed'], '#8d6e63',
|
['massif drainant', 'tranchee drainante', 'drain', 'collecteur en beton poreux', 'reseau egout separatif', 'oed'], '#8d6e63',
|
||||||
['noue', 'jardin de pluie', 'bande filtrante', 'evc'], '#009688',
|
['noue', 'jardin de pluie', 'bande filtrante', 'evc'], '#009688',
|
||||||
['filet eau', 'caniveau infiltrant', 'gargouille de trottoir', 'avaloir de rail', 'trop plein', 'ta'], '#0277bd',
|
['filet eau', 'caniveau infiltrant', 'gargouille de trottoir', 'avaloir de rail', 'trop plein', 'ta'], '#455a64',
|
||||||
'#0288d1'
|
'#0288d1'
|
||||||
],
|
],
|
||||||
'line-width': 2.5
|
'line-width': 2.5
|
||||||
|
|
@ -343,11 +343,13 @@
|
||||||
['==', ['coalesce', ['get', 'status'], ''], 'inactive'], '#343a40',
|
['==', ['coalesce', ['get', 'status'], ''], 'inactive'], '#343a40',
|
||||||
'#198754'
|
'#198754'
|
||||||
],
|
],
|
||||||
|
['match', ['coalesce', ['get', 'model_name'], ''], ['roaddrain', 'drain'], true, false], '#37474f',
|
||||||
[
|
[
|
||||||
'match', ['coalesce', ['get', 'structure_type'], ''],
|
'match', ['coalesce', ['get', 'structure_type'], ''],
|
||||||
['massif drainant', 'tranchee drainante', 'oed'], '#8d6e63',
|
['massif drainant', 'tranchee drainante', 'oed'], '#8d6e63',
|
||||||
['jardin de pluie', 'noue', 'evc'], '#009688',
|
['jardin de pluie', 'noue', 'evc'], '#009688',
|
||||||
['bassin orage', 'citerne', 'ta'], '#0277bd',
|
['bassin orage', 'citerne', 'ta'], '#0277bd',
|
||||||
|
['avaloir', 'avaloir de rail', 'bouche injection', 'filet eau', 'caniveau infiltrant', 'gargouille de trottoir', 'puits infiltration', 'drain', 'regard', 'vanne regulation', 'vanne isolation'], '#37474f',
|
||||||
'#0288d1'
|
'#0288d1'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
@ -731,51 +733,99 @@
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let gpsWatchId = null;
|
||||||
|
let isFirstGpsFix = true;
|
||||||
|
|
||||||
function setupGeolocation() {
|
function setupGeolocation() {
|
||||||
const gpsBtn = document.getElementById('btn-gps-locate');
|
const gpsBtn = document.getElementById('btn-gps-locate');
|
||||||
if (gpsBtn) {
|
if (gpsBtn) {
|
||||||
gpsBtn.addEventListener('click', locateUser);
|
gpsBtn.addEventListener('click', function() {
|
||||||
|
if (window.currentGpsPosition && map) {
|
||||||
|
map.flyTo({
|
||||||
|
center: [window.currentGpsPosition.lng, window.currentGpsPosition.lat],
|
||||||
|
zoom: 18,
|
||||||
|
duration: 800
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
startGpsTracking(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasPreselect = (window.PRESELECT_LON !== null && window.PRESELECT_LAT !== null && typeof window.PRESELECT_LON !== 'undefined' && typeof window.PRESELECT_LAT !== 'undefined');
|
const hasPreselect = (window.PRESELECT_LON !== null && window.PRESELECT_LAT !== null && typeof window.PRESELECT_LON !== 'undefined' && typeof window.PRESELECT_LAT !== 'undefined');
|
||||||
if (!hasPreselect) {
|
startGpsTracking(!hasPreselect);
|
||||||
locateUser();
|
|
||||||
|
window.addEventListener('beforeunload', stopGpsTracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopGpsTracking() {
|
||||||
|
if (gpsWatchId !== null && navigator.geolocation) {
|
||||||
|
navigator.geolocation.clearWatch(gpsWatchId);
|
||||||
|
gpsWatchId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function locateUser() {
|
function locateUser() {
|
||||||
|
startGpsTracking(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGpsTracking(shouldFlyTo = false) {
|
||||||
if (!navigator.geolocation) return;
|
if (!navigator.geolocation) return;
|
||||||
|
|
||||||
const i18n = window.INSPECTION_MAP_I18N || {};
|
const i18n = window.INSPECTION_MAP_I18N || {};
|
||||||
const statusText = document.getElementById('gps-status-text');
|
const statusText = document.getElementById('gps-status-text');
|
||||||
if (statusText) statusText.textContent = i18n.geolocation || (typeof gettext !== 'undefined' ? gettext('Géolocalisation...') : 'Géolocalisation...');
|
if (statusText) statusText.textContent = i18n.geolocation || (typeof gettext !== 'undefined' ? gettext('Géolocalisation...') : 'Géolocalisation...');
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
if (gpsWatchId !== null) {
|
||||||
|
navigator.geolocation.clearWatch(gpsWatchId);
|
||||||
|
gpsWatchId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpsWatchId = navigator.geolocation.watchPosition(
|
||||||
function(position) {
|
function(position) {
|
||||||
const lng = position.coords.longitude;
|
const lng = position.coords.longitude;
|
||||||
const lat = position.coords.latitude;
|
const lat = position.coords.latitude;
|
||||||
|
const accuracy = position.coords.accuracy;
|
||||||
|
|
||||||
|
window.currentGpsPosition = { lng, lat, accuracy, timestamp: Date.now() };
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.dispatchEvent(new CustomEvent('loko:gps-update', {
|
||||||
|
detail: window.currentGpsPosition
|
||||||
|
}));
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
if (typeof maplibregl !== 'undefined' && maplibregl.Marker && map) {
|
if (typeof maplibregl !== 'undefined' && maplibregl.Marker && map) {
|
||||||
if (userMarker) {
|
if (userMarker) {
|
||||||
userMarker.setLngLat([lng, lat]);
|
userMarker.setLngLat([lng, lat]);
|
||||||
} else {
|
} else {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'user-gps-dot';
|
el.className = 'user-gps-dot-container';
|
||||||
|
el.innerHTML = '<div class="user-gps-dot-pulse"></div><div class="user-gps-dot"></div>';
|
||||||
|
|
||||||
userMarker = new maplibregl.Marker({ element: el })
|
userMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
|
||||||
.setLngLat([lng, lat])
|
.setLngLat([lng, lat])
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.flyTo({ center: [lng, lat], zoom: 17 });
|
if (shouldFlyTo || isFirstGpsFix) {
|
||||||
|
map.flyTo({ center: [lng, lat], zoom: 18, duration: 1000 });
|
||||||
|
isFirstGpsFix = false;
|
||||||
|
shouldFlyTo = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = i18n.positionUpdated || (typeof gettext !== 'undefined' ? gettext('Position actualisée') : 'Position actualisée');
|
||||||
}
|
}
|
||||||
if (statusText) statusText.textContent = i18n.positionUpdated || (typeof gettext !== 'undefined' ? gettext('Position actualisée') : 'Position actualisée');
|
|
||||||
},
|
},
|
||||||
function(err) {
|
function(err) {
|
||||||
console.warn('GPS error:', err.message);
|
console.warn('GPS watchPosition error:', err.message);
|
||||||
if (statusText) statusText.textContent = i18n.gpsUnavailable || (typeof gettext !== 'undefined' ? gettext('GPS indisponible') : 'GPS indisponible');
|
if (statusText) {
|
||||||
|
statusText.textContent = i18n.gpsUnavailable || (typeof gettext !== 'undefined' ? gettext('GPS indisponible') : 'GPS indisponible');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 5000 }
|
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 2000 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Conteneur Carte Agrandie -->
|
<!-- Conteneur Carte Agrandie -->
|
||||||
<div id="giep_modal_map_container" class="rounded-3 border overflow-hidden" style="height: 480px; max-height: 68vh; position: relative;">
|
<div id="giep_modal_map_container" class="rounded-3 border overflow-hidden position-relative" style="height: 480px; max-height: 68vh;">
|
||||||
<div id="giepModalMap" style="width: 100%; height: 100%;"></div>
|
<div id="giepModalMap" style="width: 100%; height: 100%;"></div>
|
||||||
|
<!-- Bouton Recentrer sur ma position GPS -->
|
||||||
|
<button type="button" id="giep_modal_recenter_user_btn" class="btn btn-light btn-sm shadow rounded-circle border position-absolute d-flex align-items-center justify-content-center" style="bottom: 16px; right: 16px; z-index: 10; width: 44px; height: 44px;" title="{% translate 'Ma position GPS' %}">
|
||||||
|
<i class="bi bi-crosshair text-primary fs-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer py-2 px-3 bg-light">
|
<div class="modal-footer py-2 px-3 bg-light">
|
||||||
|
|
@ -153,6 +157,39 @@
|
||||||
.btn-seg:hover {
|
.btn-seg:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
.user-gps-dot-container {
|
||||||
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.user-gps-dot-pulse {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(111, 66, 193, 0.35);
|
||||||
|
animation: gpsPulse 2s infinite ease-out;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.user-gps-dot {
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #6f42c1;
|
||||||
|
border: 2.5px solid #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 8px rgba(111, 66, 193, 0.65), 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
@keyframes gpsPulse {
|
||||||
|
0% { transform: scale(0.5); opacity: 1; }
|
||||||
|
70% { transform: scale(1.6); opacity: 0; }
|
||||||
|
100% { transform: scale(1.6); opacity: 0; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -460,6 +497,18 @@
|
||||||
|
|
||||||
let currentMapPopup = null;
|
let currentMapPopup = null;
|
||||||
|
|
||||||
|
function computeDistanceMeters(lat1, lon1, lat2, lon2) {
|
||||||
|
if (!lat1 || !lon1 || !lat2 || !lon2) return null;
|
||||||
|
const R = 6371000;
|
||||||
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
||||||
|
const dLon = (lon2 - lon1) * Math.PI / 180;
|
||||||
|
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
||||||
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
||||||
|
Math.sin(dLon/2) * Math.sin(dLon/2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
function showAssetPopup(asset, lngLat) {
|
function showAssetPopup(asset, lngLat) {
|
||||||
if (!modalMapInstance) return;
|
if (!modalMapInstance) return;
|
||||||
if (currentMapPopup) {
|
if (currentMapPopup) {
|
||||||
|
|
@ -474,6 +523,18 @@
|
||||||
else if (devType === 'ta') badgeHtml = '<span class="badge bg-teal-subtle text-teal text-uppercase me-1">TA</span>';
|
else if (devType === 'ta') badgeHtml = '<span class="badge bg-teal-subtle text-teal text-uppercase me-1">TA</span>';
|
||||||
else badgeHtml = '<span class="badge bg-secondary-subtle text-secondary text-uppercase me-1">Équipement</span>';
|
else badgeHtml = '<span class="badge bg-secondary-subtle text-secondary text-uppercase me-1">Équipement</span>';
|
||||||
|
|
||||||
|
let distanceBadgeHtml = '';
|
||||||
|
if (window.currentGpsPosition && lngLat) {
|
||||||
|
const d = computeDistanceMeters(
|
||||||
|
window.currentGpsPosition.lat, window.currentGpsPosition.lng,
|
||||||
|
lngLat.lat, lngLat.lng
|
||||||
|
);
|
||||||
|
if (d !== null && !isNaN(d)) {
|
||||||
|
const distStr = d < 1000 ? `${Math.round(d)} m` : `${(d / 1000).toFixed(1)} km`;
|
||||||
|
distanceBadgeHtml = `<div class="mb-2"><span class="badge bg-light text-primary border extra-small d-inline-flex align-items-center gap-1 shadow-2xs"><i class="bi bi-geo-alt-fill text-danger"></i>~ ${distStr}</span></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const popupNode = document.createElement('div');
|
const popupNode = document.createElement('div');
|
||||||
popupNode.className = 'p-1';
|
popupNode.className = 'p-1';
|
||||||
popupNode.innerHTML = `
|
popupNode.innerHTML = `
|
||||||
|
|
@ -486,6 +547,7 @@
|
||||||
${badgeHtml}
|
${badgeHtml}
|
||||||
<span>${asset.structure_type || ''}</span>
|
<span>${asset.structure_type || ''}</span>
|
||||||
</div>
|
</div>
|
||||||
|
${distanceBadgeHtml}
|
||||||
<button type="button" class="btn btn-primary btn-sm w-100 rounded-pill py-1.5 px-3 fw-semibold select-popup-btn">
|
<button type="button" class="btn btn-primary btn-sm w-100 rounded-pill py-1.5 px-3 fw-semibold select-popup-btn">
|
||||||
<i class="bi bi-check-circle me-1"></i>{% filter escapejs %}{% translate "Sélectionner" %}{% endfilter %}
|
<i class="bi bi-check-circle me-1"></i>{% filter escapejs %}{% translate "Sélectionner" %}{% endfilter %}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1055,10 +1117,14 @@
|
||||||
|
|
||||||
if (matchText && matchType && asset.rawFeature && typeof maplibregl !== 'undefined') {
|
if (matchText && matchType && asset.rawFeature && typeof maplibregl !== 'undefined') {
|
||||||
const geom = asset.rawFeature.geometry;
|
const geom = asset.rawFeature.geometry;
|
||||||
let color = '#0277bd'; // Bleu GIEP (naturerwiasset)
|
let color = '#0277bd'; // Bleu GIEP par défaut
|
||||||
|
const descStr = ((asset.structure_type || '') + ' ' + (asset.name || '')).toLowerCase();
|
||||||
|
const isPointAccess = descStr.includes('avaloir') || descStr.includes('injection') || descStr.includes('regard') || descStr.includes('grille') || descStr.includes('caniveau') || descStr.includes('filet eau');
|
||||||
|
|
||||||
if (devType === 'evc') color = '#2e7d32'; // Vert EVC
|
if (devType === 'evc') color = '#2e7d32'; // Vert EVC
|
||||||
if (devType === 'oed') color = '#0277bd'; // Bleu OED
|
else if (devType === 'oed') color = '#8d6e63'; // Brun OED
|
||||||
if (devType === 'ta') color = '#00796b'; // Turquoise TA
|
else if (devType === 'ta') color = '#00796b'; // Turquoise TA
|
||||||
|
else if (isPointAccess) color = '#37474f'; // Gris ardoise (avaloir, regard, bouche d'injection)
|
||||||
|
|
||||||
if (geom) {
|
if (geom) {
|
||||||
const featureClone = {
|
const featureClone = {
|
||||||
|
|
@ -1204,9 +1270,65 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cadrer la carte sur les assets
|
// Cadrer la carte sur les assets et la position GPS
|
||||||
if (hasCoords && bounds && !bounds.isEmpty()) {
|
if (hasCoords && bounds && !bounds.isEmpty()) {
|
||||||
modalMapInstance.fitBounds(bounds, { padding: 50, maxZoom: 18, duration: 300 });
|
if (window.currentGpsPosition) {
|
||||||
|
const userLng = window.currentGpsPosition.lng;
|
||||||
|
const userLat = window.currentGpsPosition.lat;
|
||||||
|
const centerLngLat = bounds.getCenter();
|
||||||
|
const distToCenter = computeDistanceMeters(userLat, userLng, centerLngLat.lat, centerLngLat.lng);
|
||||||
|
if (distToCenter !== null && distToCenter < 1200) {
|
||||||
|
bounds.extend([userLng, userLat]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modalMapInstance.fitBounds(bounds, { padding: 50, maxZoom: 18, duration: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let modalMapUserMarker = null;
|
||||||
|
let modalGpsWatchId = null;
|
||||||
|
|
||||||
|
function updateModalMapGpsPosition(pos) {
|
||||||
|
if (!pos || !modalMapInstance) return;
|
||||||
|
const lng = pos.lng || pos.coords?.longitude;
|
||||||
|
const lat = pos.lat || pos.coords?.latitude;
|
||||||
|
if (!lng || !lat) return;
|
||||||
|
|
||||||
|
if (modalMapUserMarker) {
|
||||||
|
modalMapUserMarker.setLngLat([lng, lat]);
|
||||||
|
} else if (typeof maplibregl !== 'undefined') {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'user-gps-dot-container';
|
||||||
|
el.innerHTML = '<div class="user-gps-dot-pulse"></div><div class="user-gps-dot"></div>';
|
||||||
|
modalMapUserMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
|
||||||
|
.setLngLat([lng, lat])
|
||||||
|
.addTo(modalMapInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startModalGpsTracking() {
|
||||||
|
if (!navigator.geolocation) return;
|
||||||
|
if (modalGpsWatchId !== null) return;
|
||||||
|
modalGpsWatchId = navigator.geolocation.watchPosition(
|
||||||
|
(position) => {
|
||||||
|
const pos = {
|
||||||
|
lng: position.coords.longitude,
|
||||||
|
lat: position.coords.latitude,
|
||||||
|
accuracy: position.coords.accuracy,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
window.currentGpsPosition = pos;
|
||||||
|
updateModalMapGpsPosition(pos);
|
||||||
|
},
|
||||||
|
(err) => console.warn('Modal GPS watchPosition error:', err.message),
|
||||||
|
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 2000 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopModalGpsTracking() {
|
||||||
|
if (modalGpsWatchId !== null && navigator.geolocation) {
|
||||||
|
navigator.geolocation.clearWatch(modalGpsWatchId);
|
||||||
|
modalGpsWatchId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1220,30 +1342,27 @@
|
||||||
modalMapInstance = new maplibregl.Map({
|
modalMapInstance = new maplibregl.Map({
|
||||||
container: 'giepModalMap',
|
container: 'giepModalMap',
|
||||||
style: style,
|
style: style,
|
||||||
center: [4.3517, 50.8503],
|
center: (window.currentGpsPosition) ? [window.currentGpsPosition.lng, window.currentGpsPosition.lat] : [4.3517, 50.8503],
|
||||||
zoom: 15,
|
zoom: 16,
|
||||||
antialias: true
|
antialias: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Contrôles de navigation et de géolocalisation
|
// Contrôles de navigation
|
||||||
modalMapInstance.addControl(new maplibregl.NavigationControl(), 'top-right');
|
modalMapInstance.addControl(new maplibregl.NavigationControl(), 'top-right');
|
||||||
|
|
||||||
const geolocate = new maplibregl.GeolocateControl({
|
|
||||||
positionOptions: { enableHighAccuracy: true },
|
|
||||||
trackUserLocation: true,
|
|
||||||
showUserLocation: true
|
|
||||||
});
|
|
||||||
modalMapInstance.addControl(geolocate, 'top-right');
|
|
||||||
|
|
||||||
modalMapInstance.on('load', () => {
|
modalMapInstance.on('load', () => {
|
||||||
|
updateModalMapGpsPosition(window.currentGpsPosition);
|
||||||
renderModalMapFeatures(true);
|
renderModalMapFeatures(true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
modalMapInstance.resize();
|
modalMapInstance.resize();
|
||||||
|
updateModalMapGpsPosition(window.currentGpsPosition);
|
||||||
renderModalMapFeatures(true);
|
renderModalMapFeatures(true);
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startModalGpsTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMapModal(idx) {
|
function openMapModal(idx) {
|
||||||
|
|
@ -2031,10 +2150,40 @@
|
||||||
});
|
});
|
||||||
modalEl.addEventListener('hidden.bs.modal', (e) => {
|
modalEl.addEventListener('hidden.bs.modal', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
stopModalGpsTracking();
|
||||||
toggleParentGlobalSections(true);
|
toggleParentGlobalSections(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('loko:gps-update', (e) => {
|
||||||
|
updateModalMapGpsPosition(e.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
const recenterBtn = rootModal.querySelector('#giep_modal_recenter_user_btn') || document.getElementById('giep_modal_recenter_user_btn');
|
||||||
|
if (recenterBtn) {
|
||||||
|
recenterBtn.addEventListener('click', () => {
|
||||||
|
if (window.currentGpsPosition && modalMapInstance) {
|
||||||
|
modalMapInstance.flyTo({
|
||||||
|
center: [window.currentGpsPosition.lng, window.currentGpsPosition.lat],
|
||||||
|
zoom: 18,
|
||||||
|
duration: 800
|
||||||
|
});
|
||||||
|
} else if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(pos => {
|
||||||
|
const lng = pos.coords.longitude;
|
||||||
|
const lat = pos.coords.latitude;
|
||||||
|
window.currentGpsPosition = { lng, lat, accuracy: pos.coords.accuracy, timestamp: Date.now() };
|
||||||
|
updateModalMapGpsPosition(window.currentGpsPosition);
|
||||||
|
if (modalMapInstance) {
|
||||||
|
modalMapInstance.flyTo({ center: [lng, lat], zoom: 18, duration: 800 });
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
console.warn('GPS error:', err);
|
||||||
|
}, { enableHighAccuracy: true, timeout: 8000 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const searchInput = rootModal.querySelector('#giep_modal_search_input, .giep-modal-search-input');
|
const searchInput = rootModal.querySelector('#giep_modal_search_input, .giep-modal-search-input');
|
||||||
const typeFilter = rootModal.querySelector('#giep_modal_type_filter, .giep-modal-type-filter');
|
const typeFilter = rootModal.querySelector('#giep_modal_type_filter, .giep-modal-type-filter');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,38 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
.user-gps-dot-container {
|
||||||
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.user-gps-dot-pulse {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(111, 66, 193, 0.35);
|
||||||
|
animation: gpsPulse 2s infinite ease-out;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
.user-gps-dot {
|
.user-gps-dot {
|
||||||
|
position: relative;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: #0d6efd;
|
background-color: #6f42c1;
|
||||||
border: 2px solid #ffffff;
|
border: 2.5px solid #ffffff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 0 10px rgba(13, 110, 253, 0.8);
|
box-shadow: 0 2px 8px rgba(111, 66, 193, 0.65), 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
@keyframes gpsPulse {
|
||||||
|
0% { transform: scale(0.5); opacity: 1; }
|
||||||
|
70% { transform: scale(1.6); opacity: 0; }
|
||||||
|
100% { transform: scale(1.6); opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bottom Sheet Modal Styling for Mobile */
|
/* Bottom Sheet Modal Styling for Mobile */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue