1
0
forked from orson/bachemap

fixed crosshair and part of the dashboard displays

This commit is contained in:
Orson 2025-04-14 17:24:31 -06:00
parent c99a897d92
commit ee49adc084
8 changed files with 154 additions and 32 deletions

6
app.py
View File

@ -275,7 +275,8 @@ def create_app(config=Config):
qr_update = mongo.db.users.update_one({'_id': ObjectId(current_user.id)}, {'$set': {'referral_code': invite_code}}) qr_update = mongo.db.users.update_one({'_id': ObjectId(current_user.id)}, {'$set': {'referral_code': invite_code}})
print(invite_code) print(invite_code)
if not current_user.is_admin: if not current_user.is_admin:
pins = list(mongo.db.pins.find({"added_by": current_user.id})) pins = list(mongo.db.pins.find({"added_by": current_user.id}).sort("time", -1))
print(pins)
return render_template('dashboard.html', pins=pins, invite_code=invite_code) return render_template('dashboard.html', pins=pins, invite_code=invite_code)
if current_user.is_admin: if current_user.is_admin:
users = list(mongo.db.users.find()) users = list(mongo.db.users.find())
@ -362,7 +363,7 @@ def create_app(config=Config):
if len(username) >= 2: if len(username) >= 2:
cleaned_username = username[0] + "***" + username[-1] cleaned_username = username[0] + "***" + username[-1]
else: else:
cleaned_username = username cleaned_username = username+"***"+random.choice(string.ascii_letters, k=1)
cleaned_leaders.append({"username": cleaned_username, "count": count}) cleaned_leaders.append({"username": cleaned_username, "count": count})
except Exception as e: except Exception as e:
print(f"Error processing user_id {user_id}: {e}") print(f"Error processing user_id {user_id}: {e}")
@ -385,6 +386,7 @@ def create_app(config=Config):
# Calculate percentage (handle case where total_users might be 0) # Calculate percentage (handle case where total_users might be 0)
active_percentage = round((active_users / total_users) * 100, 1) if total_users > 0 else 0 active_percentage = round((active_users / total_users) * 100, 1) if total_users > 0 else 0
return render_template('leaderboard.html', leaders=cleaned_leaders, percentage = active_percentage) return render_template('leaderboard.html', leaders=cleaned_leaders, percentage = active_percentage)

View File

