Refactor: Leaflet → MapLibre GL
This commit is contained in:
parent
8016e2976e
commit
1f02bf04e5
@ -6,8 +6,8 @@
|
|||||||
<meta name="theme-color" content="#FF6B35">
|
<meta name="theme-color" content="#FF6B35">
|
||||||
<title>BuhApp</title>
|
<title>BuhApp</title>
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css">
|
||||||
<script src="/vendor/leaflet.js"></script>
|
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||||
<link rel="stylesheet" href="/css/app.css">
|
<link rel="stylesheet" href="/css/app.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -1,15 +1,38 @@
|
|||||||
// Map view — главный экран
|
// Map view — MapLibre GL (замена Leaflet)
|
||||||
|
// Раньше: Leaflet + OSM тайлы = медленно, блокируется
|
||||||
|
// Сейчас: MapLibre GL + 2GIS raster tiles = WebGL, быстро
|
||||||
const MapView = {
|
const MapView = {
|
||||||
map: null,
|
map: null,
|
||||||
markers: [],
|
selfMarkerEl: null,
|
||||||
placeMarkers: [], // маркеры баров/ресторанов
|
nearbyMarkers: [],
|
||||||
placeMarkersLayer: null,
|
placeMarkers: [],
|
||||||
radius: 5,
|
radius: 5,
|
||||||
visible: true,
|
visible: true,
|
||||||
nearby: [],
|
nearby: [],
|
||||||
places: [],
|
places: [],
|
||||||
showPlaces: true,
|
showPlaces: true,
|
||||||
|
|
||||||
|
// стиль карты — только raster source 2GIS (без API ключа)
|
||||||
|
getMAP_STYLE() {
|
||||||
|
return {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
'2gis': {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: [
|
||||||
|
'https://tile2.maps.2gis.com/tiles?x={x}&y={y}&z={z}',
|
||||||
|
],
|
||||||
|
tileSize: 256,
|
||||||
|
attribution: '© 2GIS',
|
||||||
|
maxzoom: 19,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{ id: '2gis', type: 'raster', source: '2gis', minzoom: 0, maxzoom: 22 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async render(root) {
|
async render(root) {
|
||||||
root.innerHTML = `
|
root.innerHTML = `
|
||||||
<div class="view">
|
<div class="view">
|
||||||
@ -30,7 +53,7 @@ const MapView = {
|
|||||||
</button>
|
</button>
|
||||||
<div class="privacy-note" id="loc-status">Нажми чтобы получить координаты</div>
|
<div class="privacy-note" id="loc-status">Нажми чтобы получить координаты</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="map" style="height:340px;border-radius:12px;overflow:hidden;margin-bottom:12px;"></div>
|
<div id="map" style="height:340px;border-radius:12px;overflow:hidden;margin-bottom:12px;position:relative;"></div>
|
||||||
<div class="row" style="gap:8px;flex-wrap:wrap;">
|
<div class="row" style="gap:8px;flex-wrap:wrap;">
|
||||||
<button class="btn" id="places-toggle" style="width:auto;flex:1;margin:0;font-size:14px;padding:10px;">
|
<button class="btn" id="places-toggle" style="width:auto;flex:1;margin:0;font-size:14px;padding:10px;">
|
||||||
🍻 <span id="places-toggle-text">Скрыть бары</span>
|
🍻 <span id="places-toggle-text">Скрыть бары</span>
|
||||||
@ -48,15 +71,13 @@ const MapView = {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// если старый map привязан к удалённому div — убьём его и создадим новый
|
// убиваем старый map если его контейнер удалён
|
||||||
if (this.map) {
|
if (this.map) {
|
||||||
try {
|
try {
|
||||||
const container = this.map.getContainer();
|
const container = this.map.getContainer();
|
||||||
if (!container || !document.body.contains(container)) {
|
if (!container || !document.body.contains(container)) {
|
||||||
this.map.remove();
|
this.map.remove();
|
||||||
this.map = null;
|
this.map = null;
|
||||||
this.placeMarkersLayer = null;
|
|
||||||
this.markers = [];
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.map = null;
|
this.map = null;
|
||||||
@ -64,14 +85,18 @@ const MapView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.map) {
|
if (!this.map) {
|
||||||
this.map = L.map('map').setView([55.7558, 37.6173], 12);
|
this.map = new maplibregl.Map({
|
||||||
// 2GIS tile layer
|
container: 'map',
|
||||||
L.tileLayer('https://tile2.maps.2gis.com/tiles?x={x}&y={y}&z={z}', {
|
style: this.getMAP_STYLE(),
|
||||||
attribution: '© 2GIS',
|
center: [37.6173, 55.7558],
|
||||||
maxZoom: 19,
|
zoom: 11,
|
||||||
}).addTo(this.map);
|
attributionControl: true,
|
||||||
|
});
|
||||||
|
this.map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
|
||||||
}
|
}
|
||||||
setTimeout(() => this.map.invalidateSize(), 100);
|
|
||||||
|
// invalidateSize — MapLibre делает это через resize()
|
||||||
|
setTimeout(() => { try { this.map.resize(); } catch (e) {} }, 100);
|
||||||
|
|
||||||
document.getElementById('visible-toggle').addEventListener('change', async (e) => {
|
document.getElementById('visible-toggle').addEventListener('change', async (e) => {
|
||||||
this.visible = e.target.checked;
|
this.visible = e.target.checked;
|
||||||
@ -86,7 +111,6 @@ const MapView = {
|
|||||||
|
|
||||||
document.getElementById('radius-sel').addEventListener('change', async (e) => {
|
document.getElementById('radius-sel').addEventListener('change', async (e) => {
|
||||||
this.radius = parseInt(e.target.value, 10);
|
this.radius = parseInt(e.target.value, 10);
|
||||||
document.getElementById('radius-label') && (document.getElementById('radius-label').textContent = this.radius + ' км');
|
|
||||||
if (window._lastKnownPos) {
|
if (window._lastKnownPos) {
|
||||||
await this.refreshNearbyFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude);
|
await this.refreshNearbyFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude);
|
||||||
await this.refreshPlacesFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude);
|
await this.refreshPlacesFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude);
|
||||||
@ -102,20 +126,17 @@ const MapView = {
|
|||||||
this.showPlaces = !this.showPlaces;
|
this.showPlaces = !this.showPlaces;
|
||||||
const txt = document.getElementById('places-toggle-text');
|
const txt = document.getElementById('places-toggle-text');
|
||||||
if (txt) txt.textContent = this.showPlaces ? 'Скрыть бары' : 'Показать бары';
|
if (txt) txt.textContent = this.showPlaces ? 'Скрыть бары' : 'Показать бары';
|
||||||
if (this.placeMarkersLayer) {
|
this.placeMarkers.forEach((m) => {
|
||||||
if (this.showPlaces) this.map.addLayer(this.placeMarkersLayer);
|
const el = m.getElement();
|
||||||
else this.map.removeLayer(this.placeMarkersLayer);
|
if (el) el.style.display = this.showPlaces ? 'block' : 'none';
|
||||||
}
|
});
|
||||||
const list = document.getElementById('places-list');
|
const list = document.getElementById('places-list');
|
||||||
if (list) list.style.display = this.showPlaces ? 'block' : 'none';
|
if (list) list.style.display = this.showPlaces ? 'block' : 'none';
|
||||||
};
|
};
|
||||||
|
|
||||||
// если уже есть сохранённые координаты — покажем на карте (быстро)
|
|
||||||
if (window._lastKnownPos) {
|
if (window._lastKnownPos) {
|
||||||
this.applyPosition(window._lastKnownPos);
|
this.applyPosition(window._lastKnownPos);
|
||||||
}
|
}
|
||||||
// НЕ запрашиваем автоматически — это блокирует UI на iOS Safari.
|
|
||||||
// Юзер нажмёт кнопку "📍 Поделиться геолокацией".
|
|
||||||
},
|
},
|
||||||
|
|
||||||
requestLocation() {
|
requestLocation() {
|
||||||
@ -165,6 +186,15 @@ const MapView = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// helper — создать HTML маркер для MapLibre
|
||||||
|
makeMarkerEl(html, size = 36) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'ml-marker';
|
||||||
|
el.style.cssText = 'width:' + size + 'px;height:' + size + 'px;display:flex;align-items:center;justify-content:center;cursor:pointer;';
|
||||||
|
el.innerHTML = html;
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
|
||||||
async applyPosition(pos) {
|
async applyPosition(pos) {
|
||||||
const lat = pos.coords.latitude;
|
const lat = pos.coords.latitude;
|
||||||
const lng = pos.coords.longitude;
|
const lng = pos.coords.longitude;
|
||||||
@ -173,45 +203,71 @@ const MapView = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('updateLocation:', e);
|
console.warn('updateLocation:', e);
|
||||||
}
|
}
|
||||||
if (this.map) {
|
if (!this.map) return;
|
||||||
// важно: invalidateSize() чтобы Leaflet пересчитал размеры (после смены таба лейаут мог обнулиться)
|
try { this.map.resize(); } catch (e) {}
|
||||||
this.map.invalidateSize();
|
|
||||||
// убираем старый self-marker и круг
|
// убираем старый selfMarker
|
||||||
if (this.selfMarker) { this.map.removeLayer(this.selfMarker); this.selfMarker = null; }
|
if (this.selfMarkerEl) {
|
||||||
this.map.eachLayer((layer) => {
|
this.selfMarkerEl.remove();
|
||||||
if (layer instanceof L.Circle && layer.options && layer.options._isRadius) {
|
this.selfMarkerEl = null;
|
||||||
this.map.removeLayer(layer);
|
|
||||||
}
|
}
|
||||||
});
|
// старый круг радиуса (через source/layer)
|
||||||
|
if (this.map.getLayer && this.map.getLayer('radius-circle')) {
|
||||||
|
this.map.removeLayer('radius-circle');
|
||||||
|
this.map.removeSource('radius-circle');
|
||||||
|
}
|
||||||
|
|
||||||
// маркер "Я"
|
// маркер "Я"
|
||||||
const meIcon = L.divIcon({
|
this.selfMarkerEl = this.makeMarkerEl(
|
||||||
className: 'self-marker',
|
'<div style="width:18px;height:18px;border-radius:50%;background:#4285F4;border:3px solid white;box-shadow:0 0 0 2px rgba(66,133,244,0.4);"></div>',
|
||||||
html: '<div style="width:18px;height:18px;border-radius:50%;background:#4285F4;border:3px solid white;box-shadow:0 0 0 2px rgba(66,133,244,0.4);"></div>',
|
18
|
||||||
iconSize: [18, 18],
|
);
|
||||||
iconAnchor: [9, 9],
|
this.selfMarkerObj = new maplibregl.Marker({ element: this.selfMarkerEl })
|
||||||
|
.setLngLat([lng, lat])
|
||||||
|
.addTo(this.map);
|
||||||
|
|
||||||
|
// круг радиуса — добавляем через GeoJSON source
|
||||||
|
const radiusKm = this.radius;
|
||||||
|
const radiusM = radiusKm * 1000;
|
||||||
|
// создаём круг из 64 точек вокруг центра
|
||||||
|
const center = [lng, lat];
|
||||||
|
const coords = [];
|
||||||
|
const earthR = 6378137;
|
||||||
|
const d2r = Math.PI / 180;
|
||||||
|
const r2d = 180 / Math.PI;
|
||||||
|
for (let i = 0; i <= 64; i++) {
|
||||||
|
const bearing = (i / 64) * 360;
|
||||||
|
const lat1 = lat * d2r;
|
||||||
|
const lng1 = lng * d2r;
|
||||||
|
const brng = bearing * d2r;
|
||||||
|
const lat2 = Math.asin(Math.sin(lat1) * Math.cos(radiusM / earthR) + Math.cos(lat1) * Math.sin(radiusM / earthR) * Math.cos(brng));
|
||||||
|
const lng2 = lng1 + Math.atan2(Math.sin(brng) * Math.sin(radiusM / earthR) * Math.cos(lat1), Math.cos(radiusM / earthR) - Math.sin(lat1) * Math.sin(lat2));
|
||||||
|
coords.push([lng2 * r2d, lat2 * r2d]);
|
||||||
|
}
|
||||||
|
this.map.addSource('radius-circle', {
|
||||||
|
type: 'geojson',
|
||||||
|
data: {
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: { type: 'Polygon', coordinates: [coords] },
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.selfMarker = L.marker([lat, lng], { icon: meIcon, zIndexOffset: 1000 }).addTo(this.map);
|
this.map.addLayer({
|
||||||
// круг радиуса
|
id: 'radius-circle',
|
||||||
L.circle([lat, lng], {
|
type: 'fill',
|
||||||
radius: this.radius * 1000,
|
source: 'radius-circle',
|
||||||
color: '#FF6B35',
|
paint: {
|
||||||
fillColor: '#FF6B35',
|
'fill-color': '#FF6B35',
|
||||||
fillOpacity: 0.1,
|
'fill-opacity': 0.12,
|
||||||
weight: 2,
|
'fill-outline-color': '#FF6B35',
|
||||||
_isRadius: true,
|
},
|
||||||
}).addTo(this.map);
|
});
|
||||||
// центр + zoom на мне
|
|
||||||
this.map.setView([lat, lng], 14, { animate: true });
|
// центр + zoom
|
||||||
|
this.map.flyTo({ center: [lng, lat], zoom: 14, speed: 1.4 });
|
||||||
|
|
||||||
this.refreshNearbyFromPos(lat, lng);
|
this.refreshNearbyFromPos(lat, lng);
|
||||||
this.refreshPlacesFromPos(lat, lng);
|
this.refreshPlacesFromPos(lat, lng);
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async refreshNearby() {
|
|
||||||
if (!navigator.geolocation) return;
|
|
||||||
navigator.geolocation.getCurrentPosition(async (pos) => {
|
|
||||||
this.refreshNearbyFromPos(pos.coords.latitude, pos.coords.longitude);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshNearbyFromPos(lat, lng) {
|
async refreshNearbyFromPos(lat, lng) {
|
||||||
@ -221,19 +277,22 @@ const MapView = {
|
|||||||
const c = document.getElementById('nearby-count');
|
const c = document.getElementById('nearby-count');
|
||||||
if (c) c.textContent = '👥 ' + this.nearby.length + ' чел. рядом';
|
if (c) c.textContent = '👥 ' + this.nearby.length + ' чел. рядом';
|
||||||
|
|
||||||
this.markers.forEach(m => this.map.removeLayer(m));
|
// убираем старые маркеры людей
|
||||||
this.markers = [];
|
this.nearbyMarkers.forEach((m) => m.remove());
|
||||||
|
this.nearbyMarkers = [];
|
||||||
|
|
||||||
this.nearby.forEach((n) => {
|
this.nearby.forEach((n) => {
|
||||||
const icon = L.divIcon({
|
const el = this.makeMarkerEl(
|
||||||
className: 'custom-marker',
|
'<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;">' +
|
||||||
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>`,
|
escapeHtml((n.name || '?')[0]) +
|
||||||
iconSize: [36, 36],
|
'</div>'
|
||||||
});
|
);
|
||||||
const m = L.marker([n.lat, n.lng], { icon }).addTo(this.map);
|
el.title = (n.name || '') + ' • ' + (n.distance_m / 1000).toFixed(1) + ' км';
|
||||||
m.bindTooltip(n.name + ' • ' + (n.distance_m / 1000).toFixed(1) + ' км', { direction: 'top' });
|
el.onclick = () => App.openUserProfile(n.user_id, n.name);
|
||||||
m.on('click', () => App.openUserProfile(n.user_id, n.name));
|
const m = new maplibregl.Marker({ element: el })
|
||||||
this.markers.push(m);
|
.setLngLat([n.lng, n.lat])
|
||||||
|
.addTo(this.map);
|
||||||
|
this.nearbyMarkers.push(m);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const c = document.getElementById('nearby-count');
|
const c = document.getElementById('nearby-count');
|
||||||
@ -246,26 +305,22 @@ const MapView = {
|
|||||||
try {
|
try {
|
||||||
const r = await API.PlacesApi.nearby(lat, lng, this.radius, 50);
|
const r = await API.PlacesApi.nearby(lat, lng, this.radius, 50);
|
||||||
this.places = r.places || [];
|
this.places = r.places || [];
|
||||||
// маркеры
|
// убираем старые маркеры мест
|
||||||
if (this.placeMarkersLayer) this.map.removeLayer(this.placeMarkersLayer);
|
this.placeMarkers.forEach((m) => m.remove());
|
||||||
this.placeMarkersLayer = L.layerGroup();
|
this.placeMarkers = [];
|
||||||
this.places.forEach((p) => {
|
this.places.forEach((p) => {
|
||||||
const icon = L.divIcon({
|
const el = this.makeMarkerEl(
|
||||||
className: 'place-marker',
|
'<div style="width:32px;height:32px;border-radius:8px;background:#FFD8C2;color:#FF6B35;display:flex;align-items:center;justify-content:center;font-weight:700;border:2px solid #FF6B35;font-size:18px;">' +
|
||||||
html: `<div style="width:32px;height:32px;border-radius:8px;background:#FFD8C2;color:#FF6B35;display:flex;align-items:center;justify-content:center;font-weight:700;border:2px solid #FF6B35;font-size:18px;">${placeEmoji(p.type)}</div>`,
|
placeEmoji(p.type) +
|
||||||
iconSize: [32, 32],
|
'</div>'
|
||||||
});
|
|
||||||
const m = L.marker([p.lat, p.lng], { icon });
|
|
||||||
m.bindTooltip(
|
|
||||||
`<b>${escapeHtml(p.name)}</b><br>${placeTypeName(p.type)} • ${(p.distance_m/1000).toFixed(1)} км${p.cuisine ? '<br>🍴 ' + escapeHtml(p.cuisine) : ''}`,
|
|
||||||
{ direction: 'top' }
|
|
||||||
);
|
);
|
||||||
m.on('click', () => this.openPlaceCard(p));
|
el.title = (p.name || '') + ' • ' + (p.distance_m/1000).toFixed(1) + ' км';
|
||||||
this.placeMarkersLayer.addLayer(m);
|
el.onclick = () => this.openPlaceCard(p);
|
||||||
|
const m = new maplibregl.Marker({ element: el })
|
||||||
|
.setLngLat([p.lng, p.lat])
|
||||||
|
.addTo(this.map);
|
||||||
|
this.placeMarkers.push(m);
|
||||||
});
|
});
|
||||||
if (this.showPlaces) this.placeMarkersLayer.addTo(this.map);
|
|
||||||
|
|
||||||
// список снизу
|
|
||||||
this.renderPlacesList();
|
this.renderPlacesList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('refreshPlaces:', e);
|
console.warn('refreshPlaces:', e);
|
||||||
@ -290,7 +345,6 @@ const MapView = {
|
|||||||
<button class="btn ghost" data-invite-place="${p.osm_id}" style="width:auto;margin:0;padding:6px 10px;font-size:12px;">📨</button>
|
<button class="btn ghost" data-invite-place="${p.osm_id}" style="width:auto;margin:0;padding:6px 10px;font-size:12px;">📨</button>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
// клики по карточкам
|
|
||||||
box.querySelectorAll('.card.clickable').forEach((el) => {
|
box.querySelectorAll('.card.clickable').forEach((el) => {
|
||||||
el.onclick = () => {
|
el.onclick = () => {
|
||||||
const id = parseInt(el.dataset.placeId, 10);
|
const id = parseInt(el.dataset.placeId, 10);
|
||||||
@ -314,13 +368,11 @@ const MapView = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async inviteToPlace(p) {
|
async inviteToPlace(p) {
|
||||||
// открываем список чатов для выбора кому предложить встречу
|
|
||||||
const chats = await API.ChatApi.listChats();
|
const chats = await API.ChatApi.listChats();
|
||||||
if (!chats.chats || !chats.chats.length) {
|
if (!chats.chats || !chats.chats.length) {
|
||||||
TG_APP.showAlert('Сначала начни чат с кем-нибудь');
|
TG_APP.showAlert('Сначала начни чат с кем-нибудь');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// простая реализация — показать список через confirm
|
|
||||||
const list = chats.chats.slice(0, 5).map((c, i) => `${i+1}. ${c.other_name || c.otherName || 'собеседник'}`).join('\n');
|
const list = chats.chats.slice(0, 5).map((c, i) => `${i+1}. ${c.other_name || c.otherName || 'собеседник'}`).join('\n');
|
||||||
TG_APP.showConfirm(
|
TG_APP.showConfirm(
|
||||||
`Отправить предложение встретиться в "${p.name}"?\n\nТвои чаты:\n${list}\n\nПредложение будет отправлено в самый новый чат.`
|
`Отправить предложение встретиться в "${p.name}"?\n\nТвои чаты:\n${list}\n\nПредложение будет отправлено в самый новый чат.`
|
||||||
@ -329,7 +381,8 @@ const MapView = {
|
|||||||
const target = chats.chats[0];
|
const target = chats.chats[0];
|
||||||
const text = `🍻 Давай встретимся здесь: ${p.name}\n📍 https://2gis.ru/?m=${p.lng.toFixed(6)}%2C${p.lat.toFixed(6)}%2F16\n${p.address ? '🏠 ' + p.address : ''}`;
|
const text = `🍻 Давай встретимся здесь: ${p.name}\n📍 https://2gis.ru/?m=${p.lng.toFixed(6)}%2C${p.lat.toFixed(6)}%2F16\n${p.address ? '🏠 ' + p.address : ''}`;
|
||||||
try {
|
try {
|
||||||
await API.ChatApi.sendMessage(target.chat.id || target.chat.id, text);
|
const chatId = target.chat?.id || target.id;
|
||||||
|
await API.ChatApi.sendMessage(chatId, text);
|
||||||
TG_APP.showAlert('Приглашение отправлено в чат с ' + (target.other_name || 'собеседником'));
|
TG_APP.showAlert('Приглашение отправлено в чат с ' + (target.other_name || 'собеседником'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
TG_APP.showAlert('Ошибка: ' + e.message);
|
TG_APP.showAlert('Ошибка: ' + e.message);
|
||||||
|
|||||||
6
public/vendor/leaflet.js
vendored
6
public/vendor/leaflet.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user