All files / app url.ts

100% Statements 17/17
100% Branches 2/2
100% Functions 6/6
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43          3x     3x   3x   3x 2x     3x         3x 3x 1x 2x   3x 2x   3x     3x 2x   3x 8x            
import { Option } from 'effect'
 
import { Camera, type LatLon } from '../core/domain'
 
/** The cue goes in the query, as `?p=lat,lon`, so a shared link opens with the same cue. */
export const CUE_PARAM = 'p'
 
/** The camera goes in the hash, as `#view=zoom/lat/lon/bearing/pitch`, the same form as MapLibre's own hash. */
export const CAMERA_HASH_KEY = 'view'
 
const CUE_DECIMALS = 7
 
export const cueToUrlValue = ({ lat, lon }: LatLon.LatLon): string =>
  `${lat.toFixed(CUE_DECIMALS)},${lon.toFixed(CUE_DECIMALS)}`
 
/** The URL with the cue and the camera written in; a missing cue removes the parameter. */
export const withState = (
  href: string,
  maybeCue: Option.Option<LatLon.LatLon>,
  maybeCamera: Option.Option<Camera.Camera>,
): URL => {
  const url = new URL(href)
  Option.match(maybeCue, {
    onNone: () => url.searchParams.delete(CUE_PARAM),
    onSome: cue => url.searchParams.set(CUE_PARAM, cueToUrlValue(cue)),
  })
  if (Option.isSome(maybeCamera)) {
    url.hash = `${CAMERA_HASH_KEY}=${Camera.toUrlValue(maybeCamera.value)}`
  }
  return url
}
 
export const readCueQuery = (href: string): Option.Option<string> =>
  Option.fromNullishOr(new URL(href).searchParams.get(CUE_PARAM))
 
export const readCamera = (href: string): Option.Option<Camera.Camera> =>
  Option.flatMap(
    Option.fromNullishOr(
      new URLSearchParams(new URL(href).hash.slice(1)).get(CAMERA_HASH_KEY),
    ),
    Camera.fromUrlValue,
  )