-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
69d878a
commit 3032b69
Showing
49 changed files
with
3,392 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
modules/react-maplibre/src/components/attribution-control.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as React from 'react'; | ||
import {useEffect, memo} from 'react'; | ||
import {applyReactStyle} from '../utils/apply-react-style'; | ||
import {useControl} from './use-control'; | ||
|
||
import type {ControlPosition, AttributionControlOptions} from '../types/lib'; | ||
|
||
export type AttributionControlProps = AttributionControlOptions & { | ||
/** Placement of the control relative to the map. */ | ||
position?: ControlPosition; | ||
/** CSS style override, applied to the control's container */ | ||
style?: React.CSSProperties; | ||
}; | ||
|
||
function _AttributionControl(props: AttributionControlProps) { | ||
const ctrl = useControl(({mapLib}) => new mapLib.AttributionControl(props), { | ||
position: props.position | ||
}); | ||
|
||
useEffect(() => { | ||
applyReactStyle(ctrl._container, props.style); | ||
}, [props.style]); | ||
|
||
return null; | ||
} | ||
|
||
export const AttributionControl = memo(_AttributionControl); |
35 changes: 35 additions & 0 deletions
35
modules/react-maplibre/src/components/fullscreen-control.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* global document */ | ||
import * as React from 'react'; | ||
import {useEffect, memo} from 'react'; | ||
import {applyReactStyle} from '../utils/apply-react-style'; | ||
import {useControl} from './use-control'; | ||
|
||
import type {ControlPosition, FullscreenControlOptions} from '../types/lib'; | ||
|
||
export type FullscreenControlProps = Omit<FullscreenControlOptions, 'container'> & { | ||
/** Id of the DOM element which should be made full screen. By default, the map container | ||
* element will be made full screen. */ | ||
containerId?: string; | ||
/** Placement of the control relative to the map. */ | ||
position?: ControlPosition; | ||
/** CSS style override, applied to the control's container */ | ||
style?: React.CSSProperties; | ||
}; | ||
|
||
function _FullscreenControl(props: FullscreenControlProps) { | ||
const ctrl = useControl( | ||
({mapLib}) => | ||
new mapLib.FullscreenControl({ | ||
container: props.containerId && document.getElementById(props.containerId) | ||
}), | ||
{position: props.position} | ||
); | ||
|
||
useEffect(() => { | ||
applyReactStyle(ctrl._controlContainer, props.style); | ||
}, [props.style]); | ||
|
||
return null; | ||
} | ||
|
||
export const FullscreenControl = memo(_FullscreenControl); |
81 changes: 81 additions & 0 deletions
81
modules/react-maplibre/src/components/geolocate-control.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import * as React from 'react'; | ||
import {useImperativeHandle, useRef, useEffect, forwardRef, memo} from 'react'; | ||
import {applyReactStyle} from '../utils/apply-react-style'; | ||
import {useControl} from './use-control'; | ||
|
||
import type { | ||
ControlPosition, | ||
GeolocateControlInstance, | ||
GeolocateControlOptions | ||
} from '../types/lib'; | ||
import type {GeolocateEvent, GeolocateResultEvent, GeolocateErrorEvent} from '../types/events'; | ||
|
||
export type GeolocateControlProps = GeolocateControlOptions & { | ||
/** Placement of the control relative to the map. */ | ||
position?: ControlPosition; | ||
/** CSS style override, applied to the control's container */ | ||
style?: React.CSSProperties; | ||
|
||
/** Called on each Geolocation API position update that returned as success. */ | ||
onGeolocate?: (e: GeolocateResultEvent) => void; | ||
/** Called on each Geolocation API position update that returned as an error. */ | ||
onError?: (e: GeolocateErrorEvent) => void; | ||
/** Called on each Geolocation API position update that returned as success but user position | ||
* is out of map `maxBounds`. */ | ||
onOutOfMaxBounds?: (e: GeolocateResultEvent) => void; | ||
/** Called when the GeolocateControl changes to the active lock state. */ | ||
onTrackUserLocationStart?: (e: GeolocateEvent) => void; | ||
/** Called when the GeolocateControl changes to the background state. */ | ||
onTrackUserLocationEnd?: (e: GeolocateEvent) => void; | ||
}; | ||
|
||
function _GeolocateControl(props: GeolocateControlProps, ref: React.Ref<GeolocateControlInstance>) { | ||
const thisRef = useRef({props}); | ||
|
||
const ctrl = useControl( | ||
({mapLib}) => { | ||
const gc = new mapLib.GeolocateControl(props); | ||
|
||
// Hack: fix GeolocateControl reuse | ||
// When using React strict mode, the component is mounted twice. | ||
// GeolocateControl's UI creation is asynchronous. Removing and adding it back causes the UI to be initialized twice. | ||
const setupUI = gc._setupUI; | ||
gc._setupUI = () => { | ||
if (!gc._container.hasChildNodes()) { | ||
setupUI(); | ||
} | ||
}; | ||
|
||
gc.on('geolocate', e => { | ||
thisRef.current.props.onGeolocate?.(e as GeolocateResultEvent); | ||
}); | ||
gc.on('error', e => { | ||
thisRef.current.props.onError?.(e as GeolocateErrorEvent); | ||
}); | ||
gc.on('outofmaxbounds', e => { | ||
thisRef.current.props.onOutOfMaxBounds?.(e as GeolocateResultEvent); | ||
}); | ||
gc.on('trackuserlocationstart', e => { | ||
thisRef.current.props.onTrackUserLocationStart?.(e as GeolocateEvent); | ||
}); | ||
gc.on('trackuserlocationend', e => { | ||
thisRef.current.props.onTrackUserLocationEnd?.(e as GeolocateEvent); | ||
}); | ||
|
||
return gc; | ||
}, | ||
{position: props.position} | ||
); | ||
|
||
thisRef.current.props = props; | ||
|
||
useImperativeHandle(ref, () => ctrl, []); | ||
|
||
useEffect(() => { | ||
applyReactStyle(ctrl._container, props.style); | ||
}, [props.style]); | ||
|
||
return null; | ||
} | ||
|
||
export const GeolocateControl = memo(forwardRef(_GeolocateControl)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import {useContext, useEffect, useMemo, useState, useRef} from 'react'; | ||
import {MapContext} from './map'; | ||
import assert from '../utils/assert'; | ||
import {deepEqual} from '../utils/deep-equal'; | ||
|
||
import type {MapInstance, CustomLayerInterface} from '../types/lib'; | ||
import type {AnyLayer} from '../types/style-spec'; | ||
|
||
// Omiting property from a union type, see | ||
// https://github.com/microsoft/TypeScript/issues/39556#issuecomment-656925230 | ||
type OptionalId<T> = T extends {id: string} ? Omit<T, 'id'> & {id?: string} : T; | ||
type OptionalSource<T> = T extends {source: string} ? Omit<T, 'source'> & {source?: string} : T; | ||
|
||
export type LayerProps = (OptionalSource<OptionalId<AnyLayer>> | CustomLayerInterface) & { | ||
/** If set, the layer will be inserted before the specified layer */ | ||
beforeId?: string; | ||
}; | ||
|
||
/* eslint-disable complexity, max-statements */ | ||
function updateLayer(map: MapInstance, id: string, props: LayerProps, prevProps: LayerProps) { | ||
assert(props.id === prevProps.id, 'layer id changed'); | ||
assert(props.type === prevProps.type, 'layer type changed'); | ||
|
||
if (props.type === 'custom' || prevProps.type === 'custom') { | ||
return; | ||
} | ||
|
||
// @ts-ignore filter does not exist in some Layer types | ||
const {layout = {}, paint = {}, filter, minzoom, maxzoom, beforeId} = props; | ||
|
||
if (beforeId !== prevProps.beforeId) { | ||
map.moveLayer(id, beforeId); | ||
} | ||
if (layout !== prevProps.layout) { | ||
const prevLayout = prevProps.layout || {}; | ||
for (const key in layout) { | ||
if (!deepEqual(layout[key], prevLayout[key])) { | ||
map.setLayoutProperty(id, key, layout[key]); | ||
} | ||
} | ||
for (const key in prevLayout) { | ||
if (!layout.hasOwnProperty(key)) { | ||
map.setLayoutProperty(id, key, undefined); | ||
} | ||
} | ||
} | ||
if (paint !== prevProps.paint) { | ||
const prevPaint = prevProps.paint || {}; | ||
for (const key in paint) { | ||
if (!deepEqual(paint[key], prevPaint[key])) { | ||
map.setPaintProperty(id, key, paint[key]); | ||
} | ||
} | ||
for (const key in prevPaint) { | ||
if (!paint.hasOwnProperty(key)) { | ||
map.setPaintProperty(id, key, undefined); | ||
} | ||
} | ||
} | ||
|
||
// @ts-ignore filter does not exist in some Layer types | ||
if (!deepEqual(filter, prevProps.filter)) { | ||
map.setFilter(id, filter); | ||
} | ||
if (minzoom !== prevProps.minzoom || maxzoom !== prevProps.maxzoom) { | ||
map.setLayerZoomRange(id, minzoom, maxzoom); | ||
} | ||
} | ||
|
||
function createLayer(map: MapInstance, id: string, props: LayerProps) { | ||
// @ts-ignore | ||
if (map.style && map.style._loaded && (!('source' in props) || map.getSource(props.source))) { | ||
const options: LayerProps = {...props, id}; | ||
delete options.beforeId; | ||
|
||
// @ts-ignore | ||
map.addLayer(options, props.beforeId); | ||
} | ||
} | ||
|
||
/* eslint-enable complexity, max-statements */ | ||
|
||
let layerCounter = 0; | ||
|
||
export function Layer(props: LayerProps) { | ||
const map = useContext(MapContext).map.getMap(); | ||
const propsRef = useRef(props); | ||
const [, setStyleLoaded] = useState(0); | ||
|
||
const id = useMemo(() => props.id || `jsx-layer-${layerCounter++}`, []); | ||
|
||
useEffect(() => { | ||
if (map) { | ||
const forceUpdate = () => setStyleLoaded(version => version + 1); | ||
map.on('styledata', forceUpdate); | ||
forceUpdate(); | ||
|
||
return () => { | ||
map.off('styledata', forceUpdate); | ||
// @ts-ignore | ||
if (map.style && map.style._loaded && map.getLayer(id)) { | ||
map.removeLayer(id); | ||
} | ||
}; | ||
} | ||
return undefined; | ||
}, [map]); | ||
|
||
// @ts-ignore | ||
const layer = map && map.style && map.getLayer(id); | ||
if (layer) { | ||
try { | ||
updateLayer(map, id, props, propsRef.current); | ||
} catch (error) { | ||
console.warn(error); // eslint-disable-line | ||
} | ||
} else { | ||
createLayer(map, id, props); | ||
} | ||
|
||
// Store last rendered props | ||
propsRef.current = props; | ||
|
||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from 'react'; | ||
import {useEffect, memo} from 'react'; | ||
import {applyReactStyle} from '../utils/apply-react-style'; | ||
import {useControl} from './use-control'; | ||
|
||
import type {ControlPosition, LogoControlOptions} from '../types/lib'; | ||
|
||
export type LogoControlProps = LogoControlOptions & { | ||
/** Placement of the control relative to the map. */ | ||
position?: ControlPosition; | ||
/** CSS style override, applied to the control's container */ | ||
style?: React.CSSProperties; | ||
}; | ||
|
||
function _LogoControl(props: LogoControlProps) { | ||
const ctrl = useControl(({mapLib}) => new mapLib.LogoControl(props), {position: props.position}); | ||
|
||
useEffect(() => { | ||
applyReactStyle(ctrl._container, props.style); | ||
}, [props.style]); | ||
|
||
return null; | ||
} | ||
|
||
export const LogoControl = memo(_LogoControl); |
Oops, something went wrong.