Skip to main content

Components

shadcn/ui Components

The app uses shadcn/ui for standard UI components. These are installed directly into src/components/ui/ and can be customized:

ComponentUsage
ButtonPrimary actions, form submissions, destructive actions
CardContent containers for dashboard cards, detail sections
InputText inputs with label support
SelectDropdown selects for filters, form fields
BadgeTrip status badges (ACTIVE, COMPLETED, CANCELLED)
CheckboxToggle switches (GPS logging, auto-refresh)
LabelForm field labels
TextareaMulti-line text input (descriptions, notes)
SeparatorVisual dividers
DropdownMenuNavigation dropdowns (Features menu in header)

Theme

shadcn/ui uses CSS variables for theming with automatic dark mode support via the dark class on <html>. The theme is configured in globals.css with oklch colors.


Shared Components (@elcto/apollon-ui)

Custom components that are shared across apps via the @elcto/apollon-ui workspace package.

Map

File: shared/ui/src/components/map.tsx

Interactive MapLibre GL map with composable children components. Supports multiple tile providers with automatic dark/light theme switching.

Map Props

PropTypeDefaultDescription
latitudenumberCenter latitude
longitudenumberCenter longitude
center[lng, lat]Center as GeoJSON array (alternative to lat/lng)
zoomnumber13Initial zoom level
tilesMapTileProvider'carto'Tile provider
themeOverride'light' | 'dark'Override auto theme detection
childrenReactNodeMap children (markers, routes, etc.)

Tile Providers

ProviderLightDark
cartoCarto VoyagerCarto Dark Matter
openfreemapOpenFreeMap BrightCarto Dark Matter
openfreemap3dOpenFreeMap Liberty (60° pitch)Carto Dark Matter
positronOpenFreeMap PositronCarto Dark Matter
libertyOpenFreeMap Liberty (flat)Carto Dark Matter
fiordOpenFreeMap FiordCarto Dark Matter

Child Components

ComponentDescription
MapMarkerPosition marker with React children (dot, arrow, badge)
StaticMarkerSimple colored dot marker
MapRouteGeoJSON line layer for routes
FitBoundsAuto-fit map to given points

Current Position Marker

The GPS page uses MapMarker with a CurrentPositionMarker child that renders:

  • Dot#14F5BF with beacon pulse animation
  • Heading arrow — SVG chevron pointing in direction of travel (only when speed > 2 km/h)
  • Speed badge — Spring-animated speed display with useSpring, positioned opposite to heading arrow using 8-point anchoring
  • Dark mode — Adapts border/stroke colors for dark map tiles

Usage

import { Map, MapMarker, StaticMarker, MapRoute, FitBounds } from '@elcto/apollon-ui';

<Map latitude={52.52} longitude={13.40} zoom={14} tiles="carto">
<MapMarker longitude={13.40} latitude={52.52}>
<CurrentPositionMarker speed={12.5} heading={90} />
</MapMarker>
<StaticMarker latitude={52.51} longitude={13.39} color="#22c55e" />
<MapRoute points={gpsHistory} />
<FitBounds points={allPoints} />
</Map>

Spinner

Loading indicator with configurable size (sm, md, lg).

import { Spinner } from '@elcto/apollon-ui';

<Spinner size="md" />

ThemeToggle

Dark/light mode toggle with system preference detection. Persists to localStorage.

import { ThemeToggle } from '@elcto/apollon-ui';

<ThemeToggle />

Speed Overlay

Files: src/app/(overlay)/overlay/speed/page.tsx (Server) + speed-client.tsx (Client)

