Files
eventmap/js/store/store.js
dapelzaandClaude Sonnet 5 2aaff8f680 Seed-Daten per Fetch von externem Server laden, UI-Sprachumschalter (DE/EN) und KI-Übersetzungs-Prompt-Generator ergänzen
- Beispieldaten werden nicht mehr eingebettet (data/seed.js entfernt),
  sondern beim ersten Start per fetch(EventMap.config.SEED_DATA_URL) von
  einem externen, selbst gehosteten Server geladen (siehe ADR 0001,
  Revision 2026-07-28).
- Neuer UI-Sprachumschalter (Deutsch/Englisch) für die Bedienoberfläche:
  js/i18n/strings.js + js/i18n/i18n.js, Sprachwahl im Menü, Persistenz über
  settings.language, Retranslation ohne Reload.
- Neuer Menüpunkt "Ausflugsziel übersetzen (KI)": erzeugt einen Prompt, der
  eine externe KI bittet, Name/Beschreibungen eines bestehenden Ortes ins
  Englische, Spanische oder Französische zu übersetzen (js/ai/translatePromptBuilder.js,
  js/ui/translatePromptGenerator.js).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 15:16:31 +02:00

124 lines
4.0 KiB
JavaScript

// Zentraler Pub/Sub-Store der Anwendung. Hält den Laufzeitzustand
// (places, filters, userLocation, selectedPlaceId) und benachrichtigt
// Subscriber bei Änderungen. Lädt beim Start aus LocalStorage bzw. beim
// allerersten Start von EventMap.config.SEED_DATA_URL und persistiert
// Änderungen zurück.
(function () {
const { loadData, saveData, loadSettings, saveSettings } = EventMap.store;
const { normalizePlace } = EventMap.models;
let state = {
places: [],
filters: null,
userLocation: null,
selectedPlaceId: null,
};
let settings = loadSettings();
const subscribers = new Set();
function notify() {
for (const fn of subscribers) {
fn(state);
}
}
/** Registriert einen Listener, der bei jeder Zustandsänderung aufgerufen wird. */
function subscribe(fn) {
subscribers.add(fn);
return () => subscribers.delete(fn);
}
function getState() {
return state;
}
function setState(patch) {
state = { ...state, ...patch };
notify();
}
function getSettings() {
return settings;
}
function updateSettings(patch) {
settings = { ...settings, ...patch };
saveSettings(settings);
}
/** Lädt die Beispieldaten von EventMap.config.SEED_DATA_URL nach. Wirft bei Netzwerk-/HTTP-/Parse-Fehlern. */
async function loadSeedPlaces() {
const response = await fetch(EventMap.config.SEED_DATA_URL);
if (!response.ok) {
throw new Error(`Seed-Daten-Download fehlgeschlagen (HTTP ${response.status}).`);
}
const raw = await response.json();
return raw.map((item) => normalizePlace(item));
}
/**
* Initialisiert den Store: lädt persistierte Places aus LocalStorage oder,
* falls noch keine vorhanden sind, die Beispieldaten von
* EventMap.config.SEED_DATA_URL. Gibt { seedLoadFailed } zurück, damit
* main.js bei einem fehlgeschlagenen Download einen Hinweis anzeigen kann
* (die App bleibt dabei mit einer leeren Places-Liste nutzbar).
*/
async function initStore() {
const persisted = loadData();
if (persisted && Array.isArray(persisted.places) && persisted.places.length > 0) {
setState({ places: persisted.places.map((p) => normalizePlace(p)) });
return { seedLoadFailed: false };
}
try {
const places = await loadSeedPlaces();
saveData(places);
setState({ places });
return { seedLoadFailed: false };
} catch (err) {
console.warn('EventMap: Beispieldaten konnten nicht geladen werden.', err);
setState({ places: [] });
return { seedLoadFailed: true };
}
}
/** Persistiert state.places nach Änderungen (Erstellen/Bearbeiten/Löschen). */
function persistPlaces() {
saveData(state.places);
}
/**
* Merged patch in state.filters (initialisiert mit EventMap.filters.DEFAULT_FILTERS
* falls state.filters noch null ist) und setzt eine neue Objekt-Referenz.
*
* WICHTIG: EventMap.filters wird hier bewusst erst zur Aufrufzeit
* referenziert (nicht beim IIFE-Start destrukturiert), analog zum Muster
* zwischen js/ui/detail.js und js/ui/form.js - so ist store.js unabhängig
* von der genauen Ladereihenfolge relativ zu js/filters/filterEngine.js.
*/
function updateFilters(patch) {
const current = state.filters || EventMap.filters.DEFAULT_FILTERS;
setState({ filters: { ...current, ...patch } });
}
/** Selector: liefert state.places gefiltert nach state.filters/state.userLocation. */
function getFilteredPlaces() {
const filters = state.filters || EventMap.filters.DEFAULT_FILTERS;
return EventMap.filters.applyFilters(state.places, filters, state.userLocation);
}
EventMap.store.subscribe = subscribe;
EventMap.store.getState = getState;
EventMap.store.setState = setState;
EventMap.store.getSettings = getSettings;
EventMap.store.updateSettings = updateSettings;
EventMap.store.initStore = initStore;
EventMap.store.persistPlaces = persistPlaces;
EventMap.store.updateFilters = updateFilters;
EventMap.store.getFilteredPlaces = getFilteredPlaces;
})();