Phase 0+1: Scaffold, Datenmodell, Karte und Liste
Reines HTML5/CSS3/JavaScript ohne Build-Tooling, lauffaehig direkt per Doppelklick auf index.html (file://, kein Server noetig): - Datenmodell fuer Ausflugsziele mit allen Feldern aus der Anforderung, 15 feste Kategorien inkl. Farbe/Icon - Zentraler Pub/Sub-Store mit LocalStorage-Persistenz und Migrations-Grundgeruest - Interaktive Karte (Leaflet + Leaflet.markercluster, lokal vendored) mit kategoriefarbenen Markern, Clustering, Popups, Geolocation-Button - Kartengrundlage ueber CARTO-Basemaps statt tile.openstreetmap.org, da dessen Referer-Pflicht file://-Aufrufe blockiert - Seed-Daten als eingebettetes Script (data/seed.js) statt JSON-Datei, da fetch() auf lokale Dateien unter file:// nicht zuverlaessig funktioniert - Ergebnisliste mit 10 Beispiel-Ausflugszielen, sicher gerendert ueber textContent/DOM-APIs statt innerHTML (Vorbereitung fuer spaeteren KI-JSON-Import mit Fremddaten) - Responsives Layout (Desktop nebeneinander, Mobile gestapelt)
This commit is contained in:
+168
@@ -0,0 +1,168 @@
|
||||
# EventMap – Gesamtplan
|
||||
|
||||
Dieses Dokument fasst den vom architect-Agenten erarbeiteten Plan zusammen
|
||||
und wird phasenweise fortgeschrieben. Aktuell umgesetzt: **Phase 0 (Scaffold)**
|
||||
und **Phase 1 (Datenmodell + Karte + Liste, read-only mit Beispieldaten)**.
|
||||
|
||||
## Tech-Stack
|
||||
|
||||
- Vanilla HTML5/CSS3/JavaScript, klassische Scripts (kein `type="module"`);
|
||||
Module-Ersatz über einen globalen Namespace `window.EventMap` +
|
||||
IIFE-Kapselung pro Datei, siehe `docs/adr/0001-vanilla-esm.md`. Grund:
|
||||
die App wird per Doppelklick direkt aus `file://` geöffnet, native
|
||||
ES-Module würden dort per CORS-Policy blockiert.
|
||||
- Kein Build-Tool, kein Bundler, kein Framework
|
||||
- Karte: [Leaflet](https://leafletjs.com/) 1.9.4 + [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster) 1.5.3, lokal vendored unter `vendor/`
|
||||
- Kartengrundlage: OpenStreetMap-Daten, ausgeliefert über CARTO-Basemap-Tiles
|
||||
(`basemaps.cartocdn.com`, Stil "light_all") statt des offiziellen
|
||||
`tile.openstreetmap.org`. Grund: dessen Tile Usage Policy verlangt seit
|
||||
2024 einen gültigen HTTP-Referer, den `file://`-Seiten (Doppelklick-Start)
|
||||
grundsätzlich nicht mitsenden können ("Access blocked: Referer is
|
||||
required"). CARTO liefert dieselben OSM-Daten kartographisch auf, ohne
|
||||
diese Einschränkung; Attribution zeigt weiterhin OpenStreetMap + CARTO.
|
||||
- Datenhaltung: LocalStorage (Offline-first), vorbereitet für spätere Migration auf Backend/API
|
||||
|
||||
## Ordnerstruktur
|
||||
|
||||
```
|
||||
index.html
|
||||
css/ reset, variables (Design-Tokens), layout, components, responsive
|
||||
js/config/ constants.js, categories.js
|
||||
js/store/ store.js (Pub/Sub-State), storage.js (LocalStorage-IO), migrations.js
|
||||
js/models/ place.js (Normalisierung), schema.js (Feldstruktur/Defaults)
|
||||
js/map/ mapController.js, markers.js, geolocation.js
|
||||
js/ui/ list.js (Ergebnisliste; weitere UI-Module folgen in späteren Phasen)
|
||||
js/utils/ dom.js, geo.js, id.js
|
||||
data/seed.js Beispieldaten (10 Ausflugsziele, wird nur beim allerersten Start geladen)
|
||||
vendor/ lokal vendorte Leaflet-Bibliotheken (JS/CSS/Bildassets)
|
||||
docs/ dieser Plan, ADRs
|
||||
```
|
||||
|
||||
## Datenmodell (Ausflugsziel / Place)
|
||||
|
||||
Das interne Place-Objekt orientiert sich eng am verbindlichen KI-Import-JSON-Format
|
||||
aus `anforderung.md` (siehe dortiger Abschnitt "Vorgabe für das KI-JSON"):
|
||||
|
||||
- Basisfelder: `name`, `category`, `shortDescription`, `description`, `latitude`,
|
||||
`longitude`, `address`, `city`, `region`
|
||||
- `ageRecommendation: { min, max }`
|
||||
- `cost: { type: 'free'|'paid'|'mixed'|'unknown', description }`
|
||||
- `openingHours`, `durationMinutes`
|
||||
- `facilities: { strollerAccessible, wheelchairAccessible, toilets, parking, restaurant }`
|
||||
- `environment: 'indoor'|'outdoor'|'mixed'`
|
||||
- `website`, `image`, `rating`, `tags[]`, `source`, `sourceVerified`
|
||||
- Intern generiert: `id` (UUID), `createdAt`, `updatedAt` (ISO-Strings)
|
||||
|
||||
`js/models/place.js` normalisiert rohe Objekte (aus `data/seed.js` oder späteren
|
||||
Importen) zu diesem vollständigen internen Format; die Kategorie wird dabei
|
||||
immer auf eine gültige Kategorie-`id` (Slug, siehe `js/config/categories.js`)
|
||||
aufgelöst – entweder direkt (falls schon ein Slug vorliegt) oder über das
|
||||
Label (z.B. `"Spielplatz"` → `spielplatz`), wie es die KI liefern würde.
|
||||
|
||||
### Kategorien
|
||||
|
||||
14 Kategorien aus der Anforderung + "Sonstige" als generischer Fallback
|
||||
(insgesamt 15), jeweils mit `id` (Slug), `label`, `color` (Hex) und `icon`
|
||||
(Emoji-Glyph) – siehe `js/config/categories.js`.
|
||||
|
||||
## Store-Architektur
|
||||
|
||||
`js/store/store.js` ist ein zentraler Pub/Sub-Store:
|
||||
|
||||
```js
|
||||
state = { places, filters: null, userLocation: null, selectedPlaceId: null }
|
||||
```
|
||||
|
||||
- `subscribe(fn)` registriert Listener, `setState(patch)` merged den State und
|
||||
benachrichtigt alle Subscriber.
|
||||
- `initStore()` lädt beim Start persistierte Daten aus LocalStorage
|
||||
(`js/store/storage.js`). Ist noch nichts gespeichert, wird `data/seed.js`
|
||||
geladen, normalisiert und einmalig persistiert.
|
||||
- Einstellungen (`lastFilters`, `lastMapView`, `userConsentGeo`, `defaultCenter`)
|
||||
werden getrennt über `getSettings()`/`updateSettings()` verwaltet und im
|
||||
Key `eventmap.settings.v1` persistiert.
|
||||
|
||||
### LocalStorage-Schema
|
||||
|
||||
- `eventmap.data.v1` → `{ schemaVersion: 1, places: [...], updatedAt }`
|
||||
- `eventmap.settings.v1` → `{ lastFilters, lastMapView, userConsentGeo, defaultCenter }`
|
||||
|
||||
`js/store/migrations.js` stellt eine schlanke, sequenziell anwendbare
|
||||
Migrations-Infrastruktur bereit (`migrate(data)`). Aktuell existiert nur
|
||||
Schema-Version 1, das Migrationsarray ist entsprechend leer.
|
||||
|
||||
`defaultCenter` (frei wählbares Fallback-Kartenzentrum) ist als Datenfeld
|
||||
vorbereitet, aber in Phase 1 noch nicht über die UI einstellbar. Solange kein
|
||||
`defaultCenter` gesetzt ist, verwendet die Karte den hartcodierten Default aus
|
||||
`js/config/constants.js` (geografische Mitte Deutschlands, lat 51.1657,
|
||||
lng 10.4515, zoom 6).
|
||||
|
||||
## Karte
|
||||
|
||||
- `js/map/mapController.js`: initialisiert Leaflet-Karte + OSM-Tile-Layer
|
||||
(inkl. korrekter Attribution), verwaltet ein `L.markerClusterGroup`,
|
||||
reagiert per `subscribe()` auf Änderungen an `state.places` (Marker neu
|
||||
rendern) und `state.selectedPlaceId` (Karte zentrieren + Popup öffnen).
|
||||
`fitToPlaces()` passt die Ansicht auf alle vorhandenen Places an, mit
|
||||
Fallback auf das Default-Center falls keine Places mit gültigen
|
||||
Koordinaten vorhanden sind.
|
||||
- `js/map/markers.js`: erzeugt je Kategorie ein `L.divIcon` (Farbe + Emoji,
|
||||
kein PNG-Sprite nötig) und den Popup-Inhalt (Name, Kategorie,
|
||||
Kurzbeschreibung) als DOM-Node (nicht als HTML-String, siehe
|
||||
Sicherheitshinweis unten).
|
||||
- `js/map/geolocation.js`: fragt den Standort nur auf explizite
|
||||
Nutzeraktion (Button "Meinen Standort verwenden") ab, setzt bei Erfolg
|
||||
`userLocation` im Store und zentriert die Karte; bei Ablehnung/Fehler
|
||||
bleibt das Default-Center erhalten und ein Hinweis wird angezeigt.
|
||||
|
||||
## Ergebnisliste
|
||||
|
||||
`js/ui/list.js` rendert `state.places` als Cards (Name, Kategorie mit
|
||||
Farbe/Icon, Kurzbeschreibung). Ein Klick auf eine Card setzt
|
||||
`selectedPlaceId` im Store; `mapController.js` reagiert darauf und
|
||||
zentriert die Karte auf den zugehörigen Marker. Filterung (Phase 4) ist
|
||||
hier noch nicht implementiert – aktuell werden immer alle Places gezeigt.
|
||||
|
||||
**Sicherheitshinweis:** Da künftig Fremddaten per KI-Import in die App
|
||||
gelangen, wird an keiner Stelle `innerHTML` mit Place-Daten befüllt.
|
||||
`js/utils/dom.js` stellt dafür `el()` (DOM-Erzeugung über `textContent`/
|
||||
Properties) sowie `escapeHtml()` als zusätzliches Sicherheitsnetz bereit.
|
||||
|
||||
## Layout
|
||||
|
||||
- Header mit Titel "EventMap".
|
||||
- Mobile/Tablet (< 1024px): Karte oben (fixe Höhe ca. 50vh), Liste darunter,
|
||||
normal scrollbar.
|
||||
- Desktop (≥ 1024px): Grid mit Karte (~65%) und Liste-Sidebar (~35%)
|
||||
nebeneinander, siehe `css/responsive.css`.
|
||||
|
||||
## Kein Dev-Server nötig
|
||||
|
||||
Die App verwendet klassische `<script>`-Tags statt ES-Module (siehe
|
||||
`docs/adr/0001-vanilla-esm.md`) und lässt sich daher **per Doppelklick auf
|
||||
`index.html`** direkt aus dem lokal synchronisierten Ordner öffnen
|
||||
(`file://...`) – ein lokaler HTTP-Server ist dafür nicht mehr nötig.
|
||||
|
||||
Die Beispieldaten liegen in `data/seed.js` als eingebettetes
|
||||
`<script>`-Objekt (`EventMap.data.SEED_PLACES`) statt als `.json`-Datei,
|
||||
da `fetch()` auf lokale Dateien über `file://` in manchen Browsern
|
||||
(insbesondere Chrome/Chromium) blockiert wird. `initStore()` liest die
|
||||
Seed-Daten dadurch synchron direkt aus dem globalen Namespace, ganz ohne
|
||||
`fetch()`.
|
||||
|
||||
## Phasenübersicht
|
||||
|
||||
| Phase | Inhalt | Status |
|
||||
|---|---|---|
|
||||
| 0 | Scaffold (Ordnerstruktur, vendored Leaflet, Grundgerüst) | ✅ umgesetzt |
|
||||
| 1 | Datenmodell, Karte, Liste (read-only, Seed-Daten) | ✅ umgesetzt |
|
||||
| 2 | Detailansicht (Titelbild, vollständige Beschreibung, Mini-Karte, Navigation-Button, Bearbeiten/Löschen) | offen |
|
||||
| 3 | Formular zum manuellen Anlegen/Bearbeiten (Validierung, Koordinatenprüfung) | offen |
|
||||
| 4 | Filter- und Suchfunktion (Kategorie, Entfernung, Alter, Kosten, Indoor/Outdoor, Ausstattung, Bewertung, Region) inkl. Anwendung auf Karte + Liste | offen |
|
||||
| 5 | Route/Navigation-Integration (z.B. Link zu externem Kartendienst) | offen |
|
||||
| 6 | KI-Prompt-Generator (Eingabeformular, Promptgenerierung, Copy/Download) | offen |
|
||||
| 7 | KI-JSON-Import (Validierung, Vorschau, Übernahme ins LocalStorage) | offen |
|
||||
|
||||
Nicht Teil von Phase 0/1 und daher bewusst noch nicht angelegt:
|
||||
Filter-Modul, Detail-Modul, Formular-Modul, Prompt-Generator-Modul,
|
||||
Import-Modul.
|
||||
Reference in New Issue
Block a user