leaflet_svelte/docs/ARCHITECTURE.md
2026-04-22 01:27:38 +09:00

4.8 KiB
Raw Permalink Blame History

Architecture

Layer overview

src/
├── routes/                 # SvelteKit pages (thin; they compose features)
├── app.html                # plain SPA shell
├── app.css                 # global styles (imports Bootstrap + bootstrap-icons)
└── lib/
    ├── domain/             # pure types + pure functions (no Svelte, no fetch)
    ├── state/              # store factories (persisted, broadcast across tabs)
    ├── api/                # HTTP client + resource modules (points, scenarios…)
    ├── auth/               # auth store, login/logout, requireAuthenticated()
    ├── i18n/               # t() store, locale loading, message dictionaries
    ├── map/                # IMap interface + MapLibre impl + plot helpers + tools
    ├── ui/                 # library-agnostic primitives (panel, tab, editor…)
    └── features/           # vertical slices — one folder per feature
        ├── auth/           # Navbar, LoginForm
        ├── footer/
        ├── prediction/     # ControlPanel, ScenarioPanel, Point/Scenario/Curve editors
        ├── tracking/       # TelemetryPanel
        ├── workspaces/     # multi-layer workspace system
        ├── timeline/       # global playback clock
        └── settings/       # schema-driven settings panel
mocks/                      # dev-server API mock (enabled via VITE_USE_MOCK_API)

Rules of thumb

  • domain/ depends on nothing.
  • state/, i18n/, api/ depend only on domain/.
  • map/ depends on domain/ (and Maplibre, internally).
  • ui/ depends on domain/, state/, i18n/ at most.
  • features/* depend on everything in lib/ but never on each other directly — cross-feature coordination happens through stores (e.g. workspacesStore is shared by prediction + timeline).
  • routes/ compose features. Pages stay thin.

Data flow

Stores

$state → persisted via persisted() (localStorage + BroadcastChannel for cross-tab sync). workspaces, settings, and auth state live here.

API calls

All go through $api (src/lib/api/client.ts) which:

  • prepends VITE_API_BASE_URL
  • reads the csrftoken cookie (priming it via /api/csrf/ if missing)
  • serializes JSON bodies and parses structured DRF errors into ApiError
  • delegates 401s to the auth layer (setUnauthorizedHandler)

Auth

authStore is the single source of truth for who is logged in. Pages that require auth call requireAuthenticated() from $auth in onMount — this refreshes state and redirects to /login if anonymous. The API client triggers the same redirect on 401s.

Map

The map library is behind the IMap interface. Every plot operation (line, marker, circle) runs through a Scene — a named collection of layers owned by a feature. When a feature is done with its layers, scene.clear() or map.disposeScene(name) removes them all at once. This is how each workspace paints an independent trajectory without stepping on its neighbors.

Swapping MapLibre for another library means implementing IMap once (see src/lib/map/maplibre.ts) and updating createMapLibreMap() at one call-site inside Map.svelte.

Workspaces

A workspace is an independently-configured prediction layer. The user creates several, edits their flight parameters separately, runs each one, and sees all their trajectories overlaid on the same map with their own colors and opacities. workspacesStore.run(id) calls the prediction API and stores the result in-memory on that workspace.

WorkspaceRenderer.svelte listens to the workspaces store and repaints layers when workspaces change. It also drives the global timeline: whenever workspaces change it recomputes the global [min, max] time range (the union of every visible workspaces trajectory span) and updates the timeline store.

Timeline

timelineStore is a plain writable backed by a requestAnimationFrame loop. It exposes play/pause/seek/setSpeed. WorkspaceRenderer subscribes to time and draws a cursor marker on each visible workspace at that timestamp. Workspaces stay in sync because they all sample the same clock.

i18n

initI18n() (called from the root layout) loads the saved locale from localStorage, imports the JSON dictionary, and stores it. Components use the $t store from $i18n:

<h5>{$t('panel.workspaces')}</h5>

Adding a locale: drop xx.json under src/lib/i18n/locales/, register it in loaders and in the Locale type.

Build & deploy

  • @sveltejs/adapter-static with fallback: 'index.html' produces a pure SPA.
  • SSR is disabled in the root +layout.ts (ssr = false, prerender = false).
  • CSS is imported once in +layout.svelte (which loads app.css).
  • No server-side routes. All data comes from the Django REST backend.