- vendor/leaflet.js (no more CDN dependency for JS)
- direct tile.openstreetmap.org (was {s}.tile)
- backend GET /api/v1/auth/consents (check if all accepted)
- frontend ConsentApi.status() before showing overlay
- only show overlay once
127 lines
4.6 KiB
JavaScript
127 lines
4.6 KiB
JavaScript
// Map view — главный экран
|
||
const MapView = {
|
||
map: null,
|
||
markers: [],
|
||
radius: 5,
|
||
visible: true,
|
||
nearby: [],
|
||
|
||
async render(root) {
|
||
root.innerHTML = `
|
||
<div class="view">
|
||
<h1>🗺️ Карта</h1>
|
||
<div class="card" id="my-info">
|
||
<div class="row">
|
||
<div>
|
||
<strong>Я на карте</strong>
|
||
<div class="privacy-note">Другие пользователи видят твоё местоположение ±300м</div>
|
||
</div>
|
||
<label class="switch">
|
||
<input type="checkbox" id="visible-toggle" ${this.visible ? 'checked' : ''}>
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div id="map" style="height:400px;border-radius:12px;overflow:hidden;margin-bottom:12px;"></div>
|
||
<div class="row">
|
||
<label>Радиус: <strong id="radius-label">${this.radius} км</strong></label>
|
||
<select id="radius-sel" style="background:var(--bg-elev);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px;">
|
||
<option value="1">1 км</option>
|
||
<option value="5" selected>5 км</option>
|
||
<option value="10">10 км</option>
|
||
<option value="25">25 км</option>
|
||
<option value="50">50 км</option>
|
||
</select>
|
||
</div>
|
||
<div id="nearby-count" class="sub">—</div>
|
||
</div>
|
||
`;
|
||
|
||
if (!this.map) {
|
||
this.map = L.map('map').setView([55.7558, 37.6173], 12);
|
||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© OpenStreetMap',
|
||
maxZoom: 19,
|
||
}).addTo(this.map);
|
||
}
|
||
setTimeout(() => this.map.invalidateSize(), 100);
|
||
|
||
document.getElementById('visible-toggle').addEventListener('change', async (e) => {
|
||
this.visible = e.target.checked;
|
||
try {
|
||
await API.Auth.setVisibility(this.visible);
|
||
this.refreshLocation();
|
||
} catch (err) {
|
||
API.Store && console.warn(err);
|
||
TG_APP.showAlert('Не удалось обновить видимость');
|
||
}
|
||
});
|
||
|
||
document.getElementById('radius-sel').addEventListener('change', (e) => {
|
||
this.radius = parseInt(e.target.value, 10);
|
||
document.getElementById('radius-label').textContent = this.radius + ' км';
|
||
this.refreshNearby();
|
||
});
|
||
|
||
this.refreshLocation();
|
||
},
|
||
|
||
async refreshLocation() {
|
||
if (!navigator.geolocation) {
|
||
document.getElementById('nearby-count').textContent = 'Геолокация недоступна';
|
||
return;
|
||
}
|
||
navigator.geolocation.getCurrentPosition(
|
||
async (pos) => {
|
||
const lat = pos.coords.latitude;
|
||
const lng = pos.coords.longitude;
|
||
try {
|
||
await API.Auth.updateLocation(lat, lng, this.visible);
|
||
} catch (e) {
|
||
console.warn(e);
|
||
}
|
||
this.map.setView([lat, lng], 13);
|
||
L.circle([lat, lng], {
|
||
radius: this.radius * 1000,
|
||
color: '#FF6B35', fillColor: '#FF6B35', fillOpacity: 0.1, weight: 2,
|
||
}).addTo(this.map);
|
||
this.refreshNearby();
|
||
},
|
||
(err) => {
|
||
document.getElementById('nearby-count').textContent = 'Нет доступа к GPS: ' + err.message;
|
||
},
|
||
{ enableHighAccuracy: true, timeout: 15000 }
|
||
);
|
||
},
|
||
|
||
async refreshNearby() {
|
||
if (!navigator.geolocation) return;
|
||
navigator.geolocation.getCurrentPosition(async (pos) => {
|
||
try {
|
||
const r = await API.Auth.searchNearby(pos.coords.latitude, pos.coords.longitude, this.radius);
|
||
this.nearby = r.results || [];
|
||
document.getElementById('nearby-count').textContent = this.nearby.length + ' чел. рядом';
|
||
|
||
// удалить старые маркеры (кроме своего круга)
|
||
this.markers.forEach(m => this.map.removeLayer(m));
|
||
this.markers = [];
|
||
|
||
this.nearby.forEach((n) => {
|
||
const icon = L.divIcon({
|
||
className: 'custom-marker',
|
||
html: `<div style="width:36px;height:36px;border-radius:50%;background:#FF6B35;color:white;display:flex;align-items:center;justify-content:center;font-weight:700;border:2px solid white;">${escapeHtml((n.name || '?')[0])}</div>`,
|
||
iconSize: [36, 36],
|
||
});
|
||
const m = L.marker([n.lat, n.lng], { icon }).addTo(this.map);
|
||
m.bindTooltip(n.name + ' • ' + (n.distance_m / 1000).toFixed(1) + ' км', { direction: 'top' });
|
||
m.on('click', () => App.openUserProfile(n.user_id, n.name));
|
||
this.markers.push(m);
|
||
});
|
||
} catch (e) {
|
||
console.warn(e);
|
||
}
|
||
});
|
||
},
|
||
};
|
||
|
||
window.MapView = MapView; |