- 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>
124 lines
4.0 KiB
JavaScript
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;
|
|
})();
|