Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -32,8 +32,8 @@ def index():
|
|
32 |
<title>TON AR Hotspots</title>
|
33 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
34 |
<script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
|
35 |
-
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
|
36 |
-
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
|
37 |
<style>
|
38 |
body, html {
|
39 |
margin: 0;
|
@@ -111,64 +111,67 @@ def index():
|
|
111 |
z-index: 10;
|
112 |
text-align: center;
|
113 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
.hotspot.hidden {
|
115 |
opacity: 0;
|
116 |
pointer-events: none;
|
117 |
}
|
118 |
-
#
|
119 |
position: fixed;
|
120 |
bottom: 10px;
|
121 |
left: 10px;
|
122 |
width: 200px;
|
123 |
height: 200px;
|
124 |
-
background-color:
|
125 |
-
border: 2px solid #555;
|
126 |
border-radius: 10px;
|
127 |
overflow: hidden;
|
128 |
-
z-index: 20;
|
129 |
-
box-shadow: 0 0 15px rgba(0,0,0,0.7);
|
130 |
transition: all 0.3s ease;
|
|
|
131 |
display: flex;
|
132 |
flex-direction: column;
|
133 |
}
|
134 |
-
#
|
135 |
width: 100%;
|
136 |
height: 100%;
|
137 |
top: 0;
|
138 |
left: 0;
|
139 |
border-radius: 0;
|
|
|
|
|
140 |
}
|
141 |
#map {
|
142 |
flex-grow: 1;
|
143 |
width: 100%;
|
|
|
|
|
|
|
|
|
|
|
144 |
height: 100%;
|
|
|
145 |
}
|
146 |
#toggle-map-button {
|
147 |
-
|
148 |
-
top: 5px;
|
149 |
-
right: 5px;
|
150 |
-
z-index: 21;
|
151 |
-
background-color: rgba(0,0,0,0.6);
|
152 |
color: white;
|
153 |
border: none;
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
font-size: 18px;
|
158 |
cursor: pointer;
|
159 |
-
|
160 |
-
align-
|
161 |
-
justify-content: center;
|
162 |
}
|
163 |
-
#
|
|
|
164 |
top: 10px;
|
165 |
right: 10px;
|
166 |
-
|
167 |
-
|
168 |
-
z-index: auto !important;
|
169 |
-
}
|
170 |
-
.leaflet-control-attribution.leaflet-control a {
|
171 |
-
color: #fff;
|
172 |
}
|
173 |
</style>
|
174 |
</head>
|
@@ -186,9 +189,9 @@ def index():
|
|
186 |
<video id="camera-view" playsinline autoplay muted></video>
|
187 |
<div id="ar-container"></div>
|
188 |
|
189 |
-
<div id="
|
190 |
-
<button id="toggle-map-button">⤢</button>
|
191 |
<div id="map"></div>
|
|
|
192 |
</div>
|
193 |
|
194 |
<script>
|
@@ -201,12 +204,14 @@ def index():
|
|
201 |
hotspots: [],
|
202 |
currentUserPosition: null,
|
203 |
deviceOrientation: { alpha: 0, beta: 0, gamma: 0 },
|
204 |
-
cameraFov: 60
|
|
|
|
|
|
|
|
|
205 |
};
|
206 |
|
207 |
-
|
208 |
-
let userMarker;
|
209 |
-
let hotspotMarkers = [];
|
210 |
|
211 |
async function fetchBalance(address) {
|
212 |
try {
|
@@ -224,7 +229,7 @@ def index():
|
|
224 |
await setupCamera();
|
225 |
setupGPS();
|
226 |
setupOrientationListener();
|
227 |
-
|
228 |
await loadHotspots();
|
229 |
setupAddHotspotListener();
|
230 |
requestAnimationFrame(update);
|
@@ -267,13 +272,11 @@ def index():
|
|
267 |
lat: position.coords.latitude,
|
268 |
lon: position.coords.longitude
|
269 |
};
|
270 |
-
if (
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
} else {
|
276 |
-
map.panTo(latLng);
|
277 |
}
|
278 |
}
|
279 |
},
|
@@ -301,26 +304,25 @@ def index():
|
|
301 |
}
|
302 |
}
|
303 |
|
304 |
-
function
|
305 |
-
map = L.map('map'
|
306 |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
307 |
-
attribution: '©
|
308 |
-
}).addTo(map);
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
});
|
324 |
}
|
325 |
|
326 |
async function loadHotspots() {
|
@@ -329,6 +331,7 @@ def index():
|
|
329 |
const data = await response.json();
|
330 |
state.hotspots = data;
|
331 |
renderHotspots();
|
|
|
332 |
} catch (error) {
|
333 |
console.error('Ошибка загрузки хотспотов:', error);
|
334 |
}
|
@@ -337,36 +340,26 @@ def index():
|
|
337 |
function renderHotspots() {
|
338 |
const container = document.getElementById('ar-container');
|
339 |
container.innerHTML = '';
|
340 |
-
|
341 |
-
hotspotMarkers.forEach(marker => marker.remove());
|
342 |
-
hotspotMarkers = [];
|
343 |
-
|
344 |
state.hotspots.forEach((hotspot, index) => {
|
345 |
const el = document.createElement('div');
|
346 |
el.className = 'hotspot';
|
347 |
el.id = `hotspot-${index}`;
|
348 |
-
|
349 |
-
const textNode = document.createElement('span');
|
350 |
-
textNode.innerText = hotspot.text;
|
351 |
-
el.appendChild(textNode);
|
352 |
-
|
353 |
-
if (hotspot.user_address) {
|
354 |
-
const creatorSpan = document.createElement('span');
|
355 |
-
creatorSpan.style.fontSize = '0.7em';
|
356 |
-
creatorSpan.style.display = 'block';
|
357 |
-
creatorSpan.style.marginTop = '5px';
|
358 |
-
creatorSpan.innerText = `by ${hotspot.user_address.slice(0, 6)}...${hotspot.user_address.slice(-4)}`;
|
359 |
-
el.appendChild(creatorSpan);
|
360 |
-
}
|
361 |
container.appendChild(el);
|
|
|
|
|
362 |
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
370 |
});
|
371 |
}
|
372 |
|
@@ -378,7 +371,7 @@ def index():
|
|
378 |
return;
|
379 |
}
|
380 |
if (!tonConnectUI.account || !tonConnectUI.account.address) {
|
381 |
-
alert('
|
382 |
return;
|
383 |
}
|
384 |
|
@@ -388,7 +381,7 @@ def index():
|
|
388 |
text: text,
|
389 |
lat: state.currentUserPosition.lat,
|
390 |
lon: state.currentUserPosition.lon,
|
391 |
-
|
392 |
};
|
393 |
|
394 |
try {
|
@@ -398,8 +391,10 @@ def index():
|
|
398 |
body: JSON.stringify(newHotspot)
|
399 |
});
|
400 |
if(response.ok) {
|
401 |
-
|
|
|
402 |
renderHotspots();
|
|
|
403 |
} else {
|
404 |
alert('Не удалось сохранить хотспот.');
|
405 |
}
|
@@ -453,7 +448,7 @@ def index():
|
|
453 |
|
454 |
const distance = haversineDistance(state.currentUserPosition, hotspot);
|
455 |
|
456 |
-
if (distance >
|
457 |
el.classList.add('hidden');
|
458 |
return;
|
459 |
}
|
@@ -472,7 +467,7 @@ def index():
|
|
472 |
const x = screenWidth / 2 + (angleDiff / (state.cameraFov / 2)) * (screenWidth / 2);
|
473 |
const y = screenHeight / 2;
|
474 |
|
475 |
-
const scale = Math.max(0.5, 1 - distance /
|
476 |
|
477 |
el.style.left = `${x}px`;
|
478 |
el.style.top = `${y}px`;
|
@@ -519,17 +514,17 @@ def handle_hotspots():
|
|
519 |
text = data.get('text')
|
520 |
lat = data.get('lat')
|
521 |
lon = data.get('lon')
|
522 |
-
|
523 |
|
524 |
-
if not all([text, lat, lon,
|
525 |
-
return jsonify({"error": "Missing data: text, lat, lon, or
|
526 |
|
527 |
try:
|
528 |
new_hotspot = {
|
529 |
"text": str(text),
|
530 |
"lat": float(lat),
|
531 |
"lon": float(lon),
|
532 |
-
"
|
533 |
}
|
534 |
save_hotspot(new_hotspot)
|
535 |
return jsonify({"success": True, "hotspot": new_hotspot}), 201
|
|
|
32 |
<title>TON AR Hotspots</title>
|
33 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
34 |
<script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
|
35 |
+
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
|
36 |
+
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
|
37 |
<style>
|
38 |
body, html {
|
39 |
margin: 0;
|
|
|
111 |
z-index: 10;
|
112 |
text-align: center;
|
113 |
}
|
114 |
+
.hotspot small {
|
115 |
+
font-size: 0.7em;
|
116 |
+
opacity: 0.8;
|
117 |
+
display: block;
|
118 |
+
margin-top: 5px;
|
119 |
+
}
|
120 |
.hotspot.hidden {
|
121 |
opacity: 0;
|
122 |
pointer-events: none;
|
123 |
}
|
124 |
+
#map-container {
|
125 |
position: fixed;
|
126 |
bottom: 10px;
|
127 |
left: 10px;
|
128 |
width: 200px;
|
129 |
height: 200px;
|
130 |
+
background-color: rgba(0, 0, 0, 0.7);
|
|
|
131 |
border-radius: 10px;
|
132 |
overflow: hidden;
|
|
|
|
|
133 |
transition: all 0.3s ease;
|
134 |
+
z-index: 50;
|
135 |
display: flex;
|
136 |
flex-direction: column;
|
137 |
}
|
138 |
+
#map-container.fullscreen {
|
139 |
width: 100%;
|
140 |
height: 100%;
|
141 |
top: 0;
|
142 |
left: 0;
|
143 |
border-radius: 0;
|
144 |
+
justify-content: center;
|
145 |
+
align-items: center;
|
146 |
}
|
147 |
#map {
|
148 |
flex-grow: 1;
|
149 |
width: 100%;
|
150 |
+
height: calc(100% - 35px);
|
151 |
+
border-radius: 10px;
|
152 |
+
overflow: hidden;
|
153 |
+
}
|
154 |
+
#map-container.fullscreen #map {
|
155 |
height: 100%;
|
156 |
+
border-radius: 0;
|
157 |
}
|
158 |
#toggle-map-button {
|
159 |
+
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
|
|
|
160 |
color: white;
|
161 |
border: none;
|
162 |
+
padding: 5px 10px;
|
163 |
+
margin: 5px;
|
164 |
+
border-radius: 5px;
|
|
|
165 |
cursor: pointer;
|
166 |
+
font-size: 12px;
|
167 |
+
align-self: flex-end;
|
|
|
168 |
}
|
169 |
+
#map-container.fullscreen #toggle-map-button {
|
170 |
+
position: absolute;
|
171 |
top: 10px;
|
172 |
right: 10px;
|
173 |
+
font-size: 16px;
|
174 |
+
margin: 0;
|
|
|
|
|
|
|
|
|
175 |
}
|
176 |
</style>
|
177 |
</head>
|
|
|
189 |
<video id="camera-view" playsinline autoplay muted></video>
|
190 |
<div id="ar-container"></div>
|
191 |
|
192 |
+
<div id="map-container">
|
|
|
193 |
<div id="map"></div>
|
194 |
+
<button id="toggle-map-button">Minimap</button>
|
195 |
</div>
|
196 |
|
197 |
<script>
|
|
|
204 |
hotspots: [],
|
205 |
currentUserPosition: null,
|
206 |
deviceOrientation: { alpha: 0, beta: 0, gamma: 0 },
|
207 |
+
cameraFov: 60,
|
208 |
+
map: null,
|
209 |
+
userMarker: null,
|
210 |
+
hotspotMarkers: [],
|
211 |
+
initialMapSet: false
|
212 |
};
|
213 |
|
214 |
+
const MAX_VISIBLE_DISTANCE = 10;
|
|
|
|
|
215 |
|
216 |
async function fetchBalance(address) {
|
217 |
try {
|
|
|
229 |
await setupCamera();
|
230 |
setupGPS();
|
231 |
setupOrientationListener();
|
232 |
+
initMap();
|
233 |
await loadHotspots();
|
234 |
setupAddHotspotListener();
|
235 |
requestAnimationFrame(update);
|
|
|
272 |
lat: position.coords.latitude,
|
273 |
lon: position.coords.longitude
|
274 |
};
|
275 |
+
if (state.userMarker) {
|
276 |
+
state.userMarker.setLatLng([state.currentUserPosition.lat, state.currentUserPosition.lon]);
|
277 |
+
if (!state.initialMapSet) {
|
278 |
+
state.map.setView([state.currentUserPosition.lat, state.currentUserPosition.lon], 15);
|
279 |
+
state.initialMapSet = true;
|
|
|
|
|
280 |
}
|
281 |
}
|
282 |
},
|
|
|
304 |
}
|
305 |
}
|
306 |
|
307 |
+
function initMap() {
|
308 |
+
state.map = L.map('map').setView([0, 0], 2);
|
309 |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
310 |
+
attribution: '© OpenStreetMap contributors'
|
311 |
+
}).addTo(state.map);
|
312 |
+
state.userMarker = L.marker([0, 0]).addTo(state.map).bindPopup('Ваше местоположение').openPopup();
|
313 |
+
|
314 |
+
document.getElementById('toggle-map-button').addEventListener('click', toggleMap);
|
315 |
+
}
|
316 |
+
|
317 |
+
function toggleMap() {
|
318 |
+
const mapContainer = document.getElementById('map-container');
|
319 |
+
mapContainer.classList.toggle('fullscreen');
|
320 |
+
setTimeout(() => {
|
321 |
+
state.map.invalidateSize();
|
322 |
+
if (state.currentUserPosition) {
|
323 |
+
state.map.setView([state.currentUserPosition.lat, state.currentUserPosition.lon], state.map.getZoom());
|
324 |
+
}
|
325 |
+
}, 300);
|
|
|
326 |
}
|
327 |
|
328 |
async function loadHotspots() {
|
|
|
331 |
const data = await response.json();
|
332 |
state.hotspots = data;
|
333 |
renderHotspots();
|
334 |
+
renderHotspotsOnMap();
|
335 |
} catch (error) {
|
336 |
console.error('Ошибка загрузки хотспотов:', error);
|
337 |
}
|
|
|
340 |
function renderHotspots() {
|
341 |
const container = document.getElementById('ar-container');
|
342 |
container.innerHTML = '';
|
|
|
|
|
|
|
|
|
343 |
state.hotspots.forEach((hotspot, index) => {
|
344 |
const el = document.createElement('div');
|
345 |
el.className = 'hotspot';
|
346 |
el.id = `hotspot-${index}`;
|
347 |
+
el.innerHTML = `${hotspot.text}<br><small>by ${hotspot.creator_address ? hotspot.creator_address.slice(0, 6) + '...' + hotspot.creator_address.slice(-4) : 'Unknown'}</small>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
348 |
container.appendChild(el);
|
349 |
+
});
|
350 |
+
}
|
351 |
|
352 |
+
function renderHotspotsOnMap() {
|
353 |
+
if (!state.map) return;
|
354 |
+
|
355 |
+
state.hotspotMarkers.forEach(marker => state.map.removeLayer(marker));
|
356 |
+
state.hotspotMarkers = [];
|
357 |
+
|
358 |
+
state.hotspots.forEach(hotspot => {
|
359 |
+
const marker = L.marker([hotspot.lat, hotspot.lon])
|
360 |
+
.addTo(state.map)
|
361 |
+
.bindPopup(`${hotspot.text}<br>by ${hotspot.creator_address ? hotspot.creator_address.slice(0, 6) + '...' + hotspot.creator_address.slice(-4) : 'Unknown'}`);
|
362 |
+
state.hotspotMarkers.push(marker);
|
363 |
});
|
364 |
}
|
365 |
|
|
|
371 |
return;
|
372 |
}
|
373 |
if (!tonConnectUI.account || !tonConnectUI.account.address) {
|
374 |
+
alert('Для добавления хотспота необходимо подключить кошелек.');
|
375 |
return;
|
376 |
}
|
377 |
|
|
|
381 |
text: text,
|
382 |
lat: state.currentUserPosition.lat,
|
383 |
lon: state.currentUserPosition.lon,
|
384 |
+
creator_address: tonConnectUI.account.address
|
385 |
};
|
386 |
|
387 |
try {
|
|
|
391 |
body: JSON.stringify(newHotspot)
|
392 |
});
|
393 |
if(response.ok) {
|
394 |
+
const savedHotspot = await response.json();
|
395 |
+
state.hotspots.push(savedHotspot.hotspot);
|
396 |
renderHotspots();
|
397 |
+
renderHotspotsOnMap();
|
398 |
} else {
|
399 |
alert('Не удалось сохранить хотспот.');
|
400 |
}
|
|
|
448 |
|
449 |
const distance = haversineDistance(state.currentUserPosition, hotspot);
|
450 |
|
451 |
+
if (distance > MAX_VISIBLE_DISTANCE) {
|
452 |
el.classList.add('hidden');
|
453 |
return;
|
454 |
}
|
|
|
467 |
const x = screenWidth / 2 + (angleDiff / (state.cameraFov / 2)) * (screenWidth / 2);
|
468 |
const y = screenHeight / 2;
|
469 |
|
470 |
+
const scale = Math.max(0.5, 1 - distance / MAX_VISIBLE_DISTANCE);
|
471 |
|
472 |
el.style.left = `${x}px`;
|
473 |
el.style.top = `${y}px`;
|
|
|
514 |
text = data.get('text')
|
515 |
lat = data.get('lat')
|
516 |
lon = data.get('lon')
|
517 |
+
creator_address = data.get('creator_address')
|
518 |
|
519 |
+
if not all([text, lat, lon, creator_address]):
|
520 |
+
return jsonify({"error": "Missing data: text, lat, lon, or creator_address"}), 400
|
521 |
|
522 |
try:
|
523 |
new_hotspot = {
|
524 |
"text": str(text),
|
525 |
"lat": float(lat),
|
526 |
"lon": float(lon),
|
527 |
+
"creator_address": str(creator_address)
|
528 |
}
|
529 |
save_hotspot(new_hotspot)
|
530 |
return jsonify({"success": True, "hotspot": new_hotspot}), 201
|