import { Loader2, Map as MapIcon, Mountain, MountainSnow, Rotate3d, Satellite } from 'lucide-react' import type { CSSProperties } from 'react' import { useTranslation } from '../../i18n' import type { FerrataMapLayer } from '../Ferratas/useFerrataMapLayer' import type { FerrataStatus } from '../Ferratas/useTripFerratas' import { fs } from '../shared/DialogShell' import { Tooltip } from '../shared/Tooltip' import { MAP_CONTROL_SHADOW } from './mapControlShadow' export type BaseLayer = 'default' | 'satellite' export type TourBaseLayer = BaseLayer | 'topo' const FROSTED_SHELL = { display: 'inline-flex', alignItems: 'center', padding: 4, borderRadius: 999, pointerEvents: 'auto' as const, background: 'var(--sidebar-bg)', backdropFilter: 'blur(20px) saturate(180%)', WebkitBackdropFilter: 'blur(20px) saturate(180%)', boxShadow: MAP_CONTROL_SHADOW, } // Where the switcher sits and how much room it takes, so a control placed beside it // (the phone's compass) is positioned off the same numbers instead of guessing them. // Both map engines place the switcher with the inset, so they cannot drift apart either. /** Distance from the map's left edge (plus any side panel) to the switcher. */ export const MAP_LAYER_SWITCHER_INSET = 20 /** * Outer size of the round shell: a 34px button in 4px of padding, the same shell as * MapCompassPill. The markup below keeps its literals, and FE-COMP-MAPLAYER-006 holds * them to this number. */ export const MAP_ROUND_CONTROL_SIZE = 42 // Round base-layer switcher for both planner maps, Leaflet and GL (default street // tiles ↔ satellite). Same frosted shell as MapCompassPill so it lines up with the // other map controls; the icon shows the layer it switches to. export function MapLayerSwitcher({ active, onToggle }: { active: BaseLayer; onToggle: () => void }) { const { t } = useTranslation() const isSatellite = active === 'satellite' const Icon = isSatellite ? MapIcon : Satellite const label = isSatellite ? t('map.baseLayer.switchToDefault') : t('map.baseLayer.switchToSatellite') return (
{/* Same tooltip as its neighbours on the map, for the same reasons — see TripRouteOverviewPill. To the right, though: this one sits at the bottom left of the map, where a tooltip to the left would land on the day sidebar and one below would land off the screen. */}
) } const TOUR_LAYERS = [ { id: 'default', labelKey: 'map.baseLayer.default', Icon: MapIcon }, { id: 'topo', labelKey: null, Icon: Mountain }, { id: 'satellite', labelKey: 'map.baseLayer.satellite', Icon: Satellite }, ] as const /** A pill segment of the Tours control; `filled` lets its class paint the background. */ const segmentStyle = (filled: boolean): CSSProperties => ({ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 5, minHeight: 34, borderRadius: 999, border: 'none', cursor: 'pointer', background: filled ? undefined : 'transparent', padding: '0 10px', fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 600, }) type Translate = (key: string, params?: Record) => string /** What the via ferrata layer is doing, in words, for the toggle's tooltip and the screen reader. */ function ferrataStatusLabel(status: Exclude, count: number, t: Translate): string { switch (status) { case 'loading': return t('ferratas.loading') case 'error': return t('ferratas.error') case 'offline': return t('ferratas.offline') case 'tooLarge': return t('ferratas.zoomIn') case 'ready': return t('ferratas.count', { count }) } } /** * The via ferrata switch with its count as a badge: how many ferratas reach * into the view, a small spinner while tiles load, and the warning tone when * there is a reason it cannot load (offline, the mirrors down, the view too * wide), which the tooltip names. Nothing beside the switch, so the bottom row * stays one pill wide. `compact` drops the word and keeps the icon and the * badge; the tooltip and the accessible name still say what it is. */ function FerrataToggle({ layer, compact = false }: { layer: FerrataMapLayer; compact?: boolean }) { const { t } = useTranslation() const { active, status, count } = layer const live = active && status !== 'idle' ? ferrataStatusLabel(status, count, t) : null const problem = status === 'error' || status === 'offline' || status === 'tooLarge' return ( <> {/* Polite, so a screen reader hears the count arrive without being interrupted. */} {live ?? ''} ) } /** * Per-view Tours control. It never reads or writes the global map setting. * `compact` shows the base layers and the via ferrata switch by their icons * alone, each named by its tooltip, for a bottom row that has to share its * width (the 3D map's, beside the flyover dock). */ export function TourMapLayerSwitcher({ active, onChange, onToggleTerrain, terrainActive = false, ferrataLayer, compact = false, }: { active: TourBaseLayer onChange: (layer: TourBaseLayer) => void /** Offers the 3D terrain switch beside the base layers. */ onToggleTerrain?: () => void terrainActive?: boolean /** Offers the via ferrata switch, with its count badge, beside the base layers. */ ferrataLayer?: FerrataMapLayer compact?: boolean }) { const { t } = useTranslation() return (
{TOUR_LAYERS.map(({ id, labelKey, Icon }) => { const selected = active === id const label = labelKey ? t(labelKey) : 'Topo' return ( ) })} {(ferrataLayer || onToggleTerrain) && } {ferrataLayer && } {onToggleTerrain && ( )}
) }