UNPKG

@globalfishingwatch/react-map-gl

Version:

A React wrapper for MapboxGL-js and overlay API.

161 lines (141 loc) 4.71 kB
// @flow // Copyright (c) 2015 Uber Technologies, Inc. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import * as React from 'react'; import {useContext, useEffect, useMemo, useState, useRef} from 'react'; import {cloneElement} from 'react'; import PropTypes from 'prop-types'; import MapContext from './map-context'; import assert from '../utils/assert'; import deepEqual from '../utils/deep-equal'; const propTypes = { type: PropTypes.string.isRequired, id: PropTypes.string }; type SourceProps = { id?: string, type: string, children?: any, data?: any, coordinates?: any, url?: any, tiles?: any }; let sourceCounter = 0; function createSource(map, id, props) { if (map.style && map.style._loaded) { const options = {...props}; delete options.id; delete options.children; map.addSource(id, options); return map.getSource(id); } return null; } /* eslint-disable complexity */ function updateSource(source, props, prevProps) { assert(props.id === prevProps.id, 'source id changed'); assert(props.type === prevProps.type, 'source type changed'); let changedKey = ''; let changedKeyCount = 0; for (const key in props) { if (key !== 'children' && key !== 'id' && !deepEqual(prevProps[key], props[key])) { changedKey = key; changedKeyCount++; } } if (!changedKeyCount) { return; } const {type} = props; if (type === 'geojson') { source.setData(props.data); } else if (type === 'image') { source.updateImage({url: props.url, coordinates: props.coordinates}); } else if ( (type === 'canvas' || type === 'video') && changedKeyCount === 1 && changedKey === 'coordinates' ) { source.setCoordinates(props.coordinates); } else if (type === 'vector' && source.setUrl) { // Added in 1.12.0: // vectorTileSource.setTiles // vectorTileSource.setUrl switch (changedKey) { case 'url': source.setUrl(props.url); break; case 'tiles': source.setTiles(props.tiles); break; default: } } else { // eslint-disable-next-line console.warn(`Unable to update <Source> prop: ${changedKey}`); } } /* eslint-enable complexity */ function Source(props: SourceProps) { const context = useContext(MapContext); const propsRef = useRef<SourceProps>({id: props.id, type: props.type}); const [, setStyleLoaded] = useState(0); const id = useMemo(() => props.id || `jsx-source-${sourceCounter++}`, []); const {map} = context; useEffect( () => { if (map) { const forceUpdate = () => setStyleLoaded(version => version + 1); map.on('styledata', forceUpdate); return () => { map.off('styledata', forceUpdate); /* global requestAnimationFrame */ // Do not remove source immediately because the // dependent <Layer>s' componentWillUnmount() might not have been called // Removing source before dependent layers will throw error // TODO - find a more robust solution requestAnimationFrame(() => { if (map.style && map.style._loaded) { map.removeSource(id); } }); }; } return undefined; }, [map] ); let source = map && map.style && map.getSource(id); if (source) { updateSource(source, props, propsRef.current); } else { source = createSource(map, id, props); } return source ? React.Children.map( props.children, child => child && cloneElement(child, { source: id }) ) : null; } Source.propTypes = propTypes; export default Source;