2025-03-14 06:48:49 +00:00
|
|
|
{% extends 'base.html' %}
|
|
|
|
|
{% block head %}
|
|
|
|
|
<title>Take a Photo with Geolocation</title>
|
|
|
|
|
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
|
|
|
|
<style>
|
|
|
|
|
body, html {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
#camera-container {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
#video {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
#canvas {
|
|
|
|
|
display: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
#file-fallback {
|
|
|
|
|
display: none;
|
|
|
|
|
position: fixed;
|
|
|
|
|
bottom: 100px;
|
|
|
|
|
left: 50%;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
z-index: 100;
|
|
|
|
|
background: rgba(255, 255, 255, 0.8);
|
|
|
|
|
padding: 10px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
color: black;
|
|
|
|
|
}
|
2025-04-14 23:24:31 +00:00
|
|
|
#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;
|
|
|
|
|
}
|
2025-03-14 06:48:49 +00:00
|
|
|
</style>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<div id="camera-container">
|
|
|
|
|
<video id="video" autoplay playsinline></video>
|
|
|
|
|
<canvas id="canvas"></canvas>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-04-14 23:24:31 +00:00
|
|
|
<div id="location-status">Esperando ubicación...</div>
|
|
|
|
|
|
2025-03-14 06:48:49 +00:00
|
|
|
<div style="position: absolute; bottom: 0; width: 100%; display: flex; flex-direction: column; padding: 10px; box-sizing: border-box;">
|
2025-03-16 01:11:32 +00:00
|
|
|
<button id="capture-btn" class="control-button" style="margin: 10px; right:20%"><i class="fa fas fa-camera"></i></button>
|
2025-03-14 06:48:49 +00:00
|
|
|
<div style="display: flex; justify-content: space-between; width: 100%; box-sizing: border-box;">
|
2025-03-16 01:11:32 +00:00
|
|
|
<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>
|
2025-04-14 23:24:31 +00:00
|
|
|
<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>
|
2025-03-14 06:48:49 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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="longitude" id="longitude" value="">
|
|
|
|
|
<input type="hidden" name="has_image" id="has_image" value="false">
|
|
|
|
|
|
|
|
|
|
<div id="file-fallback">
|
2025-03-16 01:11:32 +00:00
|
|
|
<label for="photo">Toma o selecciona una foto:</label>
|
2025-03-14 06:48:49 +00:00
|
|
|
<input type="file" name="photo" id="photo" accept="image/*" capture="environment" required>
|
2025-03-16 01:11:32 +00:00
|
|
|
<button type="submit">Subir</button>
|
2025-03-14 06:48:49 +00:00
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
// Get DOM elements
|
|
|
|
|
const video = document.getElementById('video');
|
|
|
|
|
const canvas = document.getElementById('canvas');
|
|
|
|
|
const captureBtn = document.getElementById('capture-btn');
|
|
|
|
|
const retakeBtn = document.getElementById('retake-btn');
|
|
|
|
|
const sendBtn = document.getElementById('send-btn');
|
|
|
|
|
const form = document.getElementById('camera-form');
|
|
|
|
|
const fallbackInput = document.getElementById('file-fallback');
|
|
|
|
|
const hasImageInput = document.getElementById('has_image');
|
2025-04-14 23:24:31 +00:00
|
|
|
const locationStatus = document.getElementById('location-status');
|
|
|
|
|
|
|
|
|
|
// Location tracking variables
|
|
|
|
|
let hasLocation = false;
|
|
|
|
|
let watchId = null;
|
2025-03-14 06:48:49 +00:00
|
|
|
|
|
|
|
|
// Set up camera stream
|
|
|
|
|
async function setupCamera() {
|
|
|
|
|
try {
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
video: {
|
|
|
|
|
facingMode: 'environment',
|
|
|
|
|
width: { ideal: window.innerWidth },
|
|
|
|
|
height: { ideal: window.innerHeight }
|
|
|
|
|
},
|
|
|
|
|
audio: false
|
|
|
|
|
});
|
|
|
|
|
video.srcObject = stream;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error accessing camera:', err);
|
|
|
|
|
fallbackInput.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize camera if supported
|
|
|
|
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
|
|
|
setupCamera();
|
|
|
|
|
} else {
|
|
|
|
|
console.error('getUserMedia not supported');
|
|
|
|
|
fallbackInput.style.display = 'block';
|
|
|
|
|
}
|
2025-04-14 23:24:31 +00:00
|
|
|
|
|
|
|
|
// 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';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-14 06:48:49 +00:00
|
|
|
|
|
|
|
|
// Capture photo from video feed
|
|
|
|
|
captureBtn.addEventListener('click', function() {
|
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
canvas.width = video.videoWidth;
|
|
|
|
|
canvas.height = video.videoHeight;
|
|
|
|
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
|
// Stop camera stream
|
|
|
|
|
video.srcObject.getTracks().forEach(track => track.stop());
|
|
|
|
|
video.style.display = 'none';
|
|
|
|
|
canvas.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
// Update controls
|
|
|
|
|
captureBtn.style.display = 'none';
|
|
|
|
|
retakeBtn.style.display = 'inline-block';
|
|
|
|
|
sendBtn.style.display = 'inline-block';
|
|
|
|
|
|
|
|
|
|
// Mark that we have an image
|
|
|
|
|
hasImageInput.value = 'true';
|
2025-04-14 23:24:31 +00:00
|
|
|
|
|
|
|
|
// Check if we can enable send button
|
|
|
|
|
updateSendButtonState();
|
2025-03-14 06:48:49 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Retake photo
|
|
|
|
|
retakeBtn.addEventListener('click', function() {
|
|
|
|
|
canvas.style.display = 'none';
|
|
|
|
|
video.style.display = 'block';
|
2025-03-24 18:02:15 +00:00
|
|
|
captureBtn.style.display = 'flex';
|
2025-03-14 06:48:49 +00:00
|
|
|
retakeBtn.style.display = 'none';
|
|
|
|
|
sendBtn.style.display = 'none';
|
|
|
|
|
hasImageInput.value = 'false';
|
|
|
|
|
setupCamera();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Send photo
|
|
|
|
|
sendBtn.addEventListener('click', function() {
|
2025-04-14 23:24:31 +00:00
|
|
|
if (hasImageInput.value === 'true' && hasLocation) {
|
2025-03-14 06:48:49 +00:00
|
|
|
// Convert canvas to blob and append to FormData
|
|
|
|
|
canvas.toBlob(function(blob) {
|
|
|
|
|
const formData = new FormData(form);
|
|
|
|
|
formData.delete('photo'); // Remove any file input value
|
2025-03-16 01:11:32 +00:00
|
|
|
// Generate a filename with timestamp and random element
|
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
|
|
|
const randomId = Math.floor(Math.random() * 10000);
|
|
|
|
|
const filename = `photo-${timestamp}-${randomId}.jpg`;
|
|
|
|
|
|
|
|
|
|
// Add the photo blob with the dynamic filename
|
|
|
|
|
formData.append('photo', blob, filename);
|
2025-03-14 06:48:49 +00:00
|
|
|
|
|
|
|
|
// Submit form data via fetch
|
|
|
|
|
fetch(form.action, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: formData
|
|
|
|
|
}).then(response => {
|
|
|
|
|
if (response.redirected) {
|
|
|
|
|
window.location.href = response.url;
|
2025-03-16 01:11:32 +00:00
|
|
|
} else if(response.ok) {
|
2025-03-15 00:00:31 +00:00
|
|
|
console.log('Success:', response);
|
|
|
|
|
window.location.href = '{{ url_for('dashboard') }}';
|
2025-03-14 06:48:49 +00:00
|
|
|
}
|
|
|
|
|
}).catch(error => console.error('Error:', error));
|
|
|
|
|
}, 'image/jpeg', 0.9);
|
2025-04-14 23:24:31 +00:00
|
|
|
} else if (!hasLocation) {
|
|
|
|
|
alert('Esperando ubicación GPS. Por favor espera.');
|
2025-03-14 06:48:49 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Request Geolocation Permission and Set Form Fields
|
|
|
|
|
if ('geolocation' in navigator) {
|
2025-04-14 23:24:31 +00:00
|
|
|
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(
|
2025-03-14 06:48:49 +00:00
|
|
|
(position) => {
|
|
|
|
|
document.getElementById('latitude').value = position.coords.latitude;
|
|
|
|
|
document.getElementById('longitude').value = position.coords.longitude;
|
2025-04-14 23:24:31 +00:00
|
|
|
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);
|
|
|
|
|
}
|
2025-03-14 06:48:49 +00:00
|
|
|
},
|
|
|
|
|
(error) => {
|
|
|
|
|
console.error('Error getting location:', error);
|
2025-04-14 23:24:31 +00:00
|
|
|
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(
|
|
|
|
|
(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();
|
|
|
|
|
},
|
|
|
|
|
(error) => {
|
|
|
|
|
console.error('Final error getting location:', error);
|
|
|
|
|
locationStatus.textContent = 'Error de ubicación';
|
|
|
|
|
},
|
|
|
|
|
{ maximumAge: 60000, timeout: 10000 }
|
|
|
|
|
);
|
2025-03-14 06:48:49 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
enableHighAccuracy: true,
|
2025-04-14 23:24:31 +00:00
|
|
|
timeout: 10000
|
2025-03-14 06:48:49 +00:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Geolocation not supported in this browser.');
|
2025-04-14 23:24:31 +00:00
|
|
|
locationStatus.textContent = 'GPS no soportado';
|
|
|
|
|
locationStatus.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; // Red
|
2025-03-14 06:48:49 +00:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|