A minimal, embeddable speed display designed for OBS Studio browser source.

  • Real-time WebSocket — Subscribes to the speed channel
  • Spring animation — Smooth transitions via useSpring hook
  • Multi-Unit Cycling — Kommagetrennte Units mit Fade-Animation (?unit=kmh,knots)
  • OBS compatible — Transparent background, dark card design (#14F5BF accent)
  • Float mode?float=true für freistehende Darstellung ohne Border

For URL parameters, see Overlays.


Data Overlay

Files: src/app/(overlay)/overlay/data/page.tsx (Server) + data-client.tsx (Client)

Kombiniert Compass und Speed-Widget in einer kompakten Leiste — identisch zum Widget-Bar im Map-Overlay, als Standalone ohne Karte.

  • Real-time WebSocket — Subscribes to gps + speed channels
  • Compass + Speed — Divider-getrennt, einzeln ein-/ausblendbar
  • Multi-Unit Cycling — Kommagetrennte Units mit Fade-Animation
  • Float mode?float=true für freistehende Darstellung ohne Border

For URL parameters, see Overlays.


Map Overlay

Files: src/app/(overlay)/overlay/map/page.tsx (Server) + map-client.tsx (Client)

Fullpage live GPS map for OBS Studio browser source with heading arrow, speed badge, compass widget, and AIS vessel tracking.

For the full list of URL parameters, see Overlays.


Compass Overlay

Files: src/app/(overlay)/overlay/compass/page.tsx (Server) + compass-client.tsx (Client)

Standalone compass widget for OBS. Shows heading needle, degree value, and 16-point cardinal direction.

For URL parameters, see Overlays.


Pegel Overlay

Files: src/app/(overlay)/overlay/pegel/page.tsx (Server) + pegel-client.tsx (Client)

Standalone Wasserstand-Widget für OBS. Zeigt aktuellen Pegel der nächsten WSV-Station mit Gewässername und Trend-Farbkodierung (blau=niedrig, grün=normal, rot=hoch, grau=unbekannt).

For URL parameters, see Overlays.


AIS Vessel Markers

The map overlay displays AIS (Automatic Identification System) vessel data received via the vessels WebSocket channel. The Rust API supports two AIS sources, each toggled independently: AISstream.io ([ais.aisstream], wss://stream.aisstream.io, via tokio-tungstenite) and/or a local NMEA/AIS receiver ([ais.local], e.g. Quark-elec A027+, raw NMEA 0183 over TCP, decoded by the hand-rolled apollon-ais-nmea crate). Enabling both runs dual mode: the local receiver takes priority for vessels in antenna range while aisstream fills in everything beyond it (see Configuration). Both sources feed the same vessels channel, so the map works unchanged regardless of which are active. Each vessel carries a source field ("local" or "aisstream") marking its effective tracking source, so the frontend can distinguish or style them.

Vessel Display

Vessel StateMarkerArrowBadge
Moving (Unterwegs)Orange dotOrange heading arrowSpeed, name, info rotation
Stationary (Vor Anker, Festgemacht, Auf Grund)Gray dotNoneName only, no speed

Badge Info Rotation

Moving vessels rotate through available info badges every 10 seconds with a fade animation:

  • Speed in km/h (only shown when > 0)
  • Length (meters, from ShipStaticData: A+B, if available)
  • Beam (meters, from ShipStaticData: C+D, if available)
  • Ship type (if not "Unknown")

When only one info item is available, it displays statically without fade. Ships without name show only the info badge. Ships without any data (0 km/h, no type/length/beam) show no badge.

URL Parameters

ParameterDefaultDescription
aistrueShow AIS vessels. false / 0 to disable
aisnamestrueShow vessel name badges. false / 0 to disable
aisinfotrueShow info badges (speed, length, beam, type). false / 0 to disable
aisbadgekm10Max distance (km) for name/info badges
aisbadgescale1Scale factor for AIS badges (e.g. 1.5, 2)

Distance-Based Opacity

Badge colors and blur scale with distance from own ship (no container opacity, so backdrop-blur remains visible):

  • ≤1km: full visibility
  • 1–2km: 95%
  • 2–3km: 85%
  • 3–4km: 70%
  • 4–5km: 50%
  • 5–8km: 35%
  • ≥8km: 15% minimum

z-index Layering

Layerz-index
Own ship marker100
Moving vessels10
Stationary vessels1

Unterwegs, Vor Anker, Festgemacht, Nicht unter Kommando, Tiefgangbeschränkt, Auf Grund, Fischend, Unter Segel, Manövrierunfähig.

Configuration

AIS is configured in the Rust API via the [ais] TOML section:

KeyDescription
enabledMaster switch for AIS tracking
local_priority_ttl_secsDual mode: seconds an MMSI stays local-tracked after its last local message before aisstream resumes it (default 120)
ignore_mmsiArray of MMSI numbers to blacklist — applies to both sources

The AISstream.io source is configured in [ais.aisstream]:

KeyDescription
enabledEnable the AISstream.io source
api_keyAISstream.io API key (required when this source is enabled)
radius_kmRadius around GPS position for the aisstream vessel subscription

The local receiver is configured in [ais.local]:

KeyDescription
enabledEnable the local NMEA/AIS receiver source
hostLocal receiver host, e.g. 192.168.1.69
portLocal receiver TCP port, e.g. 2000
reception_range_kmOptional. Only affects the map's bbox circle — a physical VHF receiver has no fixed radius, so received vessels are never filtered by this value. Unset = no bbox shown.

Enabling both [ais.aisstream] and [ais.local] runs dual mode: local takes priority for in-range vessels, aisstream is secondary. A vessel heard live via local is not double-tracked from aisstream; once it goes silent on local for longer than local_priority_ttl_secs (out of antenna range), aisstream resumes tracking it (handoff).


WatchlistStars

File: src/components/watchlist-stars.tsx

Overlay component that displays the current watchlist alert status. Used by the map overlay and watchlist overlay pages.

Props

PropTypeDescription
alertWatchlistAlert | nullThe current top alert to display. null hides the component.

Display Modes

  • Stars mode (displayType: "stars") -- Shows 5 stars filled right-to-left based on alert.stars count. The boundary star (first filled) pulses with a star-pulse animation. Stars use alert.starColor for fill/stroke.
  • Custom icon mode (displayType: "custom") -- Renders a Lucide icon specified by alert.overlayIcon (e.g. "ship", "anchor"). The icon pulses with an icon-pulse animation and uses alert.starColor for fill/stroke.
  • Hidden -- Returns null when alert is null or alert.overlayEnabled is false.

Behavior

  • Appears in the top-right corner with a fade-in + slide-in animation
  • Dark backdrop blur container for OBS compatibility
  • Lucide icon names are resolved dynamically and cached for performance
import { WatchlistStars } from '@/components/watchlist-stars';

<WatchlistStars alert={topAlert} />

Watchlist Overlay

Files: src/app/(overlay)/overlay/watchlist/page.tsx (Server) + watchlist-client.tsx (Client)

Standalone watchlist alert overlay for OBS Studio browser source. Subscribes to the watchlist WebSocket channel and displays the WatchlistStars component with the top alert.


GPS Tracker Page

Files: src/app/(app)/gps/page.tsx (Server) + gps-client.tsx (Client)

Displays real-time GPS position via WebSocket with interactive map.

  • Subscribes to speed and gps WebSocket channels for live updates
  • Dead reckoning interpolation for smooth marker movement between GPS updates
  • Heading arrow + spring-animated speed badge on current position marker
  • Beacon pulse animation on dot
  • Tile provider selector (Carto / OpenStreetMap) with auto dark/light mode
  • Map auto-centers on position via LiveTracker
  • Manual refresh button as REST fallback

Vessels Registry

Files: src/app/(app)/feature/vessels/page.tsx (Registry + History Log tabs) + [mmsi]/page.tsx (detail) + [mmsi]/edit/page.tsx (edit)

Vessels, die bereits in der Watchlist hinterlegt sind, tragen in der Registry-Tabelle ein Auge-Icon direkt neben der MMSI. Ein Klick öffnet die Watchlist vorgefiltert auf diese MMSI (/feature/watchlist?q=<mmsi>). Über den Filter „Nur Watchlist" lässt sich die Tabelle auf beobachtete Vessels einschränken. Auf der Detail-Page ([mmsi]) erscheint bei beobachteten Vessels zusätzlich ein „Watchlist"-Button neben „Edit" mit demselben Deep-Link.

Umgekehrt verlinkt die Watchlist-Tabelle (/feature/watchlist) jede MMSI auf die zugehörige Vessel-Detail-Page (/feature/vessels/<mmsi>).

Der Watchlist-Match ist rein client-seitig (kein API-Feld): die Vessels-Seiten laden watchlist/entries mit und matchen per MMSI über den Helper feature/vessels/watchlist-match.ts (buildWatchedSet / isWatched, normalisiert numberstring). Die ?q=-Vorfilterung wird auf der Watchlist-Seite über useSearchParams() (in einer Suspense-Boundary) als initialer Suchwert geseedet.


Trip Management

Full CRUD with filtering by status, client, category, date range, GPS tracking, and search.

Pages

PagePathDescription
Trip List/tripsFilterable list with status badges
New Trip/trips/newCreate with sender/receiver, cargo, GPS toggle
Trip Detail/trips/[id]Map with route, complete/delete actions
Edit Trip/trips/[id]/editUpdate all trip fields
Categories/trips/categoriesInline CRUD for cargo categories

Client Management

Client directory with address management, contact persons, and trip associations.

Pages

PagePathDescription
Client List/clientsNames, emails, trip counts
New Client/clients/newCompany info, address, dynamic contacts
Client Detail/clients/[id]Full info with trips as sender/receiver

Watchlist Management

Vessel proximity watchlist with categories, custom sounds, and configurable alert thresholds.

Pages

PagePathDescription
Entry List/feature/watchlistAll tracked vessels with MMSI/name search
New Entry/feature/watchlist/newAdd vessel by MMSI
Entry Detail/feature/watchlist/[id]View/edit entry, category, sounds, thresholds
Settings/feature/watchlist/settingsGlobal defaults, categories, sound management

API Proxy Routes

RouteDescription
/api/watchlist/entriesEntry CRUD
/api/watchlist/categoriesCategory CRUD
/api/watchlist/settingsSettings read/update
/api/watchlist/soundsSound list
/api/watchlist/sounds/uploadSound upload (multipart)
/api/watchlist/test-alertTrigger test alert