Wpisz dane momentu i miejsca — program policzy pozycje obiektów, osie i domy (silnik efemeryd warstwy logicznej).
diff --git a/docs/astrololo_wymagania.xlsx b/docs/astrololo_wymagania.xlsx index b925889..c7bb6d5 100644 Binary files a/docs/astrololo_wymagania.xlsx and b/docs/astrololo_wymagania.xlsx differ diff --git a/services/presentation/app/main.py b/services/presentation/app/main.py index 1d06ee4..ba46bd9 100644 --- a/services/presentation/app/main.py +++ b/services/presentation/app/main.py @@ -71,6 +71,7 @@ def chart_form(request: Request): @app.post("/", response_class=HTMLResponse) def chart_compute( request: Request, + person: str = Form(""), # imie i nazwisko — do naglowka raportu (PRE-21/24) date: str = Form(...), time: str = Form(...), tz_offset: float = Form(0.0), @@ -81,7 +82,7 @@ def chart_compute( zodiac: str = Form("tropical"), tables: bool = Form(False), ): - form = {"date": date, "time": time, "tz_offset": tz_offset, + form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon, "house_system": house_system, "stations": stations, "zodiac": zodiac, "tables": tables} ctx: dict = {"form": form, "result": None, "error": None, "moment": None} @@ -137,6 +138,7 @@ def interpret_form(request: Request): @app.post("/interpret", response_class=HTMLResponse) def interpret_run( request: Request, + person: str = Form(""), # imie i nazwisko — do naglowka raportu (PRE-21/24) date: str = Form(...), time: str = Form(...), tz_offset: float = Form(0.0), @@ -148,7 +150,7 @@ def interpret_run( llm_provider: str = Form("local"), llm_model: str = Form(""), ): - form = {"date": date, "time": time, "tz_offset": tz_offset, + form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon, "group": group, "prompt_budget": prompt_budget, "llm_provider": llm_provider, "llm_model": llm_model} ctx: dict = {"form": form, "result": None, "error": None, "moment": None, @@ -188,6 +190,7 @@ def timeline_form(request: Request): @app.post("/timeline", response_class=HTMLResponse) def timeline_run( request: Request, + person: str = Form(""), # imie i nazwisko — do naglowka raportu (PRE-21/24) date: str = Form(...), time: str = Form(...), tz_offset: float = Form(0.0), @@ -200,7 +203,7 @@ def timeline_run( llm_provider: str = Form("local"), llm_model: str = Form(""), ): - form = {"date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon, + form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon, "from_date": from_date, "to_date": to_date, "prompt_budget": prompt_budget, "llm_provider": llm_provider, "llm_model": llm_model} ctx: dict = {"form": form, "result": None, "error": None, "moment": None, diff --git a/services/presentation/app/static/formsync.js b/services/presentation/app/static/formsync.js new file mode 100644 index 0000000..6551af4 --- /dev/null +++ b/services/presentation/app/static/formsync.js @@ -0,0 +1,98 @@ +// Wspólne dane formularza między zakładkami (PRE-21). +// +// Problem: każdą zakładkę (Horoskop / Interpretacje / Kalendarz) wypełniało się +// od nowa — te same imię, data, godzina, strefa i miejsce. Teraz raz wpisane +// dane wędrują za użytkownikiem. +// +// Dlaczego localStorage, a nie sesja na serwerze: serwer zostaje BEZSTANOWY +// (żadnego magazynu sesji, żadnych danych urodzeniowych trzymanych po stronie +// usługi), a przy okazji dane synchronizują się też między osobnymi kartami +// przeglądarki — zdarzenie `storage` daje to za darmo. +// +// UWAGA na kolejność: ten skrypt ładujemy w
z `defer`, więc wykonuje się +// PO sparsowaniu dokumentu, ale PRZED geo.js (skrypty defer idą w kolejności +// dokumentu). Dzięki temu mapa startuje już z odtworzonymi współrzędnymi i nie +// trzeba jej potem przestawiać. +(function () { + 'use strict'; + + var KEY = 'astrololo.form.v1'; + + // Pola wspólne dla zakładek. Nazwa pola formularza -> jak je znaleźć. + // `geoSearch` (nazwa miejsca) nie ma atrybutu name — szukamy po id. + var FIELDS = [ + { key: 'person', sel: 'input[name=person]' }, + { key: 'date', sel: 'input[name=date]' }, + { key: 'time', sel: 'input[name=time]' }, + { key: 'tz_offset', sel: 'input[name=tz_offset]' }, + { key: 'lat', sel: 'input[name=lat]' }, + { key: 'lon', sel: 'input[name=lon]' }, + { key: 'house_system', sel: 'select[name=house_system]' }, + { key: 'zodiac', sel: 'select[name=zodiac]' }, + { key: 'place', sel: '#geoSearch' } + ]; + + function load() { + try { + return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; + } catch (e) { + return {}; // uszkodzony wpis nie może zablokować strony + } + } + + function save(state) { + try { + localStorage.setItem(KEY, JSON.stringify(state)); + } catch (e) { + /* tryb prywatny albo brak miejsca — trudno, formularz ma działać dalej */ + } + } + + function apply(state) { + FIELDS.forEach(function (f) { + var el = document.querySelector(f.sel); + if (!el) return; + var v = state[f.key]; + if (v === undefined || v === null || v === '') return; + if (el.value !== v) el.value = v; + }); + } + + function collect() { + var state = load(); + FIELDS.forEach(function (f) { + var el = document.querySelector(f.sel); + if (!el) return; // pola nieobecnego na tej zakładce nie kasujemy + if (el.value === '' || el.value === null) return; + state[f.key] = el.value; + }); + return state; + } + + // 1) odtwórz to, co użytkownik wpisał gdzie indziej + apply(load()); + + // 2) zapisuj każdą zmianę (input dla wpisywania, change dla list i kalendarzy) + FIELDS.forEach(function (f) { + var el = document.querySelector(f.sel); + if (!el) return; + ['input', 'change'].forEach(function (ev) { + el.addEventListener(ev, function () { save(collect()); }); + }); + }); + + // 3) zapisz stan po wysłaniu formularza — po przeładowaniu strony pola mają + // pokazywać to, co faktycznie policzyliśmy + document.addEventListener('submit', function () { save(collect()); }, true); + + // 4) druga otwarta karta zmieniła dane -> dociągnij je tutaj na żywo + window.addEventListener('storage', function (e) { + if (e.key === KEY) apply(load()); + }); + + // 5) geo.js ustawia lat/lon z mapy poza polami formularza — po takiej zmianie + // też chcemy zapamiętać (zdarzenie leci z „Tu i teraz" oraz z mapy) + document.addEventListener('astrololo:coords', function () { + setTimeout(function () { save(collect()); }, 0); + }); +})(); diff --git a/services/presentation/app/static/geo.js b/services/presentation/app/static/geo.js index a79aacd..3dbb4de 100644 --- a/services/presentation/app/static/geo.js +++ b/services/presentation/app/static/geo.js @@ -38,18 +38,32 @@ // 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) { - latEl.value = lat.toFixed(4); - lonEl.value = lon.toFixed(4); + 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.value = d.name; }) + .then(function (d) { if (d && d.name && searchEl) setVal(searchEl, d.name); }) .catch(function () {}); } @@ -68,7 +82,7 @@ li.textContent = it.name; li.addEventListener('click', function () { setCoords(it.lat, it.lon, false); - searchEl.value = it.name; + setVal(searchEl, it.name); resultsEl.hidden = true; }); resultsEl.appendChild(li); @@ -108,9 +122,14 @@ }); // ---- 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()); }); }); })(); diff --git a/services/presentation/app/templates/base.html b/services/presentation/app/templates/base.html index 8c4dde7..fce2746 100644 --- a/services/presentation/app/templates/base.html +++ b/services/presentation/app/templates/base.html @@ -5,6 +5,11 @@Wpisz dane momentu i miejsca — program policzy pozycje obiektów, osie i domy (silnik efemeryd warstwy logicznej).