f34016a4a5
build / build (push) Successful in 58s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Trzecia wskazowka partnerow. Kazda zakladke (Horoskop / Interpretacje / Kalendarz) wypelnialo sie od nowa — te same imie, data, godzina, strefa i miejsce. Teraz raz wpisane dane wedruja za uzytkownikiem, a zmiana w jednej zakladce przenosi sie na pozostale. Nowy formsync.js trzyma stan w localStorage. Dlaczego nie sesja na serwerze: serwer zostaje BEZSTANOWY — zadnego magazynu sesji, zadnych danych urodzeniowych trzymanych po stronie uslugi (spojne z postawa z LOG-32/PRE-16). Przy okazji dane synchronizuja sie tez miedzy osobnymi kartami przegladarki, bo zdarzenie `storage` daje to za darmo. Dolozone pole „imie i nazwisko" (wszystkie trzy zakladki) — potrzebne do naglowka raportu PDF (PRE-24). Handlery przyjmuja je i oddaja, wiec nie znika po przeliczeniu. Dwie rzeczy, ktore trzeba bylo domknac, zeby to dzialalo naprawde: 1. KOLEJNOSC SKRYPTOW. formsync.js ladowany w <head> z `defer` — skrypty defer wykonuja sie w kolejnosci dokumentu, wiec ten zdazy odtworzyc wspolrzedne, ZANIM geo.js zbuduje mape. Mapa startuje od razu we wlasciwym miejscu, zamiast przeskakiwac po chwili. 2. geo.js ustawia pola z KODU (`.value = ...`), co samo z siebie NIE wywoluje zdarzen — bez tego synchronizacja przegapilaby kazdy wybor z mapy, z wyszukiwarki i z „Tu i teraz". Dolozony setVal(), ktory jawnie zglasza `change`. Zakladka bez danego pola (np. Sygnifikatory) nie kasuje wartosci zapamietanej gdzie indziej; uszkodzony wpis w localStorage nie blokuje formularza. Q-14 rozstrzygniete: lancuch LaTeX->PDF stanie jako OSOBNA USLUGA render — spojne z izolacja swisseph (LOG-27), obraz produktu zostaje maly. Weryfikacja na zywym stacku (data+logika+prezentacja) w przegladarce: dane wpisane w Horoskopie pojawily sie w Interpretacjach; zmiana godziny w Interpretacjach dotarla do Kalendarza; pola nieobecne na zakladce (zodiak, system domow) przetrwaly; po POST imie zostalo, a horoskop policzyl sie normalnie. Testy: 22 nowe strukturalne. Calosc: prezentacja 67 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
136 lines
5.8 KiB
JavaScript
136 lines
5.8 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);
|
|
|
|
// Ustawienie `.value` z kodu NIE wywołuje zdarzeń, więc synchronizacja
|
|
// między zakładkami (formsync.js) przegapiłaby zmiany z mapy. Zgłaszamy je
|
|
// jawnie przez `change`.
|
|
function setVal(el, value) {
|
|
if (!el) return;
|
|
el.value = value;
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
|
|
function setCoords(lat, lon, fromMap) {
|
|
setVal(latEl, lat.toFixed(4));
|
|
setVal(lonEl, 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) setVal(searchEl, '');
|
|
fetch('/reverse?lat=' + lat + '&lon=' + lon)
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
.then(function (d) { if (d && d.name && searchEl) setVal(searchEl, 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);
|
|
setVal(searchEl, 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());
|
|
});
|
|
});
|
|
})();
|