Spaces:
Sleeping
Sleeping
sara.mesquita commited on
Commit Β·
95aa944
1
Parent(s): 766f51d
Adjusts IA rules
Browse files- index.html +5 -4
- static/app.js +34 -5
- static/style.css +5 -0
index.html
CHANGED
|
@@ -10,6 +10,7 @@
|
|
| 10 |
<link rel="stylesheet" href="/static/style.css"/>
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/lucide@0.454.0/dist/umd/lucide.min.js"></script>
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/lottie-web@5.12.2/build/player/lottie.min.js"></script>
|
|
|
|
| 13 |
</head>
|
| 14 |
<body>
|
| 15 |
<div id="desktop-layout">
|
|
@@ -196,7 +197,7 @@
|
|
| 196 |
<div id="viewfinder">
|
| 197 |
<div id="gps-pill">
|
| 198 |
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
| 199 |
-
<span id="gps-text">
|
| 200 |
</div>
|
| 201 |
<img id="photo-preview" src="" alt="photo"/>
|
| 202 |
<div id="camera-placeholder">
|
|
@@ -213,12 +214,12 @@
|
|
| 213 |
<svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
| 214 |
<textarea id="notes-input" rows="1" placeholder="Add a note (optional)"></textarea>
|
| 215 |
</div>
|
| 216 |
-
<button class="btn-
|
| 217 |
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
| 218 |
-
Add Location
|
| 219 |
</button>
|
| 220 |
<button class="btn-primary" id="submit-reg-btn" disabled>
|
| 221 |
-
<svg viewBox="0 0 24 24"><path d="
|
| 222 |
Report Sighting
|
| 223 |
</button>
|
| 224 |
</div>
|
|
|
|
| 10 |
<link rel="stylesheet" href="/static/style.css"/>
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/lucide@0.454.0/dist/umd/lucide.min.js"></script>
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/lottie-web@5.12.2/build/player/lottie.min.js"></script>
|
| 13 |
+
<script src="https://cdn.jsdelivr.net/npm/exifr@7.1.3/dist/full.umd.js"></script>
|
| 14 |
</head>
|
| 15 |
<body>
|
| 16 |
<div id="desktop-layout">
|
|
|
|
| 197 |
<div id="viewfinder">
|
| 198 |
<div id="gps-pill">
|
| 199 |
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
| 200 |
+
<span id="gps-text">Add a photo to detect location</span>
|
| 201 |
</div>
|
| 202 |
<img id="photo-preview" src="" alt="photo"/>
|
| 203 |
<div id="camera-placeholder">
|
|
|
|
| 214 |
<svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
| 215 |
<textarea id="notes-input" rows="1" placeholder="Add a note (optional)"></textarea>
|
| 216 |
</div>
|
| 217 |
+
<button class="btn-locate" id="gps-btn" style="display:none">
|
| 218 |
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
| 219 |
+
Add Location Manually
|
| 220 |
</button>
|
| 221 |
<button class="btn-primary" id="submit-reg-btn" disabled>
|
| 222 |
+
<svg viewBox="0 0 24 24"><path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/></svg>
|
| 223 |
Report Sighting
|
| 224 |
</button>
|
| 225 |
</div>
|
static/app.js
CHANGED
|
@@ -506,9 +506,37 @@
|
|
| 506 |
document.getElementById('photo-preview').style.display = 'block';
|
| 507 |
document.getElementById('camera-placeholder').style.display = 'none';
|
| 508 |
updateSubmitBtn();
|
|
|
|
| 509 |
});
|
| 510 |
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
document.getElementById('gps-btn').addEventListener('click', requestGPS);
|
| 513 |
|
| 514 |
function requestGPS() {
|
|
@@ -521,6 +549,7 @@
|
|
| 521 |
gpsCoords = { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
| 522 |
txt.textContent = `${gpsCoords.lat.toFixed(4)}, ${gpsCoords.lng.toFixed(4)}`; txt.classList.add('located');
|
| 523 |
btn.innerHTML = `<svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg> Location Added`;
|
|
|
|
| 524 |
btn.classList.add('located'); btn.disabled = false; updateSubmitBtn();
|
| 525 |
},
|
| 526 |
err => {
|
|
@@ -786,11 +815,12 @@
|
|
| 786 |
document.getElementById('photo-input').value = '';
|
| 787 |
document.getElementById('notes-input').value = '';
|
| 788 |
document.getElementById('animal-name-input').value = '';
|
| 789 |
-
document.getElementById('gps-text').textContent = '
|
| 790 |
document.getElementById('gps-text').classList.remove('located');
|
| 791 |
const btn = document.getElementById('gps-btn');
|
| 792 |
-
btn.className = 'btn-
|
| 793 |
-
btn.
|
|
|
|
| 794 |
btn.disabled = false;
|
| 795 |
document.querySelectorAll('.cond-chip').forEach(c => c.classList.remove('active'));
|
| 796 |
updateSubmitBtn();
|
|
@@ -914,7 +944,6 @@
|
|
| 914 |
confirm.classList.remove('open');
|
| 915 |
setTimeout(() => confirm.classList.add('hidden'), 300);
|
| 916 |
});
|
| 917 |
-
|
| 918 |
// ββ Photo lightbox ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 919 |
const lightbox = document.getElementById('photo-lightbox');
|
| 920 |
const lightboxImg = document.getElementById('lightbox-img');
|
|
|
|
| 506 |
document.getElementById('photo-preview').style.display = 'block';
|
| 507 |
document.getElementById('camera-placeholder').style.display = 'none';
|
| 508 |
updateSubmitBtn();
|
| 509 |
+
extractPhotoGPS(file);
|
| 510 |
});
|
| 511 |
|
| 512 |
+
// Try to read GPS straight from the photo's EXIF metadata. If found, we use it
|
| 513 |
+
// automatically; if not, we reveal the manual "Add Location" button as a fallback.
|
| 514 |
+
async function extractPhotoGPS(file) {
|
| 515 |
+
const txt = document.getElementById('gps-text');
|
| 516 |
+
const locBtn = document.getElementById('gps-btn');
|
| 517 |
+
locBtn.style.display = 'none';
|
| 518 |
+
txt.textContent = 'Reading photo locationβ¦'; txt.classList.remove('located');
|
| 519 |
+
try {
|
| 520 |
+
const gps = (typeof exifr !== 'undefined') ? await exifr.gps(file) : null;
|
| 521 |
+
if (gps && Number.isFinite(gps.latitude) && Number.isFinite(gps.longitude)) {
|
| 522 |
+
gpsCoords = { lat: gps.latitude, lng: gps.longitude };
|
| 523 |
+
txt.textContent = `${gpsCoords.lat.toFixed(4)}, ${gpsCoords.lng.toFixed(4)} Β· from photo`;
|
| 524 |
+
txt.classList.add('located');
|
| 525 |
+
return;
|
| 526 |
+
}
|
| 527 |
+
} catch (err) {
|
| 528 |
+
console.warn('EXIF GPS read failed:', err);
|
| 529 |
+
}
|
| 530 |
+
// No usable metadata β fall back to manual detection.
|
| 531 |
+
gpsCoords = null;
|
| 532 |
+
txt.textContent = 'No location in photo β add it manually';
|
| 533 |
+
txt.classList.remove('located');
|
| 534 |
+
locBtn.style.display = 'flex';
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
document.getElementById('gps-pill').addEventListener('click', () => {
|
| 538 |
+
if (document.getElementById('gps-btn').style.display !== 'none') requestGPS();
|
| 539 |
+
});
|
| 540 |
document.getElementById('gps-btn').addEventListener('click', requestGPS);
|
| 541 |
|
| 542 |
function requestGPS() {
|
|
|
|
| 549 |
gpsCoords = { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
| 550 |
txt.textContent = `${gpsCoords.lat.toFixed(4)}, ${gpsCoords.lng.toFixed(4)}`; txt.classList.add('located');
|
| 551 |
btn.innerHTML = `<svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg> Location Added`;
|
| 552 |
+
txt.textContent = `${gpsCoords.lat.toFixed(4)}, ${gpsCoords.lng.toFixed(4)} Β· current location`;
|
| 553 |
btn.classList.add('located'); btn.disabled = false; updateSubmitBtn();
|
| 554 |
},
|
| 555 |
err => {
|
|
|
|
| 815 |
document.getElementById('photo-input').value = '';
|
| 816 |
document.getElementById('notes-input').value = '';
|
| 817 |
document.getElementById('animal-name-input').value = '';
|
| 818 |
+
document.getElementById('gps-text').textContent = 'Add a photo to detect location';
|
| 819 |
document.getElementById('gps-text').classList.remove('located');
|
| 820 |
const btn = document.getElementById('gps-btn');
|
| 821 |
+
btn.className = 'btn-locate';
|
| 822 |
+
btn.style.display = 'none';
|
| 823 |
+
btn.innerHTML = `<svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg> Add Location Manually`;
|
| 824 |
btn.disabled = false;
|
| 825 |
document.querySelectorAll('.cond-chip').forEach(c => c.classList.remove('active'));
|
| 826 |
updateSubmitBtn();
|
|
|
|
| 944 |
confirm.classList.remove('open');
|
| 945 |
setTimeout(() => confirm.classList.add('hidden'), 300);
|
| 946 |
});
|
|
|
|
| 947 |
// ββ Photo lightbox ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 948 |
const lightbox = document.getElementById('photo-lightbox');
|
| 949 |
const lightboxImg = document.getElementById('lightbox-img');
|
static/style.css
CHANGED
|
@@ -130,6 +130,11 @@
|
|
| 130 |
.btn-secondary svg { width:18px; height:18px; fill:none; stroke:var(--green); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
|
| 131 |
.btn-secondary.located { background:var(--green); color:#fff; border-color:var(--green); }
|
| 132 |
.btn-secondary.located svg { stroke:#fff; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
.btn-primary { width:100%; padding:15px; border-radius:12px; font-size:15px; font-weight:700; font-family:inherit; cursor:pointer; transition:all .15s; display:flex; align-items:center; justify-content:center; gap:8px; background:var(--green); color:#fff; border:none; }
|
| 134 |
.btn-primary:hover { background:var(--green-dark); }
|
| 135 |
.btn-primary:disabled { background:#ccc; cursor:not-allowed; }
|
|
|
|
| 130 |
.btn-secondary svg { width:18px; height:18px; fill:none; stroke:var(--green); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
|
| 131 |
.btn-secondary.located { background:var(--green); color:#fff; border-color:var(--green); }
|
| 132 |
.btn-secondary.located svg { stroke:#fff; }
|
| 133 |
+
.btn-locate { width:100%; padding:14px; border-radius:12px; font-size:15px; font-weight:600; font-family:inherit; cursor:pointer; transition:all .15s; margin-bottom:10px; display:flex; align-items:center; justify-content:center; gap:8px; background:#FFF3E0; color:var(--orange); border:1.5px solid #FFD9A8; }
|
| 134 |
+
.btn-locate:hover { background:#FFE8C9; border-color:var(--orange); }
|
| 135 |
+
.btn-locate svg { width:18px; height:18px; fill:none; stroke:var(--orange); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
|
| 136 |
+
.btn-locate.located { background:var(--green); color:#fff; border-color:var(--green); }
|
| 137 |
+
.btn-locate.located svg { stroke:#fff; }
|
| 138 |
.btn-primary { width:100%; padding:15px; border-radius:12px; font-size:15px; font-weight:700; font-family:inherit; cursor:pointer; transition:all .15s; display:flex; align-items:center; justify-content:center; gap:8px; background:var(--green); color:#fff; border:none; }
|
| 139 |
.btn-primary:hover { background:var(--green-dark); }
|
| 140 |
.btn-primary:disabled { background:#ccc; cursor:not-allowed; }
|