Skip to content

Commit

Permalink
wip: reuse new maplibre-backed map
Browse files Browse the repository at this point in the history
  • Loading branch information
viet nguyen committed Feb 26, 2024
1 parent dc49346 commit 4b804da
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 165 deletions.
1 change: 0 additions & 1 deletion src/app/(default)/area/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Link from 'next/link'
import { Metadata } from 'next'
import { validate } from 'uuid'
import { MapPinLine, Lightbulb, ArrowRight } from '@phosphor-icons/react/dist/ssr'
import 'mapbox-gl/dist/mapbox-gl.css'
import Markdown from 'react-markdown'

import PhotoMontage, { UploadPhotoCTA } from '@/components/media/PhotoMontage'
Expand Down
2 changes: 1 addition & 1 deletion src/app/(default)/components/ui/AreaPageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const AreaPageContainer: React.FC<{
{breadcrumbs == null ? <BreadCrumbsSkeleton /> : breadcrumbs}
{children == null ? <ContentSkeleton /> : children}
</div>
<div id='#map' className='w-full mt-16 relative h-[90vh] border-t snap-start snap-normal'>
<div id='map' className='w-full mt-16 relative h-[90vh] border-t snap-start snap-normal'>
{map != null && map}
</div>
</article>
Expand Down
34 changes: 34 additions & 0 deletions src/app/(maps)/components/FullScreenMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'
import { useEffect, useState } from 'react'
import { GlobalMap } from '@/components/maps/GlobalMap'

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

useEffect(() => {
getVisitorLocation().then((visitorLocation) => {
if (visitorLocation != null) {
setInitialCenter([visitorLocation.longitude, visitorLocation.latitude])
}
}).catch(() => {
console.log('Unable to determine user\'s location')
})
}, [])

return (
<GlobalMap
showFullscreenControl={false}
initialCenter={initialCenter}
/>
)
}

const getVisitorLocation = async (): Promise<{ longitude: number, latitude: number } | undefined> => {
try {
const res = await fetch('/api/geo')
return await res.json()
} catch (err) {
console.log('ERROR', err)
return undefined
}
}
6 changes: 2 additions & 4 deletions src/app/(maps)/maps/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { GlobalMap } from '@/components/maps/GlobalMap'
import { ProfileMenu } from '../components/ProfileMenu'
import { FullScreenMap } from '../components/FullScreenMap'

export const dynamic = 'force-dynamic'

export default async function MapPage (): Promise<any> {
return (
<div className='w-full h-full'>
<ProfileMenu />
<GlobalMap
showFullscreenControl={false}
/>
<FullScreenMap />
</div>
)
}
5 changes: 4 additions & 1 deletion src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,12 @@ A slightly deemphasized dotted underline for a tag in order to not competing wit
}

/**
* Force mapbox-gl library to use our font otherwise components inside the map
* Force mapbox-gl/maplibre library to use our font otherwise components inside the map
* will use their font and look out of place.
*/
.mapboxgl-map {
font-family: inherit !important;
}
.maplibregl-map {
font-family: inherit !important;
}
33 changes: 10 additions & 23 deletions src/components/maps/AreaInfoHover.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,40 @@
import * as Popover from '@radix-ui/react-popover'
import { HoverInfo, MapAreaFeatureProperties } from './AreaMap'
import { getAreaPageFriendlyUrl } from '@/js/utils'
import { Card } from '../core/Card'
import { EntityIcon } from '@/app/(default)/editArea/[slug]/general/components/AreaItem'
import { SelectedPolygon } from './AreaActiveMarker'
import { HoverInfo, MapAreaFeatureProperties } from './GlobalMap'

/**
* Area info panel
*/
export const AreaInfoHover: React.FC<HoverInfo> = ({ data, geometry, mapInstance }) => {
const ancestors = data?.ancestors == null ? null : JSON.parse(data.ancestors)
const pathTokens = data?.pathTokens == null ? null : JSON.parse(data.pathTokens)

let screenXY
if (geometry.type === 'Point') {
screenXY = mapInstance.project(geometry.coordinates)
} else {
return <SelectedPolygon geometry={geometry} />
}

const parentId = ancestors?.[ancestors.length - 2] ?? null
const parentName = pathTokens?.[pathTokens.length - 2] ?? 'Unknown'
return (
<Popover.Root defaultOpen>
<Popover.Anchor style={{ position: 'absolute', left: screenXY.x, top: screenXY.y }} />
<Popover.Content align='center' side='top' alignOffset={12}>
{data != null && <Content {...data} parentName={parentName} parentId={parentId} />}
<Popover.Arrow />
<Popover.Content align='center' side='top' sideOffset={8} collisionPadding={24} className='z-50'>
{data != null && <Content {...data} />}
</Popover.Content>
</Popover.Root>
)
}