@ -398,7 +398,9 @@ section#pinner-modal::-webkit-scrollbar-thumb {
/* Animation removed as it was causing visibility issues */ /* Animation removed as it was causing visibility issues */
} }
#dash-container {
max-width: 75vw;
}
/* Notifications */ /* Notifications */
.flashes { .flashes {
@ -422,6 +424,13 @@ ul.flashes li {
font-size: 1.2rem; font-size: 1.2rem;
color: black; color: black;
} }
/*/html/body/main/div[1]/div[1]/div[2]/svg/g/path */
path {
cursor: crosshair !important;
}
/* Animations */ /* Animations */
@keyframes fadeOut { @keyframes fadeOut {
0% { opacity: 1; transform: translateY(0); } 0% { opacity: 1; transform: translateY(0); }
@ -474,7 +483,9 @@ ul.flashes li {
nav ul { nav ul {
justify-content: center; justify-content: center;
} }
#dash-container {
max-width: 95vw;
}
} }
@media (min-width: 812px) { @media (min-width: 812px) {

View File

@ -28,7 +28,14 @@
</form> </form>
<script> <script>
submitButton = document.getElementById('submit');
if (submitButton) {
submitButton.addEventListener('click', function() {
// Disable the button after submission
submitButton.disabled = true;
});
}
document.getElementById('submit').onclick=function(e){ document.getElementById('submit').onclick=function(e){
document.getElementById('progress').hidden='' document.getElementById('progress').hidden='false'
} }
</script> </script>

View File

@ -17,7 +17,6 @@
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script> crossorigin=""></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="icon" href="{{ url_for('static', filename='images/favico.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ url_for('static', filename='images/favico.ico') }}" type="image/x-icon">
<link rel="shortcut icon" href="{{ url_for('static', filename='images/favico.ico') }}" type="image/x-icon"> <link rel="shortcut icon" href="{{ url_for('static', filename='images/favico.ico') }}" type="image/x-icon">
<!-- <script src="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/leaflet.markercluster.js" type="text/javascript"></script> --> <!-- <script src="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/leaflet.markercluster.js" type="text/javascript"></script> -->
@ -25,6 +24,8 @@
<link href="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/MarkerCluster.css"> <link href="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/MarkerCluster.css">
<link href="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/MarkerCluster.Default.css"> <link href="https://app.unpkg.com/leaflet.markercluster@1.4.1/files/dist/MarkerCluster.Default.css">
<script src="{{ url_for('static', filename='leaflet.markercluster.js')}}"></script> <script src="{{ url_for('static', filename='leaflet.markercluster.js')}}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<!-- <script src="{{ url_for('static', filename='src/MarkerClusterGroup.js')}}"></script> --> <!-- <script src="{{ url_for('static', filename='src/MarkerClusterGroup.js')}}"></script> -->
<!-- <script type="importmap" src="{{ url_for('static', filename='leaflet.markercluster.js.map')}}"></script> <!-- <script type="importmap" src="{{ url_for('static', filename='leaflet.markercluster.js.map')}}"></script>
@ -106,7 +107,7 @@
</section> </section>
{% else %} {% else %}
<div class="flashes"> <div class="flashes">
<h3>Seguramente quieres hacer <a href="{{ url_for('thelogin') }}">login</a></h3> <h3>Seguramente quieres <a href="{{ url_for('thelogin') }}">iniciar sesión</a></h3>
</div> </div>
</section> </section>
{% endif %} {% endif %}
@ -132,7 +133,9 @@
pinner_button_top.addEventListener('click', toggleSlide); pinner_button_top.addEventListener('click', toggleSlide);
} }
const cancel_add = document.getElementById("cancel-add"); const cancel_add = document.getElementById("cancel-add");
if (cancel_add) {
cancel_add.addEventListener('click', toggleSlide); cancel_add.addEventListener('click', toggleSlide);
}
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
</script> </script>
<script> <script>

View File

@ -28,7 +28,6 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
#file-fallback { #file-fallback {
display: none; display: none;
position: fixed; position: fixed;
@ -41,6 +40,16 @@
border-radius: 10px; border-radius: 10px;
color: black; color: black;
} }
#location-status {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 5px;
z-index: 100;
}
</style> </style>
{% endblock %} {% endblock %}
@ -50,21 +59,21 @@
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
</div> </div>
<div id="location-status">Esperando ubicación...</div>
<div style="position: absolute; bottom: 0; width: 100%; display: flex; flex-direction: column; padding: 10px; box-sizing: border-box;"> <div style="position: absolute; bottom: 0; width: 100%; display: flex; flex-direction: column; padding: 10px; box-sizing: border-box;">
<button id="capture-btn" class="control-button" style="margin: 10px; right:20%"><i class="fa fas fa-camera"></i></button> <button id="capture-btn" class="control-button" style="margin: 10px; right:20%"><i class="fa fas fa-camera"></i></button>
<div style="display: flex; justify-content: space-between; width: 100%; box-sizing: border-box;"> <div style="display: flex; justify-content: space-between; width: 100%; box-sizing: border-box;">
<button id="retake-btn" class="control-button" style="display: none; margin: 10px 5px 10px 10px; flex: 1; left:20%; background-color: rgba(231, 9, 9, 0.671);color:white; left:20%"><i class="fas fa-redo"></i></button> <button id="retake-btn" class="control-button" style="display: none; margin: 10px 5px 10px 10px; flex: 1; left:20%; background-color: rgba(231, 9, 9, 0.671);color:white; left:20%"><i class="fas fa-redo"></i></button>
<button id="send-btn" class="control-button" style="display: none; margin: 10px 10px 10px 5px; flex: 1; background-color: rgba(49, 182, 28, 0.6);color:white; right: 20%;"><i class="fas fa-paper-plane"></i></button> <button id="send-btn" class="control-button" style="display: none; margin: 10px 10px 10px 5px; flex: 1; background-color: rgba(49, 182, 28, 0.6);color:white; right: 20%;" disabled><i class="fas fa-paper-plane"></i></button>
</div> </div>
</div> </div>
<form id="camera-form" action="{{ url_for('camera') }}" method="POST" enctype="multipart/form-data" style="display:none;"> <form id="camera-form" action="{{ url_for('camera') }}" method="POST" enctype="multipart/form-data" style="display:none;">
<input type="hidden" name="latitude" id="latitude" value=""> <input type="hidden" name="latitude" id="latitude" value="">
<input type="hidden" name="longitude" id="longitude" value=""> <input type="hidden" name="longitude" id="longitude" value="">
<input type="hidden" name="has_image" id="has_image" value="false"> <input type="hidden" name="has_image" id="has_image" value="false">
<!-- Fallback file input for unsupported browsers -->
<div id="file-fallback"> <div id="file-fallback">
<label for="photo">Toma o selecciona una foto:</label> <label for="photo">Toma o selecciona una foto:</label>
<input type="file" name="photo" id="photo" accept="image/*" capture="environment" required> <input type="file" name="photo" id="photo" accept="image/*" capture="environment" required>
@ -82,6 +91,11 @@
const form = document.getElementById('camera-form'); const form = document.getElementById('camera-form');
const fallbackInput = document.getElementById('file-fallback'); const fallbackInput = document.getElementById('file-fallback');
const hasImageInput = document.getElementById('has_image'); const hasImageInput = document.getElementById('has_image');
const locationStatus = document.getElementById('location-status');
// Location tracking variables
let hasLocation = false;
let watchId = null;
// Set up camera stream // Set up camera stream
async function setupCamera() { async function setupCamera() {
@ -109,6 +123,17 @@
fallbackInput.style.display = 'block'; fallbackInput.style.display = 'block';
} }
// Check if we can enable the send button
function updateSendButtonState() {
if (hasImageInput.value === 'true' && hasLocation) {
sendBtn.disabled = false;
sendBtn.style.opacity = '1';
} else {
sendBtn.disabled = true;
sendBtn.style.opacity = '0.5';
}
}
// Capture photo from video feed // Capture photo from video feed
captureBtn.addEventListener('click', function() { captureBtn.addEventListener('click', function() {
const context = canvas.getContext('2d'); const context = canvas.getContext('2d');
@ -128,6 +153,9 @@
// Mark that we have an image // Mark that we have an image
hasImageInput.value = 'true'; hasImageInput.value = 'true';
// Check if we can enable send button
updateSendButtonState();
}); });
// Retake photo // Retake photo
@ -143,7 +171,7 @@
// Send photo // Send photo
sendBtn.addEventListener('click', function() { sendBtn.addEventListener('click', function() {
if (hasImageInput.value === 'true') { if (hasImageInput.value === 'true' && hasLocation) {
// Convert canvas to blob and append to FormData // Convert canvas to blob and append to FormData
canvas.toBlob(function(blob) { canvas.toBlob(function(blob) {
const formData = new FormData(form); const formData = new FormData(form);
@ -169,26 +197,62 @@
} }
}).catch(error => console.error('Error:', error)); }).catch(error => console.error('Error:', error));
}, 'image/jpeg', 0.9); }, 'image/jpeg', 0.9);
} else if (!hasLocation) {
alert('Esperando ubicación GPS. Por favor espera.');
} }
}); });
// Request Geolocation Permission and Set Form Fields // Request Geolocation Permission and Set Form Fields
if ('geolocation' in navigator) { if ('geolocation' in navigator) {
locationStatus.textContent = 'Obteniendo ubicación...';
locationStatus.style.backgroundColor = 'rgba(255, 165, 0, 0.7)'; // Orange
// Start watching position for more accurate results
watchId = navigator.geolocation.watchPosition(
(position) => {
document.getElementById('latitude').value = position.coords.latitude;
document.getElementById('longitude').value = position.coords.longitude;
hasLocation = true;
locationStatus.textContent = 'Ubicación: ✓';
locationStatus.style.backgroundColor = 'rgba(0, 128, 0, 0.7)'; // Green
updateSendButtonState();
// Once we get a good reading, we can stop watching
if (position.coords.accuracy < 100) { // If accuracy is under 100 meters
navigator.geolocation.clearWatch(watchId);
}
},
(error) => {
console.error('Error getting location:', error);
locationStatus.textContent = 'Error de ubicación';
locationStatus.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; // Red
// Try getting location once more with different options
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
document.getElementById('latitude').value = position.coords.latitude; document.getElementById('latitude').value = position.coords.latitude;
document.getElementById('longitude').value = position.coords.longitude; document.getElementById('longitude').value = position.coords.longitude;
hasLocation = true;
locationStatus.textContent = 'Ubicación: ✓';
locationStatus.style.backgroundColor = 'rgba(0, 128, 0, 0.7)'; // Green
updateSendButtonState();
}, },
(error) => { (error) => {
console.error('Error getting location:', error); console.error('Final error getting location:', error);
locationStatus.textContent = 'Error de ubicación';
},
{ maximumAge: 60000, timeout: 10000 }
);
}, },
{ {
enableHighAccuracy: true, enableHighAccuracy: true,
timeout: 5000 timeout: 10000
} }
); );
} else { } else {
console.error('Geolocation not supported in this browser.'); console.error('Geolocation not supported in this browser.');
locationStatus.textContent = 'GPS no soportado';
locationStatus.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; // Red
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@ -7,11 +7,11 @@
overflow-y: scroll; overflow-y: scroll;
} }
</style> </style>
<div class="container-fluid" style="max-width: 75vw; background-color: rgba(189, 216, 3, 0.9); padding: 3rem; padding-top:10rem;display:block"> <div class="container-fluid" id="dash-container" style="background-color: rgba(189, 216, 3, 0.9); padding: 3rem; padding-top:10rem;display:block">
<h3>Hola <span style="color: darkgreen;">{{current_user.username}}</span></h3> <h3>Hola <span style="color: darkgreen;">{{current_user.username}}</span></h3>
<p>Aquí puedes ver los pines que has agregado y consultar tu enlace/QR de invitación. Cada vez que recargas esta página, tu enlace de invitacón cambia y el anterior se vuelve inválido.</p> <p>Aquí puedes ver los pines que has agregado y consultar tu enlace/QR de invitación. Cada vez que recargas esta página, tu enlace de invitacón cambia y el anterior se vuelve inválido.</p>
<div class="grid" style="padding-bottom: 2rem;"> <div class="grid" style="padding-bottom: 2rem;">
<div id="qrgen" style="padding-left: 5rem;"> <div id="qrgen" style="display:flex;justify-content: center;">
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
var qrcode = new QRCode(document.getElementById("qrgen"), { var qrcode = new QRCode(document.getElementById("qrgen"), {
@ -23,21 +23,56 @@
correctLevel : QRCode.CorrectLevel.L correctLevel : QRCode.CorrectLevel.L
}); });
</script> </script>
<div> <div style="display: grid; justify-content: center;">
<h2>Tu link es:</h2> <input type="text" id="invite_code" value="{{invite_code}}" style="" hidden="true">
<p><a href="https://baches.qro.mx/registrame/{{invite_code}}">https://baches.qro.mx/registrame/{{invite_code}}</a></p> <button id="copy-button" onclick="copyToClipboard()" style="background-color: rgb(255, 213, 0); color: rgb(0, 0, 0); border: none; padding: 12px 20px; border-radius: 25px; cursor: pointer; margin-top: 15px; display: flex; align-items: center; justify-content: center; font-size:1.2rem;font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: all 0.3s ease;">
<span style="font-size: 2rem; margin-right: 10px;">📋</span> Copia tu link de invitación
</button>
<script>
function copyToClipboard() {
var copyText = document.getElementById("invite_code");
copyText.select();
// Modern clipboard API
navigator.clipboard.writeText("https://baches.qro.mx/registrame/" + copyText.value).then(() => {
console.log('Link copied successfully');
}).catch(err => {
// Fallback for browsers that don't support clipboard API
document.execCommand("copy");
console.log('Using fallback clipboard method');
});
var button = document.getElementById("copy-button");
// Change to copied state
button.style.backgroundColor = "green";
button.style.color = "white";
button.style.transition = "all 0.5s ease";
button.innerHTML = "<span style=\"font-size: 2rem; margin-right: 10px;\"></span> ¡Copiado!";
// Revert back after 2 seconds
setTimeout(function() {
button.style.backgroundColor = "rgb(255, 213, 0)";
button.style.color = "rgb(0, 0, 0)";
button.style.transition = "all 0.5s ease";
button.innerHTML = "<span style=\"font-size: 2rem; margin-right: 10px;\">📋</span> Copia tu link de invitación";
}, 2000);
}
</script>
<p>¡Cada vez que recargas esta página, tu código de invitación cambia para prevenir malos usos! </p>
<p>Asegúrate de que a quien invites use su invitación de inmediato 😈</p>
</div> </div>
</div> </div>
{% if pins %} {% if pins %}
{% for pin in pins %} {% for pin in pins %}
<div class="pin-card" style="{% if pin.reviewed and pin.reviewed == false %} background-color: rgb(255, 170, 0) {% endif %};margin-bottom: 2rem; background-color: #f8f9fa; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; display: flex; flex-wrap: wrap;">
<div class="pin-card" style="margin-bottom: 2rem; background-color: #f8f9fa; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; display: flex; flex-wrap: wrap;">
<div class="pin-image" style="flex: 0 0 250px; padding: 15px; display: flex; align-items: center; justify-content: center;"> <div class="pin-image" style="flex: 0 0 250px; padding: 15px; display: flex; align-items: center; justify-content: center;">
<img style="max-width: 100%; max-height: 200px; object-fit: cover; border-radius: 4px;" src="{{ pin.photo }}" alt="Foto de mejora urbana"> <img style="max-width: 100%; max-height: 200px; object-fit: cover; border-radius: 4px;" src="{{ pin.photo }}" alt="Foto de mejora urbana">
</div> </div>
<div class="pin-data" style="flex: 1; padding: 15px;"> <div class="pin-data" style="flex: 1; padding: 15px;">
<h4 style="margin-top: 0; color: #333;">{{ pin.typeofpin }}</h4> <h4 style="margin-top: 0; color: #333;">{{ pin.typeofpin }}</h4>
<p><strong>Agregado el:</strong> {{ pin.time }}</p> <p><strong>Agregado:</strong> {{ pin.time }}</p>
{% if pin.reviewed and pin.reviewed == False %}<p><strong>Estado:</strong> Pendiente de revisión</p>{% endif %}
<p><strong>Modificado:</strong> {% if pin.last_mod %} {{pin.last_mod}} {% endif %}</p>
<p><strong>Descripción:</strong> {{ pin.description }}</p> <p><strong>Descripción:</strong> {{ pin.description }}</p>
<p><strong>Dirección:</strong> {{ pin.address }}</p> <p><strong>Dirección:</strong> {{ pin.address }}</p>
<p><small>Coordenadas: {{ pin.lat }}, {{ pin.lng }}</small></p> <p><small>Coordenadas: {{ pin.lat }}, {{ pin.lng }}</small></p>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div id="map" style="height: 100%; position: static;"></div> <div id="map" style="height: 100%; position: static; cursor:crosshair"></div>
<script> <script>
// document.addEventListener('DOMContentLoaded', function() { // document.addEventListener('DOMContentLoaded', function() {
var map = new L.map('map', {center: [20.57, -100.38], zoom:16, zoomControl: false }); var map = new L.map('map', {center: [20.57, -100.38], zoom:16, zoomControl: false });
@ -38,7 +38,7 @@
// Improved geolocation using direct browser API // Improved geolocation using direct browser API
function getLocation() { function getLocation() {
if (navigator.geolocation) { if (navigator.geolocation) {
console.warn("Geolocalización soportada en este navegador.", navigator.geolocation); console.log("Geolocalización soportada en este navegador.", navigator.geolocation);
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
onLocationFound, onLocationFound,
onLocationError, onLocationError,

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class="container" style="background-color: rgba(255,255,255,0.85); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(15px); padding:2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> <div class="container" style="background-color: rgba(255,255,255,0.85); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(15px); padding:2rem; margin-top: 5rem; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<!-- Header --> <!-- Header -->
<div class="header-cta"> <div class="header-cta">
<h2 style="text-align: center; color: #172f01; margin-bottom: 1rem;">¡Hazte mapista y pon tu granito de fotoevidencia!</h2> <h2 style="text-align: center; color: #172f01; margin-bottom: 1rem;">¡Hazte mapista y pon tu granito de fotoevidencia!</h2>