5c82e8bd9f
Dwie pierwsze wskazowki od partnerow biznesowych; pozostale piec zapisane jako wymagania (PRE-21..PRE-24) do zrobienia w kolejnych krokach. PRE-19 — offset wzgledem GMT. Etykieta mowila „Strefa (offset h)”, czyli nie bylo jasne, wzgledem czego liczymy przesuniecie. Teraz „Offset wzgledem GMT (h)” z podpowiedzia. Krok juz byl 15-minutowy (0,25 h) — dolozony zakres −12…+14, zeby nie dalo sie wpisac strefy, ktora nie istnieje. Zmiana w trzech zakladkach, ktore maja to pole (Horoskop, Interpretacje, Kalendarz). PRE-20 — po „Tu i teraz” wspolrzedne i pineska skakaly na biezace polozenie, ale w polu tekstowym zostawala STARA, wczesniej wpisana nazwa. Formularz pokazywal jedno miejsce, a liczyl dla innego — cicha pomylka, nic sie nie wywalalo. Handler zdarzenia astrololo:coords odswieza teraz nazwe przez reverseName. Przy okazji druga strona tego samego bledu: reverseName czysci pole ZANIM wysle zapytanie. Gdyby /reverse nie odpowiedzialo (brak sieci), zostalaby stara nazwa — lepiej puste pole i poprawne wspolrzedne niz nazwa, ktora klamie. Wymagania: PRE-19/20 (zrobione), PRE-21 wspolne dane miedzy zakladkami wraz z polem imie i nazwisko, PRE-22 wiele predykcji okresowych w pamieci sesji, PRE-23 zakladka „Skompiluj”, PRE-24 raport PDF przez LaTeX. Dolozone pytanie otwarte Q-14 o lancuch LaTeX→PDF (gdzie postawic TeX Live, silnik unicode owy pod glify, konwersja SVG) — decyzja wplywa na deploy i rozmiar obrazow. Testy: 12 nowych, strukturalnych na zrodle (JS-a nie uruchomimy, a obie regresje sa ciche). Sprawdzone sabotazem — po cofnieciu kazdej poprawki czerwienieja. Calosc: prezentacja 45 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
5.4 KiB
JavaScript
127 lines
5.4 KiB
JavaScript
// Wyszukiwarka lokalizacji + mapa (OpenStreetMap / Leaflet, bez klucza API).
|
|
// Wpina się do formularza z polami input[name=lat] / input[name=lon]:
|
|
// - wpisanie nazwy/adresu/POI -> /geocode -> lista trafień -> klik ustawia lat/lon,
|
|
// - klik na mapie lub przeciągnięcie pineski -> ustawia lat/lon + /reverse pokazuje nazwę,
|
|
// - „Tu i teraz" (now.js) emituje event 'astrololo:coords' -> mapa się synchronizuje.
|
|
// Bez internetu wyszukiwarka/kafelki nie działają, ale ręczne lat/lon dalej tak.
|
|
(function () {
|
|
function ready(fn) {
|
|
if (document.readyState !== 'loading') fn();
|
|
else document.addEventListener('DOMContentLoaded', fn);
|
|
}
|
|
|
|
ready(function () {
|
|
var picker = document.getElementById('geoPicker');
|
|
if (!picker || typeof L === 'undefined') return; // brak Leafleta = po cichu pas
|
|
var latEl = document.querySelector('input[name=lat]');
|
|
var lonEl = document.querySelector('input[name=lon]');
|
|
if (!latEl || !lonEl) return;
|
|
|
|
var searchEl = document.getElementById('geoSearch');
|
|
var searchBtn = document.getElementById('geoSearchBtn');
|
|
var mapToggle = document.getElementById('geoMapToggle');
|
|
var resultsEl = document.getElementById('geoResults');
|
|
var mapEl = document.getElementById('map');
|
|
|
|
var num = function (el) { var v = parseFloat(el.value); return isNaN(v) ? 0 : v; };
|
|
var curLat = function () { return num(latEl); };
|
|
var curLon = function () { return num(lonEl); };
|
|
|
|
var pin = L.divIcon({ className: 'geo-pin', html: '📍', iconSize: [24, 24], iconAnchor: [12, 24] });
|
|
var map = L.map(mapEl).setView([curLat(), curLon()], 12);
|
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 19,
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
var marker = L.marker([curLat(), curLon()], { draggable: true, icon: pin }).addTo(map);
|
|
|
|
// Kontener mógł być renderowany zanim ustaliła się jego szerokość — poprawka.
|
|
setTimeout(function () { map.invalidateSize(); }, 60);
|
|
|
|
function setCoords(lat, lon, fromMap) {
|
|
latEl.value = lat.toFixed(4);
|
|
lonEl.value = lon.toFixed(4);
|
|
marker.setLatLng([lat, lon]);
|
|
if (!fromMap) map.setView([lat, lon], Math.max(map.getZoom(), 12));
|
|
if (fromMap) reverseName(lat, lon);
|
|
}
|
|
|
|
function reverseName(lat, lon) {
|
|
// Czyścimy pole OD RAZU. Dopóki nie znamy nazwy nowego punktu, stara jest
|
|
// po prostu nieprawdziwa — pokazywałaby inne miejsce niż współrzędne obok.
|
|
// Dotyczy też sytuacji, gdy /reverse nie odpowie (brak sieci): lepiej puste
|
|
// pole i poprawne współrzędne niż nazwa, która kłamie.
|
|
if (searchEl) searchEl.value = '';
|
|
fetch('/reverse?lat=' + lat + '&lon=' + lon)
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
.then(function (d) { if (d && d.name && searchEl) searchEl.value = d.name; })
|
|
.catch(function () {});
|
|
}
|
|
|
|
marker.on('dragend', function () {
|
|
var p = marker.getLatLng();
|
|
setCoords(p.lat, p.lng, true);
|
|
});
|
|
map.on('click', function (e) { setCoords(e.latlng.lat, e.latlng.lng, true); });
|
|
|
|
// ---- wyszukiwanie po nazwie ----
|
|
function renderResults(items) {
|
|
resultsEl.innerHTML = '';
|
|
if (!items.length) { resultsEl.hidden = true; return; }
|
|
items.forEach(function (it) {
|
|
var li = document.createElement('li');
|
|
li.textContent = it.name;
|
|
li.addEventListener('click', function () {
|
|
setCoords(it.lat, it.lon, false);
|
|
searchEl.value = it.name;
|
|
resultsEl.hidden = true;
|
|
});
|
|
resultsEl.appendChild(li);
|
|
});
|
|
resultsEl.hidden = false;
|
|
}
|
|
|
|
function doSearch() {
|
|
var q = searchEl.value.trim();
|
|
if (q.length < 3) { resultsEl.hidden = true; return; }
|
|
fetch('/geocode?q=' + encodeURIComponent(q))
|
|
.then(function (r) { return r.ok ? r.json() : { results: [] }; })
|
|
.then(function (d) { renderResults(d.results || []); })
|
|
.catch(function () { renderResults([]); });
|
|
}
|
|
|
|
var timer = null;
|
|
searchBtn.addEventListener('click', doSearch);
|
|
searchEl.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') { e.preventDefault(); doSearch(); } // nie wysyłaj formularza
|
|
});
|
|
searchEl.addEventListener('input', function () {
|
|
clearTimeout(timer); timer = setTimeout(doSearch, 500); // debounce (limit Nominatim)
|
|
});
|
|
|
|
// ---- zwijanie/rozwijanie mapy ----
|
|
mapToggle.addEventListener('click', function () {
|
|
var hidden = mapEl.hasAttribute('hidden');
|
|
if (hidden) {
|
|
mapEl.removeAttribute('hidden');
|
|
mapToggle.textContent = 'Ukryj mapę';
|
|
setTimeout(function () { map.invalidateSize(); map.setView([curLat(), curLon()]); }, 60);
|
|
} else {
|
|
mapEl.setAttribute('hidden', '');
|
|
mapToggle.textContent = 'Pokaż mapę';
|
|
}
|
|
});
|
|
|
|
// ---- synchronizacja z „Tu i teraz" ----
|
|
// Poza przesunięciem pineski MUSIMY odświeżyć pole tekstowe: po „Tu i teraz"
|
|
// zostawała w nim stara, ręcznie wpisana nazwa, więc formularz pokazywał jedno
|
|
// miejsce, a współrzędne wskazywały już inne. reverseName pyta o nazwę punktu,
|
|
// na który faktycznie ustawiła się mapa.
|
|
document.addEventListener('astrololo:coords', function () {
|
|
marker.setLatLng([curLat(), curLon()]);
|
|
map.setView([curLat(), curLon()], Math.max(map.getZoom(), 12));
|
|
reverseName(curLat(), curLon());
|
|
});
|
|
});
|
|
})();
|