Skip to content

Commit

Permalink
feat: (#1113) save map view state (#1125)
Browse files Browse the repository at this point in the history
* feat: (#1113) save map view state
  • Loading branch information
svkorepanov authored Apr 29, 2024
1 parent 023b47d commit fd5d173
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 6 deletions.
73 changes: 71 additions & 2 deletions src/app/(maps)/components/FullScreenMap.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
'use client'
import { useEffect, useState } from 'react'
import { GlobalMap } from '@/components/maps/GlobalMap'
import { useCallback, useEffect, useState } from 'react'
import { CameraInfo, GlobalMap } from '@/components/maps/GlobalMap'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'

export const FullScreenMap: React.FC = () => {
const [initialCenter, setInitialCenter] = useState<[number, number] | undefined>(undefined)
const [initialZoom, setInitialZoom] = useState<number | undefined>(undefined)
const router = useRouter()

const cameraParams = useCameraParams()

useEffect(() => {
const initialStateFromUrl = cameraParams.fromUrl()

if (initialStateFromUrl != null) {
setInitialCenter([initialStateFromUrl.center.lng, initialStateFromUrl.center.lat])
setInitialZoom(initialStateFromUrl.zoom)
return
}

getVisitorLocation().then((visitorLocation) => {
if (visitorLocation != null) {
setInitialCenter([visitorLocation.longitude, visitorLocation.latitude])
Expand All @@ -15,10 +28,18 @@ export const FullScreenMap: React.FC = () => {
})
}, [])

const handleCamerMovement = useCallback((camera: CameraInfo) => {
const url = cameraParams.toUrl(camera)

router.replace(url, { scroll: false })
}, [])

return (
<GlobalMap
showFullscreenControl={false}
initialCenter={initialCenter}
initialZoom={initialZoom}
onCameraMovement={handleCamerMovement}
/>
)
}
Expand All @@ -32,3 +53,51 @@ const getVisitorLocation = async (): Promise<{ longitude: number, latitude: numb
return undefined
}
}

function useCameraParams (): { toUrl: (camera: CameraInfo) => string, fromUrl: () => CameraInfo | null } {
const pathname = usePathname()
const initialSearchParams = useSearchParams()

function toUrl (camera: CameraInfo): string {
const params = new URLSearchParams(initialSearchParams)
params.delete('camera')

const queryParams = [
params.toString(),
`camera=${cameraInfoToQuery(camera)}`
]

return `${pathname}?${queryParams.filter(Boolean).join('&')}`
}

function fromUrl (): CameraInfo | null {
const cameraParams = initialSearchParams.get('camera')
if (cameraParams == null) {
return null
}

return queryToCameraInfo(cameraParams)
}

return { toUrl, fromUrl }
}

const cameraInfoToQuery = ({ zoom, center }: CameraInfo): string => {
return `${Math.ceil(zoom)}/${center.lat.toFixed(5)}/${center.lng.toFixed(5)}`
}

const queryToCameraInfo = (cameraParam: string): CameraInfo | null => {
const [zoomRaw, latitude, longitude] = cameraParam.split('/')
const lat = parseFloat(latitude)
const lng = parseFloat(longitude)
const zoom = parseInt(zoomRaw, 10)

if ([lat, lng, zoom].some(isNaN)) {
return null
}

return {
center: { lat, lng },
zoom
}
}
32 changes: 28 additions & 4 deletions src/components/maps/GlobalMap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'
import { useCallback, useState } from 'react'
import { Map, FullscreenControl, ScaleControl, NavigationControl, MapLayerMouseEvent, MapInstance } from 'react-map-gl/maplibre'
import { Map, FullscreenControl, ScaleControl, NavigationControl, MapLayerMouseEvent, MapInstance, ViewStateChangeEvent } from 'react-map-gl/maplibre'
import maplibregl, { MapLibreEvent } from 'maplibre-gl'
import { Point, Polygon } from '@turf/helpers'
import dynamic from 'next/dynamic'
Expand All @@ -13,6 +13,7 @@ import { OBCustomLayers } from './OBCustomLayers'
import { AreaType, ClimbType, MediaWithTags } from '@/js/types'
import { TileProps, transformTileProps } from './utils'
import MapLayersSelector from './MapLayersSelector'
import { debounce } from 'underscore'

export type SimpleClimbType = Pick<ClimbType, 'id' | 'name' | 'type'>

Expand All @@ -28,21 +29,31 @@ export interface HoverInfo {
mapInstance: MapInstance
}

export interface CameraInfo {
center: {
lng: number
lat: number
}
zoom: number
}

interface GlobalMapProps {
showFullscreenControl?: boolean
initialCenter?: [number, number]
initialZoom?: number
initialViewState?: {
bounds: maplibregl.LngLatBoundsLike
fitBoundsOptions: maplibregl.FitBoundsOptions
}
onCameraMovement?: (camera: CameraInfo) => void
children?: React.ReactNode
}

/**
* Global map
*/
export const GlobalMap: React.FC<GlobalMapProps> = ({
showFullscreenControl = true, initialCenter, initialViewState, children
showFullscreenControl = true, initialCenter, initialZoom, initialViewState, onCameraMovement, children
}) => {
const [clickInfo, setClickInfo] = useState<MapAreaFeatureProperties | null>(null)
const [hoverInfo, setHoverInfo] = useState<HoverInfo | null>(null)
Expand All @@ -51,15 +62,27 @@ export const GlobalMap: React.FC<GlobalMapProps> = ({
const [cursor, setCursor] = useState<string>('default')
const [mapStyle, setMapStyle] = useState<string>(MAP_STYLES.standard.style)

const onMove = useCallback(debounce((e: ViewStateChangeEvent) => {
if (onCameraMovement != null) {
onCameraMovement({
center: {
lat: e.viewState.latitude,
lng: e.viewState.longitude
},
zoom: e.viewState.zoom
})
}
}, 300), [])

const onLoad = useCallback((e: MapLibreEvent) => {
if (e.target == null) return
setMapInstance(e.target)
if (initialCenter != null) {
e.target.jumpTo({ center: initialCenter, zoom: 6 })
e.target.jumpTo({ center: initialCenter, zoom: initialZoom ?? 6 })
} else if (initialViewState != null) {
e.target.fitBounds(initialViewState.bounds, initialViewState.fitBoundsOptions)
}
}, [initialCenter])
}, [initialCenter, initialZoom])

/**
* Handle click event on the map. Place a market on the map and activate the side drawer.
Expand Down Expand Up @@ -121,6 +144,7 @@ export const GlobalMap: React.FC<GlobalMapProps> = ({
onDragStart={() => {
setCursor('move')
}}
onMove={onMove}
onDragEnd={() => {
setCursor('default')
}}
Expand Down

0 comments on commit fd5d173

Please sign in to comment.