export const Content: React.FC<MapAreaFeatureProperties & { parentName: string, parentId: string | null }> = ({ id, name, parentName, parentId }) => {
const url = parentId == null
? parentName
: (
<a
href={getAreaPageFriendlyUrl(parentId, parentName)}
className='inline-flex items-center gap-1.5'
>
<EntityIcon type='crag' size={16} /><span className='text-secondary font-medium hover:underline '>{parentName}</span>
</a>
)
export const Content: React.FC<MapAreaFeatureProperties> = ({ id, areaName, climbs }) => {
return (
<Card>
<div className='flex flex-col gap-y-1 text-xs'>
<div>{url}</div>
<div className='ml-2'>
<span className='text-secondary'>&#8735;</span><a href={getAreaPageFriendlyUrl(id, name)} className='text-sm font-medium hover:underline'>{name}</a>
<a href={getAreaPageFriendlyUrl(id, areaName)} className='text-base font-medium hover:underline'>{areaName}</a>
<div className='font-sm text-secondary flex items-center gap-1'>
<EntityIcon type='crag' size={16} />
·
<span className='text-xs'>{climbs.length} climbs</span>
</div>
</div>
</Card>
Expand Down
98 changes: 8 additions & 90 deletions src/components/maps/AreaMap.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Map, ScaleControl, FullscreenControl, NavigationControl, Source, Layer, MapLayerMouseEvent, LineLayer, MapInstance } from 'react-map-gl'
import { Source, Layer, LineLayer, MapInstance } from 'react-map-gl'
import dynamic from 'next/dynamic'
import { lineString, Point, Polygon, point } from '@turf/helpers'
import { lineString, Point, Polygon } from '@turf/helpers'
import lineToPolygon from '@turf/line-to-polygon'
import 'maplibre-gl/dist/maplibre-gl.css'

import { AreaMetadataType, AreaType } from '../../js/types'
import { MAP_STYLES } from './BaseMap'
import { AreaInfoDrawer } from './AreaInfoDrawer'
import { AreaInfoHover } from './AreaInfoHover'
import { SelectedFeature } from './AreaActiveMarker'
import { GlobalMap } from './GlobalMap'

type ChildArea = Pick<AreaType, 'uuid' | 'areaName'> & { metadata: Pick<AreaMetadataType, 'lat' | 'lng' | 'leaf' | 'bbox' | 'polygon'> }
interface AreaMapProps {
Expand Down Expand Up @@ -42,106 +39,27 @@ export interface HoverInfo {
* Area map
*/
const AreaMap: React.FC<AreaMapProps> = ({ area, subAreas }) => {
const [clickInfo, setClickInfo] = useState<MapAreaFeatureProperties | null>(null)
const [hoverInfo, setHoverInfo] = useState<HoverInfo | null>(null)
const [selected, setSelected] = useState<Point | Polygon | null>(null)
const [mapInstance, setMapInstance] = useState<MapInstance | null>(null)
const [cursor, setCursor] = useState<string>('default')
const mapRef = useRef<any>(null)

let fitBoundOpts: any = { padding: { top: 45, left: 45, bottom: 45, right: 45 } }
if (subAreas.length === 0) {
fitBoundOpts = { maxZoom: 14 }
}

const { metadata } = area
const boundary = metadata?.polygon == null ? null : lineToPolygon(lineString(metadata.polygon), { properties: { name: area.areaName } })

useEffect(() => {
if (mapRef.current != null) {
setMapInstance(mapRef.current)
}
/**
* Show drop pin if viewing a leaf area
*/
if (metadata.leaf) {
setSelected(point([metadata.lng, metadata.lat]).geometry as unknown as Point)
}
}, [metadata.leaf, mapRef?.current])

const onClick = useCallback((event: MapLayerMouseEvent): void => {
const feature = event?.features?.[0]
if (feature == null) {
setSelected(null)
setClickInfo(null)
} else {
setSelected(feature.geometry as Point | Polygon)
setClickInfo(feature.properties as MapAreaFeatureProperties)
}
}, [mapInstance])

const onHover = useCallback((event: MapLayerMouseEvent) => {
const obLayerId = event.features?.findIndex((f) => f.layer.id === 'crags' || f.layer.id === 'crag-group-boundaries') ?? -1

if (obLayerId !== -1) {
setCursor('pointer')
const feature = event.features?.[obLayerId]
if (feature != null && mapInstance != null) {
const { geometry } = feature
if (geometry.type === 'Point' || geometry.type === 'Polygon') {
setHoverInfo({
geometry: feature.geometry as Point | Polygon,
data: feature.properties as MapAreaFeatureProperties,
mapInstance
})
}
}
} else {
setHoverInfo(null)
setCursor('default')
}
}, [mapInstance])

return (
<div className='relative w-full h-full'>
<Map
ref={mapRef}
id='map'
<GlobalMap
showFullscreenControl
initialViewState={{
bounds: metadata.bbox,
fitBoundsOptions: fitBoundOpts
}}
onDragStart={() => {
setCursor('move')
}}
onDragEnd={() => {
setCursor('default')
}}
onMouseEnter={onHover}
onMouseLeave={() => {
setHoverInfo(null)
setCursor('default')
}}
onClick={onClick}
reuseMaps
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_API_KEY}
mapStyle={MAP_STYLES.light}
cursor={cursor}
cooperativeGestures
interactiveLayerIds={['crags', 'crag-group-boundaries']}
>
<ScaleControl />
<FullscreenControl />
<NavigationControl showCompass={false} />
{selected != null &&
<SelectedFeature geometry={selected} />}
<AreaInfoDrawer data={clickInfo} />
{boundary != null &&
<Source id='child-areas-polygon' type='geojson' data={boundary}>
<Layer {...areaPolygonStyle} />
</Source>}
{hoverInfo != null && <AreaInfoHover {...hoverInfo} />}
</Map>
</GlobalMap>
</div>
)
}
Expand All @@ -158,7 +76,7 @@ const areaPolygonStyle: LineLayer = {
type: 'line',
paint: {
'line-opacity': ['step', ['zoom'], 0.85, 10, 0.5],
'line-width': ['step', ['zoom'], 2, 10, 8],
'line-width': ['step', ['zoom'], 4, 8, 6],
'line-color': 'rgb(219,39,119)',
'line-blur': 4
}
Expand Down
Loading

0 comments on commit 4b804da

Please sign in to comment.