├── .babelrc ├── .gitignore ├── README.md ├── assets ├── earthquakes.geojson └── hover-map.png ├── docs ├── favicon.ico ├── iframe.html ├── index.html └── static │ ├── manager.cfd979bccf3d64bb4985.bundle.js │ ├── media │ ├── giants.22eda568.png │ ├── green-pattern.06f1ad4c.jpg │ ├── nats.67ad2cb4.png │ └── orange-pattern.f8619e80.jpg │ └── preview.13cb8d710950511a32a5.bundle.js ├── jest.config.js ├── lerna.json ├── modules ├── README.md ├── button-layer │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ButtonLayer.js │ │ ├── index.js │ │ └── stories │ │ │ ├── Example.story.src.js │ │ │ └── PointCluster.story.src.js │ └── yarn.lock ├── click │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Click.js │ │ ├── Click.story.src.js │ │ └── index.js │ └── yarn.lock ├── core │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Children.js │ │ ├── Control.js │ │ ├── Image.js │ │ ├── Layer.js │ │ ├── LayerEvent.js │ │ ├── LayerEvents.js │ │ ├── LoadImages.js │ │ ├── MapEvent.js │ │ ├── MapEvents.js │ │ ├── MapGL.js │ │ ├── MapInteraction.js │ │ ├── MapOptions.js │ │ ├── MapPosition.js │ │ ├── MapboxProvider.js │ │ ├── Marker.js │ │ ├── Popup.js │ │ ├── Source.js │ │ ├── index.js │ │ ├── stories │ │ │ ├── about.story.docs.js │ │ │ ├── assets │ │ │ │ ├── giants.png │ │ │ │ ├── green-pattern.jpg │ │ │ │ ├── nats.png │ │ │ │ └── orange-pattern.jpg │ │ │ ├── basic.story.src.js │ │ │ ├── controls.story.src.js │ │ │ ├── events.story.src.js │ │ │ ├── images.story.src.js │ │ │ ├── interaction-handlers.story.src.js │ │ │ ├── marker.story.src.js │ │ │ ├── popup.story.css │ │ │ └── popup.story.src.js │ │ └── util │ │ │ ├── __snapshots__ │ │ │ └── diff.test.js.snap │ │ │ ├── diff.js │ │ │ ├── diff.test.js │ │ │ ├── loadCSS.js │ │ │ └── loadScript.js │ └── yarn.lock ├── docs │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── Checkbox.js │ │ │ ├── Options.css │ │ │ ├── Options.js │ │ │ ├── Overlay.css │ │ │ └── Overlay.js │ │ ├── index.js │ │ └── util │ │ │ ├── mapDefaults.js │ │ │ └── sanitizeMapEvent.js │ └── yarn.lock ├── hover │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Hover.js │ │ ├── Hover.story.src.js │ │ └── index.js │ └── yarn.lock └── toggle │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── Toggle.js │ ├── Toggle.story.src.js │ └── index.js │ └── yarn.lock ├── package.json ├── tools ├── .storybook │ ├── addons.js │ ├── components │ │ ├── WithDocs.css │ │ ├── WithDocs.js │ │ ├── WithSource.css │ │ └── WithSource.js │ ├── config.js │ └── util │ │ ├── loadDocsStories.js │ │ ├── loadSourceStories.js │ │ ├── loadStories.js │ │ ├── parseBlocks.js │ │ └── parseComment.js ├── __mocks__ │ ├── fileMock.js │ └── styleMock.js ├── build-module ├── clean-module ├── exec.js ├── module-do ├── test-module └── view-coverage └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["babel-plugin-lodash"], 4 | ["babel-plugin-transform-react-jsx"] 5 | ], 6 | "env": { 7 | "storybook": { 8 | "presets": [ 9 | ["babel-preset-env", { 10 | "loose": true, 11 | "modules": "commonjs", 12 | "targets": { 13 | "browsers": ["last 2 versions", ">5%"] 14 | } 15 | }] 16 | ], 17 | "plugins": [ 18 | ["babel-plugin-transform-decorators-legacy"], 19 | ["babel-plugin-transform-class-properties"], 20 | ["babel-plugin-module-resolver", { 21 | "alias": { 22 | "^@react-mapboxgl/([^/]+)(/.+)?": "../\\1/src\\2" 23 | }, 24 | "cwd": "packagejson" 25 | }] 26 | ] 27 | }, 28 | "commonjs": { 29 | "presets": [ 30 | ["babel-preset-env", { 31 | "loose": true, 32 | "modules": "commonjs", 33 | "targets": { 34 | "browsers": ["last 2 versions", ">5%"] 35 | } 36 | }] 37 | ], 38 | "plugins": [ 39 | ["babel-plugin-transform-decorators-legacy"], 40 | ["babel-plugin-transform-class-properties"] 41 | ] 42 | }, 43 | "es": { 44 | "presets": [ 45 | ["babel-preset-env", { 46 | "loose": true, 47 | "modules": false, 48 | "targets": { 49 | "browsers": ["last 2 versions", ">5%"] 50 | } 51 | }] 52 | ], 53 | "plugins": [ 54 | ["babel-plugin-transform-decorators-legacy"], 55 | ["babel-plugin-transform-class-properties"] 56 | ] 57 | }, 58 | "node": { 59 | "presets": [ 60 | ["babel-preset-env", { 61 | "loose": true, 62 | "modules": "commonjs", 63 | "targets": { 64 | "node": 6 65 | } 66 | }] 67 | ], 68 | "plugins": [ 69 | ["babel-plugin-transform-decorators-legacy"], 70 | ["babel-plugin-transform-class-properties"] 71 | ] 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | lerna-debug.log 4 | npm-debug.log 5 | .yo-rc.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl 2 | =============== 3 | 4 | About 5 | ----- 6 | 7 | This aims to be as close of a 1-to-1 mapping of the `mapbox-gl-js` API to react as possible. It takes the largely imperative `mapbox-gl-js` codebase and wraps it in declarative react components. A few goals: 8 | 9 | - Declarative way to create mapbox-gl maps. 10 | - Support as close to 100% of the `mapbox-gl-js` API as makes sense. 11 | - Leverage the `mapbox-gl-js` way of doing things wherever possible. 12 | - "Everything is a component" 13 | - You should be able to do thinks like render layers, bind click handlers, 14 | respond to hovering over layers, ect. by merely rendering components. 15 | - Many maps will only need a render method, no state to juggle. 16 | - The core attempts to split things out to composable components wherever possible. 17 | 18 | Inspired and bootstrapped from [react-mapbox-gl](https://github.com/alex3165/react-mapbox-gl). 19 | 20 | Installation 21 | ------------ 22 | 23 | Currently, this module needs to be bundled by *your* app. If you are already 24 | using something like webpack or rollup, you should be good to go. It requires 25 | installing some peer dependencies. I chose this to avoid increasing the 26 | bundle size if you're already using some of these libraries. 27 | 28 | ```sh 29 | $ npm install --save @react-mapboxgl/core mapbox-gl lodash react prop-types 30 | ``` 31 | 32 | Documentation 33 | ------------- 34 | 35 | Documentation and examples can be found in the [storybook](https://terraeclipse.github.io/react-mapboxgl). 36 | 37 | Sandbox 38 | ------- 39 | 40 | You can easily play around with maps on codesandbox.io: 41 | 42 | [![Edit @react-mapboxgl Playground](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/E9p5AG5X0?hidenavigation=1) 43 | 44 | 45 | Quick Example 46 | ------------- 47 | 48 | This renders a map, adds a source and layer to the map, and changes the fill 49 | of polygon features when they are hovered. See it in action [in the storybook](https://terraeclipse.github.io/react-mapboxgl/?selectedKind=Hover&selectedStory=Example&full=0&down=0). 50 | 51 | ![Hover map example](https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/master/assets/hover-map.png) 52 | 53 | ```js 54 | import React from 'react' 55 | import {MapboxProvider, MapGL, Source, Layer} from '@react-mapboxgl/core' 56 | import Hover from '@react-mapboxgl/hover' 57 | 58 | const mapOptions = { 59 | style: 'mapbox://styles/mapbox/streets-v9', 60 | bbox: [[-123.881836, 25.063209], [-65.170898, 48.848451]], 61 | center: [-95.844727, 39.620499], 62 | zoom: 3, 63 | padding: 30, 64 | containerStyle: { 65 | position: 'fixed', 66 | top: 0, 67 | left: 0, 68 | right: 0, 69 | bottom: 0, 70 | zIndex: 1 71 | } 72 | } 73 | 74 | const ExampleMap = () => { 75 | return ( 76 | 77 | 78 | {/* Source to be used by layers (U.S. state polygons) */} 79 | 84 | 85 | {/* State fill layer */} 86 | 95 | 96 | {/* State borders layer */} 97 | 106 | 107 | {/* Declarative handler for hovering a layer's features. 108 | 109 | This component optionally allows a function as the 110 | *children*, similar to how libraries like react-motion do. You can 111 | leverage that to filter layers or otherwise modify them. 112 | 113 | The *property* should be a member of `feature.properties` that 114 | uniquely identifies each feature. 115 | */} 116 | 117 | {({properties: names}) => ( 118 | 132 | )} 133 | 134 | 135 | 136 | ) 137 | } 138 | 139 | export default ExampleMap 140 | ``` 141 | 142 | - - - 143 | 144 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 145 | 146 | Terra Eclipse, Inc. is a nationally recognized political technology and 147 | strategy firm located in Santa Cruz, CA and Washington, D.C. 148 | -------------------------------------------------------------------------------- /assets/hover-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/assets/hover-map.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/docs/favicon.ico -------------------------------------------------------------------------------- /docs/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | Storybook 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Storybook 10 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/static/media/giants.22eda568.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/docs/static/media/giants.22eda568.png -------------------------------------------------------------------------------- /docs/static/media/green-pattern.06f1ad4c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/docs/static/media/green-pattern.06f1ad4c.jpg -------------------------------------------------------------------------------- /docs/static/media/nats.67ad2cb4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/docs/static/media/nats.67ad2cb4.png -------------------------------------------------------------------------------- /docs/static/media/orange-pattern.f8619e80.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/docs/static/media/orange-pattern.f8619e80.jpg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // roots in ./tools/test-module 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: [ 5 | '__fixtures__' 6 | ], 7 | // collectCoverageFrom in ./tools/test-module 8 | // coverageDirectory in ./tools/test-module 9 | coveragePathIgnorePatterns: [ 10 | '__fixtures__', 11 | '\\.story\\.js$' 12 | ], 13 | coverageThreshold: { 14 | 'global': { 15 | 'branches': 60, 16 | 'functions': 85, 17 | 'lines': 85, 18 | 'statements': 85 19 | } 20 | }, 21 | moduleNameMapper: { 22 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/tools/__mocks__/fileMock.js', 23 | '\\.(css|less)$': '/tools/__mocks__/styleMock.js' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.4", 3 | "packages": [ 4 | "modules/*" 5 | ], 6 | "version": "independent", 7 | "npmClient": "yarn", 8 | "hoist": "**" 9 | } 10 | -------------------------------------------------------------------------------- /modules/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl Modules 2 | ----------------------- 3 | 4 | The @react-mapboxgl project is split into several npm modules: 5 | 6 | - `core` provides the components and utilities required to 7 | render maps. Almost anything that that directly calls `mapbox-gl-js` 8 | methods will be found here. 9 | - Modules like `click`, `hover`, `toggle`, etc. provide higher-level 10 | abstractions around common map functionality or behaviors. These will 11 | typically rely on lower-level components from core such as `MapEvent`, but 12 | wrap them in a more declarative API or provide user-facing enhancements. 13 | - Modules like `button-layer` wrap many core components to provide convenient 14 | ways to accomplish more complex map/layer UI or behavior. 15 | - `docs` provides helper components and utilities strictly used in the storybook 16 | entries. 17 | 18 | Each of these modules are installed as dependencies in your projects like: 19 | 20 | ``` 21 | $ npm install --save @react-mapboxgl/core @react-mapboxgl/click 22 | ``` 23 | -------------------------------------------------------------------------------- /modules/button-layer/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/button-layer/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/button-layer 2 | ============================ 3 | 4 | The `ButtonLayer` component encapsulates several styled layers pointing to the 5 | same source. These layers provide the typical UI required for 'interactive' 6 | features. 7 | 8 | Props 9 | ----- 10 | 11 | - **id (required):** The id for this layer. 12 | - **property (required):** The unique property to identify features. 13 | - **source:** The source (id or options). 14 | - **sourceLayer:** The source-layer (if relevant). 15 | - **base:** Options for the 'base' layer, the one that is always visible. 16 | - **borders:** Options for a borders layer (if relevant). 17 | - **hover:** Options for a hover layer (if relevant). 18 | - **hoverBorder:** Options for a hover-border layer (if relevant). 19 | - **active:** Options for an active layer (if relevant). 20 | - **activeBorder:** Options for an active border layer (if relevant). 21 | - **activeProperty:** The property value identifying the currently active feature (if one is active). 22 | 23 | Example 24 | ------- 25 | 26 | An example usage. See it in action [in the storybook](https://terraeclipse.github.io/react-mapboxgl/?selectedKind=ButtonLayer&selectedStory=Example). 27 | 28 | ```js 29 | /** 30 | * ButtonLayer - Example 31 | * 32 | * ## ButtonLayer Example - Interactive Polygons 33 | * 34 | * Uses the ButtonLayer 'meta component' to add polygons 35 | * with border, hover, and active states. 36 | * 37 | * Toggle any polygon to 'zoom' into/out of it. 38 | */ 39 | import React from 'react' 40 | import bbox from '@turf/bbox' 41 | import {MapboxProvider, MapGL} from '@react-mapboxgl/core' 42 | import Toggle from '@react-mapboxgl/toggle' 43 | import ButtonLayer from './' 44 | 45 | const mapOptions = { 46 | style: 'mapbox://styles/mapbox/streets-v9', 47 | bbox: [[-123.881836, 25.063209], [-65.170898, 48.848451]], 48 | center: [-95.844727, 39.620499], 49 | zoom: 3, 50 | padding: 30, 51 | containerStyle: { 52 | position: 'fixed', 53 | top: 0, 54 | left: 0, 55 | right: 0, 56 | bottom: 0, 57 | zIndex: 1 58 | } 59 | } 60 | 61 | class Example extends React.Component { 62 | state = { 63 | activeName: null, 64 | bbox: mapOptions.bbox 65 | } 66 | 67 | constructor () { 68 | super() 69 | this.handleToggle = this.handleToggle.bind(this) 70 | } 71 | 72 | handleToggle (feature, isOn) { 73 | if (isOn) { 74 | this.setState({ 75 | activeName: feature.properties.name, 76 | bbox: bbox(feature) 77 | }) 78 | } else { 79 | this.setState({ 80 | activeName: null, 81 | bbox: mapOptions.bbox 82 | }) 83 | } 84 | } 85 | 86 | render () { 87 | return ( 88 | 89 | 90 | 142 | 148 | 149 | 150 | ) 151 | } 152 | } 153 | 154 | export default Example 155 | ``` 156 | 157 | - - - 158 | 159 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 160 | 161 | Terra Eclipse, Inc. is a nationally recognized political technology and 162 | strategy firm located in Santa Cruz, CA and Washington, D.C. 163 | -------------------------------------------------------------------------------- /modules/button-layer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/button-layer", 3 | "version": "2.3.5", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "ButtonLayer component for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "dependencies": { 25 | "@react-mapboxgl/click": "^2.1.4", 26 | "@react-mapboxgl/core": "^2.1.3", 27 | "@react-mapboxgl/docs": "^2.1.0", 28 | "@react-mapboxgl/hover": "^2.1.6", 29 | "@react-mapboxgl/toggle": "^2.1.4", 30 | "@turf/union": "^4.4.0" 31 | }, 32 | "peerDependencies": { 33 | "lodash": "^4.17.4", 34 | "prop-types": "^15.5.10", 35 | "react": "^15.5.4" 36 | }, 37 | "devDependencies": { 38 | "@turf/bbox": "^4.3.0", 39 | "lodash": "^4.17.4", 40 | "prop-types": "^15.5.10", 41 | "react": "^15.5.4" 42 | }, 43 | "author": "Brian Link", 44 | "license": "Apache-2.0", 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 51 | }, 52 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/button-layer#readme", 53 | "keywords": [ 54 | "react-component" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /modules/button-layer/src/ButtonLayer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import union from '@turf/union' 5 | import {Children, Layer, LayerEvents, Source} from '@react-mapboxgl/core' 6 | import Click from '@react-mapboxgl/click' 7 | import Hover from '@react-mapboxgl/hover' 8 | 9 | class ButtonLayer extends React.Component { 10 | static propTypes = { 11 | id: PropTypes.string.isRequired, 12 | property: PropTypes.string, 13 | 14 | // Source 15 | source: PropTypes.oneOfType([ 16 | PropTypes.string, 17 | PropTypes.object 18 | ]).isRequired, 19 | sourceLayer: PropTypes.string, 20 | 21 | // Layers 22 | base: PropTypes.object.isRequired, 23 | border: PropTypes.object, 24 | hover: PropTypes.object, 25 | hoverBorder: PropTypes.object, 26 | cluster: PropTypes.object, 27 | clusterBorder: PropTypes.object, 28 | clusterLabel: PropTypes.object, 29 | active: PropTypes.object, 30 | activeBorder: PropTypes.object, 31 | activeProperty: PropTypes.oneOfType([ 32 | PropTypes.string, 33 | PropTypes.number, 34 | PropTypes.bool 35 | ]), 36 | 37 | // Hover 38 | hoverMode: PropTypes.string, // Either 'auto' or 'filter'. 39 | cursor: PropTypes.string, 40 | onHoverOver: PropTypes.func, 41 | onHoverOut: PropTypes.func, 42 | 43 | // Click 44 | clickEvent: PropTypes.string, 45 | avoidDoubleClick: PropTypes.bool, 46 | doubleClickSpeed: PropTypes.number, 47 | onClick: PropTypes.func 48 | 49 | // LayerEvents (bound to base layer) 50 | } 51 | 52 | static defaultProps = { 53 | hoverMode: 'auto' 54 | } 55 | 56 | static contextTypes = { 57 | map: PropTypes.object 58 | } 59 | 60 | constructor () { 61 | super() 62 | this.renderHoverChildren = this.renderHoverChildren.bind(this) 63 | } 64 | 65 | unionFeatures (features) { 66 | try { 67 | let result = [] 68 | result.push(features.reduce((joined, next) => { 69 | if (joined.geometry.type === 'Polygon' || joined.geometry.type === 'MultiPolygon') { 70 | if (next.geometry.type === 'Polygon' || next.geometry.type === 'MultiPolygon') { 71 | return union(joined, next) 72 | } else { 73 | result.push(next) 74 | return joined 75 | } 76 | } else { 77 | result.push(joined) 78 | return next 79 | } 80 | })) 81 | return result 82 | } catch (e) { 83 | console.warn('Error detected when trying to union hovered features.') 84 | console.warn(e) 85 | return features 86 | } 87 | } 88 | 89 | renderHoverChildren ({properties}) { 90 | let {map} = this.context 91 | let { 92 | id, source, sourceLayer, property, base, 93 | hoverMode, hover, hoverBorder 94 | } = this.props 95 | 96 | let sourceId = (typeof source === 'string') 97 | ? source 98 | : (source.id || `${id}-source`) 99 | let sourceDef = map.getSource(sourceId) 100 | let sourceType = sourceDef && sourceDef.type 101 | 102 | // If we don't have a sourceType yet, render nothing. 103 | if (!sourceType) { 104 | return null 105 | } 106 | 107 | // For GeoJSON we change the data on a hover . 108 | if (hoverMode !== 'filter' && sourceType === 'geojson') { 109 | let features = [] 110 | if (properties.length) { 111 | features = this.unionFeatures(map.querySourceFeatures(sourceId, { 112 | filter: ['in', property, ...properties] 113 | })) 114 | } 115 | return ( 116 | 117 | {(hover || hoverBorder) ? ( 118 | 123 | ) : null} 124 | 125 | {hover ? ( 126 | 130 | ) : null} 131 | 132 | {hoverBorder ? ( 133 | 137 | ) : null} 138 | 139 | ) 140 | // For all other types, we use a filter on the layer. 141 | } else { 142 | return ( 143 | 144 | {hover ? ( 145 | 156 | ) : null} 157 | 158 | {hoverBorder ? ( 159 | 170 | ) : null} 171 | 172 | ) 173 | } 174 | } 175 | 176 | render () { 177 | let { 178 | id, source, sourceLayer, property, 179 | base, border, hover, hoverBorder, 180 | cluster, clusterBorder, clusterLabel, 181 | active, activeBorder, activeProperty, 182 | cursor, onHoverOver, onHoverOut, 183 | clickEvent, avoidDoubleClick, doubleClickSpeed, onClick 184 | } = this.props 185 | 186 | let sourceId = (typeof source === 'string') 187 | ? source 188 | : (source.id || `${id}-source`) 189 | 190 | return ( 191 | 192 | {typeof source !== 'string' ? ( 193 | 194 | ) : null} 195 | 196 | 207 | 208 | {onClick ? ( 209 | 213 | ) : null} 214 | 215 | {border ? ( 216 | 224 | ) : null} 225 | 226 | {(hover || hoverBorder || cursor || onHoverOver || onHoverOut) ? ( 227 | 228 | {this.renderHoverChildren} 229 | 230 | ) : null} 231 | 232 | {cluster ? ( 233 | 240 | ) : null} 241 | 242 | {clusterBorder ? ( 243 | 250 | ) : null} 251 | 252 | {clusterLabel ? ( 253 | 260 | ) : null} 261 | 262 | {active ? ( 263 | 274 | ) : null} 275 | 276 | {activeBorder ? ( 277 | 288 | ) : null} 289 | 290 | ) 291 | } 292 | } 293 | 294 | export default ButtonLayer 295 | -------------------------------------------------------------------------------- /modules/button-layer/src/index.js: -------------------------------------------------------------------------------- 1 | import ButtonLayer from './ButtonLayer' 2 | export default ButtonLayer 3 | -------------------------------------------------------------------------------- /modules/button-layer/src/stories/Example.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ButtonLayer - Example 3 | * 4 | * ## ButtonLayer Example - Interactive Polygons 5 | * 6 | * Uses the ButtonLayer 'meta component' to add polygons 7 | * with border, hover, and active states. 8 | * 9 | * Toggle any polygon to 'zoom' into/out of it. 10 | */ 11 | import React from 'react' 12 | import bbox from '@turf/bbox' 13 | import {mapDefaults} from '@react-mapboxgl/docs' 14 | import {MapGL} from '@react-mapboxgl/core' 15 | import Hover from '@react-mapboxgl/hover' 16 | import Toggle from '@react-mapboxgl/toggle' 17 | import ButtonLayer from '../' 18 | 19 | class Story extends React.Component { 20 | state = { 21 | activeName: null, 22 | bbox: mapDefaults.bbox 23 | } 24 | 25 | constructor () { 26 | super() 27 | this.handleToggle = this.handleToggle.bind(this) 28 | } 29 | 30 | handleToggle (feature, isOn) { 31 | if (isOn) { 32 | this.setState({ 33 | activeName: feature.properties.name, 34 | bbox: bbox(feature) 35 | }) 36 | } else { 37 | this.setState({ 38 | activeName: null, 39 | bbox: mapDefaults.bbox 40 | }) 41 | } 42 | } 43 | 44 | render () { 45 | return ( 46 | 47 | 98 | 99 | {({properties: names}) => names[0] ? ( 100 |

111 | {names[0]} 112 |

113 | ) : null} 114 |
115 | 121 |
122 | ) 123 | } 124 | } 125 | 126 | export default Story 127 | -------------------------------------------------------------------------------- /modules/button-layer/src/stories/PointCluster.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ButtonLayer - Point Clustering 3 | * 4 | * ## ButtonLayer Example - Point Clustering 5 | * 6 | * Uses the ButtonLayer 'meta component' to add points with clustering. 7 | */ 8 | import React from 'react' 9 | import {mapDefaults} from '@react-mapboxgl/docs' 10 | import {MapGL} from '@react-mapboxgl/core' 11 | import ButtonLayer from '../' 12 | 13 | class Story extends React.Component { 14 | render () { 15 | return ( 16 | 17 | 72 | 73 | ) 74 | } 75 | } 76 | 77 | export default Story 78 | -------------------------------------------------------------------------------- /modules/button-layer/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@react-mapboxgl/click@^2.1.2": 6 | version "2.1.2" 7 | resolved "https://registry.npmjs.org/@react-mapboxgl/click/-/click-2.1.2.tgz#05d338575f8be530efddd2a9b0f8f88be57e598e" 8 | dependencies: 9 | "@react-mapboxgl/core" "^2.1.1" 10 | "@react-mapboxgl/docs" "^2.1.0" 11 | 12 | "@react-mapboxgl/core@^2.1.1": 13 | version "2.1.1" 14 | resolved "https://registry.npmjs.org/@react-mapboxgl/core/-/core-2.1.1.tgz#e337dc29c2c4f6e78284ba9994cde4a41e2cd929" 15 | dependencies: 16 | "@react-mapboxgl/docs" "^2.1.0" 17 | 18 | "@react-mapboxgl/docs@^2.1.0": 19 | version "2.1.0" 20 | resolved "https://registry.npmjs.org/@react-mapboxgl/docs/-/docs-2.1.0.tgz#b4c81a6a49ed6ca9c6f9328f35a86d309797060f" 21 | 22 | "@react-mapboxgl/hover@^2.1.1": 23 | version "2.1.1" 24 | resolved "https://registry.npmjs.org/@react-mapboxgl/hover/-/hover-2.1.1.tgz#cb13449e39f84d3311f58a0392b36ccca9e4cc66" 25 | dependencies: 26 | "@react-mapboxgl/core" "^2.1.1" 27 | "@react-mapboxgl/docs" "^2.1.0" 28 | "@terraeclipse/throttle-raf-decorator" "^1.0.4" 29 | 30 | "@react-mapboxgl/toggle@^2.1.2": 31 | version "2.1.2" 32 | resolved "https://registry.npmjs.org/@react-mapboxgl/toggle/-/toggle-2.1.2.tgz#b4ca4313464aa1b9103577b7ef329222689665c9" 33 | dependencies: 34 | "@react-mapboxgl/click" "^2.1.2" 35 | "@react-mapboxgl/core" "^2.1.1" 36 | "@react-mapboxgl/docs" "^2.1.0" 37 | 38 | "@terraeclipse/throttle-raf-decorator@^1.0.4": 39 | version "1.0.4" 40 | resolved "https://registry.npmjs.org/@terraeclipse/throttle-raf-decorator/-/throttle-raf-decorator-1.0.4.tgz#c23c37c20f5a433904d2284e1224fe51fde3f653" 41 | dependencies: 42 | raf "^3.3.2" 43 | 44 | "@turf/bbox@^4.3.0": 45 | version "4.4.0" 46 | resolved "https://registry.npmjs.org/@turf/bbox/-/bbox-4.4.0.tgz#3149458eb41404427cf786a90fb3680a0e8aab55" 47 | dependencies: 48 | "@turf/meta" "^4.4.0" 49 | 50 | "@turf/meta@^4.4.0": 51 | version "4.4.0" 52 | resolved "https://registry.npmjs.org/@turf/meta/-/meta-4.4.0.tgz#4fa25d4cc0525bd4cdbaf4ff68a6f8ae81f1975f" 53 | 54 | "@turf/union@^4.4.0": 55 | version "4.4.0" 56 | resolved "https://registry.npmjs.org/@turf/union/-/union-4.4.0.tgz#cad6e957c5ef843f64b061d595b9e884b12cf710" 57 | dependencies: 58 | jsts "1.3.0" 59 | 60 | asap@~2.0.3: 61 | version "2.0.5" 62 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 63 | 64 | core-js@^1.0.0: 65 | version "1.2.7" 66 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 67 | 68 | encoding@^0.1.11: 69 | version "0.1.12" 70 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 71 | dependencies: 72 | iconv-lite "~0.4.13" 73 | 74 | fbjs@^0.8.9: 75 | version "0.8.12" 76 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 77 | dependencies: 78 | core-js "^1.0.0" 79 | isomorphic-fetch "^2.1.1" 80 | loose-envify "^1.0.0" 81 | object-assign "^4.1.0" 82 | promise "^7.1.1" 83 | setimmediate "^1.0.5" 84 | ua-parser-js "^0.7.9" 85 | 86 | iconv-lite@~0.4.13: 87 | version "0.4.17" 88 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d" 89 | 90 | is-stream@^1.0.1: 91 | version "1.1.0" 92 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 93 | 94 | isomorphic-fetch@^2.1.1: 95 | version "2.2.1" 96 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 97 | dependencies: 98 | node-fetch "^1.0.1" 99 | whatwg-fetch ">=0.10.0" 100 | 101 | js-tokens@^3.0.0: 102 | version "3.0.1" 103 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 104 | 105 | jsts@1.3.0: 106 | version "1.3.0" 107 | resolved "https://registry.npmjs.org/jsts/-/jsts-1.3.0.tgz#e93a76f97ac9bda7d4625d9d6470f0d60ac80e45" 108 | 109 | lodash@^4.17.4: 110 | version "4.17.4" 111 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 112 | 113 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 114 | version "1.3.1" 115 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 116 | dependencies: 117 | js-tokens "^3.0.0" 118 | 119 | node-fetch@^1.0.1: 120 | version "1.7.1" 121 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 122 | dependencies: 123 | encoding "^0.1.11" 124 | is-stream "^1.0.1" 125 | 126 | object-assign@^4.1.0: 127 | version "4.1.1" 128 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 129 | 130 | performance-now@^2.1.0: 131 | version "2.1.0" 132 | resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 133 | 134 | promise@^7.1.1: 135 | version "7.1.1" 136 | resolved "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 137 | dependencies: 138 | asap "~2.0.3" 139 | 140 | prop-types@^15.5.10, prop-types@^15.5.7: 141 | version "15.5.10" 142 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" 143 | dependencies: 144 | fbjs "^0.8.9" 145 | loose-envify "^1.3.1" 146 | 147 | raf@^3.3.2: 148 | version "3.3.2" 149 | resolved "https://registry.npmjs.org/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27" 150 | dependencies: 151 | performance-now "^2.1.0" 152 | 153 | react@^15.5.4: 154 | version "15.5.4" 155 | resolved "https://registry.npmjs.org/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" 156 | dependencies: 157 | fbjs "^0.8.9" 158 | loose-envify "^1.1.0" 159 | object-assign "^4.1.0" 160 | prop-types "^15.5.7" 161 | 162 | setimmediate@^1.0.5: 163 | version "1.0.5" 164 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 165 | 166 | ua-parser-js@^0.7.9: 167 | version "0.7.12" 168 | resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 169 | 170 | whatwg-fetch@>=0.10.0: 171 | version "2.0.3" 172 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 173 | -------------------------------------------------------------------------------- /modules/click/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/click/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Terra Eclipse, Inc. 2 | http://www.terraeclipse.com/ 3 | 4 | Copyright 2017 Brian Link 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 19 | ------------------------------------------------------------------------- 20 | Apache License 21 | Version 2.0, January 2004 22 | http://www.apache.org/licenses/ 23 | 24 | 25 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 26 | 27 | 1. Definitions. 28 | 29 | "License" shall mean the terms and conditions for use, reproduction, 30 | and distribution as defined by Sections 1 through 9 of this document. 31 | 32 | "Licensor" shall mean the copyright owner or entity authorized by 33 | the copyright owner that is granting the License. 34 | 35 | "Legal Entity" shall mean the union of the acting entity and all 36 | other entities that control, are controlled by, or are under common 37 | control with that entity. For the purposes of this definition, 38 | "control" means (i) the power, direct or indirect, to cause the 39 | direction or management of such entity, whether by contract or 40 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 41 | outstanding shares, or (iii) beneficial ownership of such entity. 42 | 43 | "You" (or "Your") shall mean an individual or Legal Entity 44 | exercising permissions granted by this License. 45 | 46 | "Source" form shall mean the preferred form for making modifications, 47 | including but not limited to software source code, documentation 48 | source, and configuration files. 49 | 50 | "Object" form shall mean any form resulting from mechanical 51 | transformation or translation of a Source form, including but 52 | not limited to compiled object code, generated documentation, 53 | and conversions to other media types. 54 | 55 | "Work" shall mean the work of authorship, whether in Source or 56 | Object form, made available under the License, as indicated by a 57 | copyright notice that is included in or attached to the work 58 | (an example is provided in the Appendix below). 59 | 60 | "Derivative Works" shall mean any work, whether in Source or Object 61 | form, that is based on (or derived from) the Work and for which the 62 | editorial revisions, annotations, elaborations, or other modifications 63 | represent, as a whole, an original work of authorship. For the purposes 64 | of this License, Derivative Works shall not include works that remain 65 | separable from, or merely link (or bind by name) to the interfaces of, 66 | the Work and Derivative Works thereof. 67 | 68 | "Contribution" shall mean any work of authorship, including 69 | the original version of the Work and any modifications or additions 70 | to that Work or Derivative Works thereof, that is intentionally 71 | submitted to Licensor for inclusion in the Work by the copyright owner 72 | or by an individual or Legal Entity authorized to submit on behalf of 73 | the copyright owner. For the purposes of this definition, "submitted" 74 | means any form of electronic, verbal, or written communication sent 75 | to the Licensor or its representatives, including but not limited to 76 | communication on electronic mailing lists, source code control systems, 77 | and issue tracking systems that are managed by, or on behalf of, the 78 | Licensor for the purpose of discussing and improving the Work, but 79 | excluding communication that is conspicuously marked or otherwise 80 | designated in writing by the copyright owner as "Not a Contribution." 81 | 82 | "Contributor" shall mean Licensor and any individual or Legal Entity 83 | on behalf of whom a Contribution has been received by Licensor and 84 | subsequently incorporated within the Work. 85 | 86 | 2. Grant of Copyright License. Subject to the terms and conditions of 87 | this License, each Contributor hereby grants to You a perpetual, 88 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 89 | copyright license to reproduce, prepare Derivative Works of, 90 | publicly display, publicly perform, sublicense, and distribute the 91 | Work and such Derivative Works in Source or Object form. 92 | 93 | 3. Grant of Patent License. Subject to the terms and conditions of 94 | this License, each Contributor hereby grants to You a perpetual, 95 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 96 | (except as stated in this section) patent license to make, have made, 97 | use, offer to sell, sell, import, and otherwise transfer the Work, 98 | where such license applies only to those patent claims licensable 99 | by such Contributor that are necessarily infringed by their 100 | Contribution(s) alone or by combination of their Contribution(s) 101 | with the Work to which such Contribution(s) was submitted. If You 102 | institute patent litigation against any entity (including a 103 | cross-claim or counterclaim in a lawsuit) alleging that the Work 104 | or a Contribution incorporated within the Work constitutes direct 105 | or contributory patent infringement, then any patent licenses 106 | granted to You under this License for that Work shall terminate 107 | as of the date such litigation is filed. 108 | 109 | 4. Redistribution. You may reproduce and distribute copies of the 110 | Work or Derivative Works thereof in any medium, with or without 111 | modifications, and in Source or Object form, provided that You 112 | meet the following conditions: 113 | 114 | (a) You must give any other recipients of the Work or 115 | Derivative Works a copy of this License; and 116 | 117 | (b) You must cause any modified files to carry prominent notices 118 | stating that You changed the files; and 119 | 120 | (c) You must retain, in the Source form of any Derivative Works 121 | that You distribute, all copyright, patent, trademark, and 122 | attribution notices from the Source form of the Work, 123 | excluding those notices that do not pertain to any part of 124 | the Derivative Works; and 125 | 126 | (d) If the Work includes a "NOTICE" text file as part of its 127 | distribution, then any Derivative Works that You distribute must 128 | include a readable copy of the attribution notices contained 129 | within such NOTICE file, excluding those notices that do not 130 | pertain to any part of the Derivative Works, in at least one 131 | of the following places: within a NOTICE text file distributed 132 | as part of the Derivative Works; within the Source form or 133 | documentation, if provided along with the Derivative Works; or, 134 | within a display generated by the Derivative Works, if and 135 | wherever such third-party notices normally appear. The contents 136 | of the NOTICE file are for informational purposes only and 137 | do not modify the License. You may add Your own attribution 138 | notices within Derivative Works that You distribute, alongside 139 | or as an addendum to the NOTICE text from the Work, provided 140 | that such additional attribution notices cannot be construed 141 | as modifying the License. 142 | 143 | You may add Your own copyright statement to Your modifications and 144 | may provide additional or different license terms and conditions 145 | for use, reproduction, or distribution of Your modifications, or 146 | for any such Derivative Works as a whole, provided Your use, 147 | reproduction, and distribution of the Work otherwise complies with 148 | the conditions stated in this License. 149 | 150 | 5. Submission of Contributions. Unless You explicitly state otherwise, 151 | any Contribution intentionally submitted for inclusion in the Work 152 | by You to the Licensor shall be under the terms and conditions of 153 | this License, without any additional terms or conditions. 154 | Notwithstanding the above, nothing herein shall supersede or modify 155 | the terms of any separate license agreement you may have executed 156 | with Licensor regarding such Contributions. 157 | 158 | 6. Trademarks. This License does not grant permission to use the trade 159 | names, trademarks, service marks, or product names of the Licensor, 160 | except as required for reasonable and customary use in describing the 161 | origin of the Work and reproducing the content of the NOTICE file. 162 | 163 | 7. Disclaimer of Warranty. Unless required by applicable law or 164 | agreed to in writing, Licensor provides the Work (and each 165 | Contributor provides its Contributions) on an "AS IS" BASIS, 166 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 167 | implied, including, without limitation, any warranties or conditions 168 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 169 | PARTICULAR PURPOSE. You are solely responsible for determining the 170 | appropriateness of using or redistributing the Work and assume any 171 | risks associated with Your exercise of permissions under this License. 172 | 173 | 8. Limitation of Liability. In no event and under no legal theory, 174 | whether in tort (including negligence), contract, or otherwise, 175 | unless required by applicable law (such as deliberate and grossly 176 | negligent acts) or agreed to in writing, shall any Contributor be 177 | liable to You for damages, including any direct, indirect, special, 178 | incidental, or consequential damages of any character arising as a 179 | result of this License or out of the use or inability to use the 180 | Work (including but not limited to damages for loss of goodwill, 181 | work stoppage, computer failure or malfunction, or any and all 182 | other commercial damages or losses), even if such Contributor 183 | has been advised of the possibility of such damages. 184 | 185 | 9. Accepting Warranty or Additional Liability. While redistributing 186 | the Work or Derivative Works thereof, You may choose to offer, 187 | and charge a fee for, acceptance of support, warranty, indemnity, 188 | or other liability obligations and/or rights consistent with this 189 | License. However, in accepting such obligations, You may act only 190 | on Your own behalf and on Your sole responsibility, not on behalf 191 | of any other Contributor, and only if You agree to indemnify, 192 | defend, and hold each Contributor harmless for any liability 193 | incurred by, or claims asserted against, such Contributor by reason 194 | of your accepting any such warranty or additional liability. 195 | 196 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /modules/click/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/click 2 | ====================== 3 | 4 | The `Click` component provides some convenience around the `MapEvent` or `LayerEvent` 5 | for click handling. It optionally avoids double clicks (for when you want 6 | to use double click for map zooming). 7 | 8 | Usage 9 | ----- 10 | 11 | For an example usage, [see the storybook](https://terraeclipse.github.io/react-mapboxgl/?selectedKind=Click&selectedStory=Example). 12 | 13 | - - - 14 | 15 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 16 | 17 | Terra Eclipse, Inc. is a nationally recognized political technology and 18 | strategy firm located in Santa Cruz, CA and Washington, D.C. 19 | -------------------------------------------------------------------------------- /modules/click/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/click", 3 | "version": "2.1.4", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Toggle component for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "dependencies": { 25 | "@react-mapboxgl/core": "^2.1.3", 26 | "@react-mapboxgl/docs": "^2.1.0" 27 | }, 28 | "peerDependencies": { 29 | "prop-types": "^15.5.10", 30 | "react": "^15.5.4" 31 | }, 32 | "devDependencies": { 33 | "lodash": "^4.17.4", 34 | "prop-types": "^15.5.10", 35 | "react": "^15.5.4" 36 | }, 37 | "author": "Brian Link", 38 | "license": "Apache-2.0", 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 45 | }, 46 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/click#readme", 47 | "keywords": [ 48 | "react-component" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /modules/click/src/Click.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import {Children, MapEvent, LayerEvent} from '@react-mapboxgl/core' 4 | 5 | class Click extends React.Component { 6 | static propTypes = { 7 | layer: PropTypes.string, 8 | clickEvent: PropTypes.string, 9 | avoidDoubleClick: PropTypes.bool, 10 | doubleClickSpeed: PropTypes.number, 11 | onClick: PropTypes.func, 12 | children: PropTypes.func 13 | } 14 | 15 | static defaultProps = { 16 | clickEvent: 'click', 17 | avoidDoubleClick: false, 18 | doubleClickSpeed: 300 19 | } 20 | 21 | static contextTypes = { 22 | map: PropTypes.object 23 | } 24 | 25 | constructor () { 26 | super() 27 | this.handleClick = this.handleClick.bind(this) 28 | this.handleDoubleClick = this.handleDoubleClick.bind(this) 29 | } 30 | 31 | handleClick (e) { 32 | if (this.props.onClick) { 33 | if (this.props.avoidDoubleClick) { 34 | clearTimeout(this._doubleClickTimeout) 35 | this._doubleClickTimeout = setTimeout(() => { 36 | this.props.onClick(e, e.features) 37 | }, this.props.doubleClickSpeed) 38 | } else { 39 | this.props.onClick(e, e.features) 40 | } 41 | } 42 | } 43 | 44 | handleDoubleClick (e) { 45 | clearTimeout(this._doubleClickTimeout) 46 | } 47 | 48 | render () { 49 | let {clickEvent, layer, avoidDoubleClick} = this.props 50 | return ( 51 | 52 | {layer ? ( 53 | 54 | ) : ( 55 | 56 | )} 57 | {avoidDoubleClick ? ( 58 | 59 | ) : null} 60 | {this.props.children} 61 | 62 | ) 63 | } 64 | } 65 | 66 | export default Click 67 | -------------------------------------------------------------------------------- /modules/click/src/Click.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Click - Example 3 | * 4 | * ## Click Features in a Layer 5 | * 6 | * Adds a click handler for features in a Layer. `` provides some helpful 7 | * functionality around `` and `` for optionally avoiding 8 | * double-click events (useful when you want to use double-click for zooming). 9 | */ 10 | import React from 'react' 11 | import {action} from '@storybook/addon-actions' 12 | import {mapDefaults} from '@react-mapboxgl/docs' 13 | import {MapGL, Source, Layer} from '@react-mapboxgl/core' 14 | import Click from './' 15 | 16 | class Story extends React.Component { 17 | state = { 18 | multiple: false 19 | } 20 | 21 | constructor () { 22 | super() 23 | this.handleClick = this.handleClick.bind(this) 24 | } 25 | 26 | handleClick (e, features) { 27 | console.log(e) 28 | action('click')(features) 29 | } 30 | 31 | render () { 32 | return ( 33 | 34 | 39 | 48 | 57 | 62 | 63 | ) 64 | } 65 | } 66 | 67 | export default Story 68 | -------------------------------------------------------------------------------- /modules/click/src/index.js: -------------------------------------------------------------------------------- 1 | import Click from './Click' 2 | export default Click 3 | -------------------------------------------------------------------------------- /modules/click/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@react-mapboxgl/core@^2.0.3": 6 | version "2.0.3" 7 | resolved "https://registry.npmjs.org/@react-mapboxgl/core/-/core-2.0.3.tgz#f76c3c4d83c3da29a528bfe393bb9482ade76b46" 8 | dependencies: 9 | "@react-mapboxgl/docs" "^2.0.0" 10 | 11 | "@react-mapboxgl/docs@^2.0.0": 12 | version "2.0.0" 13 | resolved "https://registry.npmjs.org/@react-mapboxgl/docs/-/docs-2.0.0.tgz#c4110f34f423a05e55cc7cb8e0646dc44a1be2c5" 14 | 15 | asap@~2.0.3: 16 | version "2.0.5" 17 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 18 | 19 | core-js@^1.0.0: 20 | version "1.2.7" 21 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 22 | 23 | encoding@^0.1.11: 24 | version "0.1.12" 25 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 26 | dependencies: 27 | iconv-lite "~0.4.13" 28 | 29 | fbjs@^0.8.9: 30 | version "0.8.12" 31 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 32 | dependencies: 33 | core-js "^1.0.0" 34 | isomorphic-fetch "^2.1.1" 35 | loose-envify "^1.0.0" 36 | object-assign "^4.1.0" 37 | promise "^7.1.1" 38 | setimmediate "^1.0.5" 39 | ua-parser-js "^0.7.9" 40 | 41 | iconv-lite@~0.4.13: 42 | version "0.4.17" 43 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d" 44 | 45 | is-stream@^1.0.1: 46 | version "1.1.0" 47 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 48 | 49 | isomorphic-fetch@^2.1.1: 50 | version "2.2.1" 51 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 52 | dependencies: 53 | node-fetch "^1.0.1" 54 | whatwg-fetch ">=0.10.0" 55 | 56 | js-tokens@^3.0.0: 57 | version "3.0.1" 58 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 59 | 60 | lodash@^4.17.4: 61 | version "4.17.4" 62 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 63 | 64 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 65 | version "1.3.1" 66 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 67 | dependencies: 68 | js-tokens "^3.0.0" 69 | 70 | node-fetch@^1.0.1: 71 | version "1.7.1" 72 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 73 | dependencies: 74 | encoding "^0.1.11" 75 | is-stream "^1.0.1" 76 | 77 | object-assign@^4.1.0: 78 | version "4.1.1" 79 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 80 | 81 | promise@^7.1.1: 82 | version "7.1.1" 83 | resolved "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 84 | dependencies: 85 | asap "~2.0.3" 86 | 87 | prop-types@^15.5.10, prop-types@^15.5.7: 88 | version "15.5.10" 89 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" 90 | dependencies: 91 | fbjs "^0.8.9" 92 | loose-envify "^1.3.1" 93 | 94 | react@^15.5.4: 95 | version "15.5.4" 96 | resolved "https://registry.npmjs.org/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" 97 | dependencies: 98 | fbjs "^0.8.9" 99 | loose-envify "^1.1.0" 100 | object-assign "^4.1.0" 101 | prop-types "^15.5.7" 102 | 103 | setimmediate@^1.0.5: 104 | version "1.0.5" 105 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 106 | 107 | ua-parser-js@^0.7.9: 108 | version "0.7.12" 109 | resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 110 | 111 | whatwg-fetch@>=0.10.0: 112 | version "2.0.3" 113 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 114 | -------------------------------------------------------------------------------- /modules/core/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/core/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Terra Eclipse, Inc. 2 | http://www.terraeclipse.com/ 3 | 4 | Copyright 2017 Brian Link 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 19 | ------------------------------------------------------------------------- 20 | Apache License 21 | Version 2.0, January 2004 22 | http://www.apache.org/licenses/ 23 | 24 | 25 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 26 | 27 | 1. Definitions. 28 | 29 | "License" shall mean the terms and conditions for use, reproduction, 30 | and distribution as defined by Sections 1 through 9 of this document. 31 | 32 | "Licensor" shall mean the copyright owner or entity authorized by 33 | the copyright owner that is granting the License. 34 | 35 | "Legal Entity" shall mean the union of the acting entity and all 36 | other entities that control, are controlled by, or are under common 37 | control with that entity. For the purposes of this definition, 38 | "control" means (i) the power, direct or indirect, to cause the 39 | direction or management of such entity, whether by contract or 40 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 41 | outstanding shares, or (iii) beneficial ownership of such entity. 42 | 43 | "You" (or "Your") shall mean an individual or Legal Entity 44 | exercising permissions granted by this License. 45 | 46 | "Source" form shall mean the preferred form for making modifications, 47 | including but not limited to software source code, documentation 48 | source, and configuration files. 49 | 50 | "Object" form shall mean any form resulting from mechanical 51 | transformation or translation of a Source form, including but 52 | not limited to compiled object code, generated documentation, 53 | and conversions to other media types. 54 | 55 | "Work" shall mean the work of authorship, whether in Source or 56 | Object form, made available under the License, as indicated by a 57 | copyright notice that is included in or attached to the work 58 | (an example is provided in the Appendix below). 59 | 60 | "Derivative Works" shall mean any work, whether in Source or Object 61 | form, that is based on (or derived from) the Work and for which the 62 | editorial revisions, annotations, elaborations, or other modifications 63 | represent, as a whole, an original work of authorship. For the purposes 64 | of this License, Derivative Works shall not include works that remain 65 | separable from, or merely link (or bind by name) to the interfaces of, 66 | the Work and Derivative Works thereof. 67 | 68 | "Contribution" shall mean any work of authorship, including 69 | the original version of the Work and any modifications or additions 70 | to that Work or Derivative Works thereof, that is intentionally 71 | submitted to Licensor for inclusion in the Work by the copyright owner 72 | or by an individual or Legal Entity authorized to submit on behalf of 73 | the copyright owner. For the purposes of this definition, "submitted" 74 | means any form of electronic, verbal, or written communication sent 75 | to the Licensor or its representatives, including but not limited to 76 | communication on electronic mailing lists, source code control systems, 77 | and issue tracking systems that are managed by, or on behalf of, the 78 | Licensor for the purpose of discussing and improving the Work, but 79 | excluding communication that is conspicuously marked or otherwise 80 | designated in writing by the copyright owner as "Not a Contribution." 81 | 82 | "Contributor" shall mean Licensor and any individual or Legal Entity 83 | on behalf of whom a Contribution has been received by Licensor and 84 | subsequently incorporated within the Work. 85 | 86 | 2. Grant of Copyright License. Subject to the terms and conditions of 87 | this License, each Contributor hereby grants to You a perpetual, 88 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 89 | copyright license to reproduce, prepare Derivative Works of, 90 | publicly display, publicly perform, sublicense, and distribute the 91 | Work and such Derivative Works in Source or Object form. 92 | 93 | 3. Grant of Patent License. Subject to the terms and conditions of 94 | this License, each Contributor hereby grants to You a perpetual, 95 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 96 | (except as stated in this section) patent license to make, have made, 97 | use, offer to sell, sell, import, and otherwise transfer the Work, 98 | where such license applies only to those patent claims licensable 99 | by such Contributor that are necessarily infringed by their 100 | Contribution(s) alone or by combination of their Contribution(s) 101 | with the Work to which such Contribution(s) was submitted. If You 102 | institute patent litigation against any entity (including a 103 | cross-claim or counterclaim in a lawsuit) alleging that the Work 104 | or a Contribution incorporated within the Work constitutes direct 105 | or contributory patent infringement, then any patent licenses 106 | granted to You under this License for that Work shall terminate 107 | as of the date such litigation is filed. 108 | 109 | 4. Redistribution. You may reproduce and distribute copies of the 110 | Work or Derivative Works thereof in any medium, with or without 111 | modifications, and in Source or Object form, provided that You 112 | meet the following conditions: 113 | 114 | (a) You must give any other recipients of the Work or 115 | Derivative Works a copy of this License; and 116 | 117 | (b) You must cause any modified files to carry prominent notices 118 | stating that You changed the files; and 119 | 120 | (c) You must retain, in the Source form of any Derivative Works 121 | that You distribute, all copyright, patent, trademark, and 122 | attribution notices from the Source form of the Work, 123 | excluding those notices that do not pertain to any part of 124 | the Derivative Works; and 125 | 126 | (d) If the Work includes a "NOTICE" text file as part of its 127 | distribution, then any Derivative Works that You distribute must 128 | include a readable copy of the attribution notices contained 129 | within such NOTICE file, excluding those notices that do not 130 | pertain to any part of the Derivative Works, in at least one 131 | of the following places: within a NOTICE text file distributed 132 | as part of the Derivative Works; within the Source form or 133 | documentation, if provided along with the Derivative Works; or, 134 | within a display generated by the Derivative Works, if and 135 | wherever such third-party notices normally appear. The contents 136 | of the NOTICE file are for informational purposes only and 137 | do not modify the License. You may add Your own attribution 138 | notices within Derivative Works that You distribute, alongside 139 | or as an addendum to the NOTICE text from the Work, provided 140 | that such additional attribution notices cannot be construed 141 | as modifying the License. 142 | 143 | You may add Your own copyright statement to Your modifications and 144 | may provide additional or different license terms and conditions 145 | for use, reproduction, or distribution of Your modifications, or 146 | for any such Derivative Works as a whole, provided Your use, 147 | reproduction, and distribution of the Work otherwise complies with 148 | the conditions stated in this License. 149 | 150 | 5. Submission of Contributions. Unless You explicitly state otherwise, 151 | any Contribution intentionally submitted for inclusion in the Work 152 | by You to the Licensor shall be under the terms and conditions of 153 | this License, without any additional terms or conditions. 154 | Notwithstanding the above, nothing herein shall supersede or modify 155 | the terms of any separate license agreement you may have executed 156 | with Licensor regarding such Contributions. 157 | 158 | 6. Trademarks. This License does not grant permission to use the trade 159 | names, trademarks, service marks, or product names of the Licensor, 160 | except as required for reasonable and customary use in describing the 161 | origin of the Work and reproducing the content of the NOTICE file. 162 | 163 | 7. Disclaimer of Warranty. Unless required by applicable law or 164 | agreed to in writing, Licensor provides the Work (and each 165 | Contributor provides its Contributions) on an "AS IS" BASIS, 166 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 167 | implied, including, without limitation, any warranties or conditions 168 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 169 | PARTICULAR PURPOSE. You are solely responsible for determining the 170 | appropriateness of using or redistributing the Work and assume any 171 | risks associated with Your exercise of permissions under this License. 172 | 173 | 8. Limitation of Liability. In no event and under no legal theory, 174 | whether in tort (including negligence), contract, or otherwise, 175 | unless required by applicable law (such as deliberate and grossly 176 | negligent acts) or agreed to in writing, shall any Contributor be 177 | liable to You for damages, including any direct, indirect, special, 178 | incidental, or consequential damages of any character arising as a 179 | result of this License or out of the use or inability to use the 180 | Work (including but not limited to damages for loss of goodwill, 181 | work stoppage, computer failure or malfunction, or any and all 182 | other commercial damages or losses), even if such Contributor 183 | has been advised of the possibility of such damages. 184 | 185 | 9. Accepting Warranty or Additional Liability. While redistributing 186 | the Work or Derivative Works thereof, You may choose to offer, 187 | and charge a fee for, acceptance of support, warranty, indemnity, 188 | or other liability obligations and/or rights consistent with this 189 | License. However, in accepting such obligations, You may act only 190 | on Your own behalf and on Your sole responsibility, not on behalf 191 | of any other Contributor, and only if You agree to indemnify, 192 | defend, and hold each Contributor harmless for any liability 193 | incurred by, or claims asserted against, such Contributor by reason 194 | of your accepting any such warranty or additional liability. 195 | 196 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /modules/core/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/core 2 | ==================== 3 | 4 | This is the core module for `@react-mapboxgl`. Please see the [main README](https://github.com/TerraEclipse/react-mapboxgl) or the [documentation](https://terraeclipse.github.io/react-mapboxgl). 5 | 6 | - - - 7 | 8 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 9 | 10 | Terra Eclipse, Inc. is a nationally recognized political technology and 11 | strategy firm located in Santa Cruz, CA and Washington, D.C. 12 | -------------------------------------------------------------------------------- /modules/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/core", 3 | "version": "2.1.3", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Core module for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "dependencies": { 25 | "@react-mapboxgl/docs": "^2.1.0" 26 | }, 27 | "peerDependencies": { 28 | "lodash": "^4.17.4", 29 | "prop-types": "^15.5.10", 30 | "react": "^15.5.4" 31 | }, 32 | "devDependencies": { 33 | "lodash": "^4.17.4", 34 | "mapbox-gl": "^0.37.0", 35 | "prop-types": "^15.5.10", 36 | "react": "^15.5.4" 37 | }, 38 | "author": "Brian Link", 39 | "license": "Apache-2.0", 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 46 | }, 47 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/react-mapbox#readme", 48 | "keywords": [ 49 | "react-component" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /modules/core/src/Children.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | /** 4 | * Just a placeholder until rendering arrays is possible (React 16), so I can 5 | * easily find-replace and, until then, apply styling if needed. 6 | */ 7 | export default class Children extends React.PureComponent { 8 | render () { 9 | return ( 10 |
11 | {this.props.children} 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modules/core/src/Control.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class Control extends React.PureComponent { 6 | static propTypes = { 7 | type: PropTypes.oneOfType([ 8 | PropTypes.oneOf([ 9 | 'Navigation', 10 | 'Geolocate', 11 | 'Attribution', 12 | 'Scale', 13 | 'Fullscreen' 14 | ]), 15 | PropTypes.object, 16 | PropTypes.func 17 | ]).isRequired, 18 | position: PropTypes.string 19 | } 20 | 21 | static defaultProps = { 22 | position: 'top-right' 23 | } 24 | 25 | static contextTypes = { 26 | map: PropTypes.object, 27 | mapboxgl: PropTypes.object 28 | } 29 | 30 | createControl (props) { 31 | let {mapboxgl} = this.context 32 | let ControlClass = (typeof props.type === 'string') 33 | ? mapboxgl[`${props.type}Control`] 34 | : props.type 35 | let options = _.omit(props, 'type', 'position') 36 | 37 | return new ControlClass(options) 38 | } 39 | 40 | componentDidMount () { 41 | let {map} = this.context 42 | this.control = this.createControl(this.props) 43 | map.addControl(this.control, this.props.position) 44 | } 45 | 46 | componentWillUnmount () { 47 | let {map} = this.context 48 | map.removeControl(this.control) 49 | } 50 | 51 | componentWillReceiveProps (nextProps) { 52 | let {map} = this.context 53 | if (!_.isEqual(this.props, nextProps)) { 54 | map.removeControl(this.control) 55 | this.control = this.createControl(nextProps) 56 | map.addControl(this.control, nextProps.position) 57 | } 58 | } 59 | 60 | render () { 61 | return null 62 | } 63 | } 64 | 65 | export default Control 66 | -------------------------------------------------------------------------------- /modules/core/src/Image.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class Image extends React.PureComponent { 6 | static propTypes = { 7 | name: PropTypes.string, 8 | image: PropTypes.any // HTMLImageElement | ArrayBufferView 9 | } 10 | 11 | static contextTypes = { 12 | map: PropTypes.object 13 | } 14 | 15 | componentDidMount () { 16 | let {map} = this.context 17 | map.addImage(this.props.name, this.props.image, _.omit(this.props, 'name', 'image')) 18 | } 19 | 20 | componentWillUnmount () { 21 | let {map} = this.context 22 | map.removeImage(this.props.name) 23 | } 24 | 25 | render () { 26 | return null 27 | } 28 | } 29 | 30 | export default Image 31 | -------------------------------------------------------------------------------- /modules/core/src/Layer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import diff from './util/diff' 5 | import Children from './Children' 6 | import Source from './Source' 7 | import LayerEvents from './LayerEvents' 8 | 9 | class Layer extends React.Component { 10 | static propTypes = { 11 | id: PropTypes.string.isRequired, 12 | type: PropTypes.oneOf([ 13 | 'fill', 14 | 'line', 15 | 'symbol', 16 | 'circle', 17 | 'fill-extrusion', 18 | 'raster', 19 | 'background' 20 | ]).isRequired, 21 | source: PropTypes.oneOfType([ 22 | PropTypes.string, 23 | PropTypes.object 24 | ]).isRequired, 25 | sourceLayer: PropTypes.string, 26 | metadata: PropTypes.object, 27 | copy: PropTypes.string, 28 | minzoom: PropTypes.number, 29 | maxzoom: PropTypes.number, 30 | filter: PropTypes.array, 31 | layout: PropTypes.object, 32 | paint: PropTypes.object, 33 | before: PropTypes.string 34 | // LayerEvents 35 | } 36 | 37 | static contextTypes = { 38 | map: PropTypes.object 39 | } 40 | 41 | state = { 42 | added: false 43 | } 44 | 45 | shouldComponentUpdate (nextProps, nextState) { 46 | return ( 47 | !_.isEqual(this.props, nextProps) || 48 | !_.isEqual(this.state, nextState) 49 | ) 50 | } 51 | 52 | componentDidMount () { 53 | this.addLayer(this.props) 54 | } 55 | 56 | componentWillUnmount () { 57 | this.removeLayer(this.props) 58 | } 59 | 60 | componentWillReceiveProps (nextProps) { 61 | let {map} = this.context 62 | 63 | // Have to recreate layer if these change. 64 | if ( 65 | !_.isEqual(this.props.id, nextProps.id) || 66 | !_.isEqual(this.props.type, nextProps.type) || 67 | !_.isEqual(this.props.source, nextProps.source) || 68 | !_.isEqual(this.props.sourceLayer, nextProps.sourceLayer) || 69 | !_.isEqual(this.props.metadata, nextProps.metadata) || 70 | !_.isEqual(this.props.copy, nextProps.copy) 71 | ) { 72 | this.removeLayer(this.props) 73 | this.addLayer(nextProps) 74 | return 75 | } 76 | 77 | if (!_.isEqual(this.props.filter, nextProps.filter)) { 78 | map.setFilter(this.props.id, nextProps.filter) 79 | } 80 | 81 | _.each(diff(this.props.layout || {}, nextProps.layout || {}), ({type, key, value}) => { 82 | map.setLayoutProperty(this.props.id, key, type === 'remove' ? null : value) 83 | }) 84 | 85 | _.each(diff(this.props.paint || {}, nextProps.paint || {}), ({type, key, value}) => { 86 | map.setPaintProperty(this.props.id, key, type === 'remove' ? null : value) 87 | }) 88 | 89 | if (this.props.before !== nextProps.before) { 90 | map.moveLayer(this.props.id, nextProps.before) 91 | } 92 | 93 | if (this.props.minZoom !== nextProps.minZoom || 94 | this.props.maxZoom !== nextProps.maxZoom) { 95 | map.setLayerZoomRange(this.props.id, nextProps.minZoom, nextProps.maxZoom) 96 | } 97 | } 98 | 99 | addLayer (props) { 100 | let {map} = this.context 101 | let options = {} 102 | 103 | // Grab basic options from props. 104 | _.extend(options, _.omitBy(_.pick(props, [ 105 | 'id', 106 | 'type', 107 | 'metadata', 108 | 'minzoom', 109 | 'maxzoom', 110 | 'filter', 111 | 'layout', 112 | 'paint' 113 | ]), _.isNil)) 114 | 115 | // Grab 'ref' from 'copy'. 116 | if (props.copy) { 117 | options.ref = props.copy 118 | } 119 | 120 | // Check if we have a source id or object. 121 | if (_.isPlainObject(props.source)) { 122 | options.source = props.source.id || `${props.id}-source` 123 | } else { 124 | options.source = props.source 125 | } 126 | if (props.sourceLayer) { 127 | options['source-layer'] = props.sourceLayer 128 | } 129 | 130 | // Add the layer to the map. 131 | map.addLayer(options, props.before) 132 | map.fire('_addLayer', props.id) 133 | this.setState({added: true}) 134 | } 135 | 136 | removeLayer (props) { 137 | let {map} = this.context 138 | map.removeLayer(props.id) 139 | map.fire('_removeLayer', props.id) 140 | this.setState({added: false}) 141 | } 142 | 143 | render () { 144 | return ( 145 | 146 | {_.isPlainObject(this.props.source) ? ( 147 | 151 | ) : null} 152 | {this.state.added ? ( 153 | 157 | ) : null} 158 | 159 | ) 160 | } 161 | } 162 | 163 | export default Layer 164 | -------------------------------------------------------------------------------- /modules/core/src/LayerEvent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class LayerEvent extends React.PureComponent { 5 | static propTypes = { 6 | type: PropTypes.string.isRequired, 7 | layer: PropTypes.string.isRequired, 8 | onChange: PropTypes.func.isRequired 9 | } 10 | 11 | static contextTypes = { 12 | map: PropTypes.object 13 | } 14 | 15 | componentDidMount () { 16 | let {map} = this.context 17 | map.on(this.props.type, this.props.layer, this.props.onChange) 18 | } 19 | 20 | componentWillUnmount () { 21 | let {map} = this.context 22 | map.off(this.props.type, this.props.layer, this.props.onChange) 23 | } 24 | 25 | componentWillReceiveProps (nextProps) { 26 | let {map} = this.context 27 | if ( 28 | (this.props.type !== nextProps.type) || 29 | (this.props.layer !== nextProps.layer) || 30 | (this.props.onChange !== nextProps.onChange) 31 | ) { 32 | map.off(this.props.type, this.props.layer, this.props.onChange) 33 | map.on(nextProps.type, nextProps.layer, nextProps.onChange) 34 | } 35 | } 36 | 37 | render () { 38 | return null 39 | } 40 | } 41 | 42 | export default LayerEvent 43 | -------------------------------------------------------------------------------- /modules/core/src/LayerEvents.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import Children from './Children' 5 | import LayerEvent from './LayerEvent' 6 | 7 | class LayerEvents extends React.PureComponent { 8 | static propTypes = { 9 | // Layer id. 10 | layer: PropTypes.string.isRequired, 11 | 12 | // Events. 13 | onDblClick: PropTypes.func, 14 | onClick: PropTypes.func, 15 | onMouseMove: PropTypes.func, 16 | onMouseEnter: PropTypes.func, 17 | onMouseLeave: PropTypes.func, 18 | onMouseOver: PropTypes.func, 19 | onMouseOut: PropTypes.func, 20 | onMouseDown: PropTypes.func, 21 | onMouseUp: PropTypes.func, 22 | onTouchStart: PropTypes.func, 23 | onTouchEnd: PropTypes.func, 24 | onTouchCancel: PropTypes.func, 25 | onContextMenu: PropTypes.func 26 | } 27 | 28 | static pickEvents (props) { 29 | return _.pick( 30 | props, 31 | _.keys(_.omit(LayerEvents.propTypes, 'layer')) 32 | ) 33 | } 34 | 35 | render () { 36 | return ( 37 | 38 | {_.map(LayerEvents.propTypes, (_, type) => ( 39 | (this.props[type] && (type !== 'layer')) ? ( 40 | 46 | ) : null 47 | ))} 48 | 49 | ) 50 | } 51 | } 52 | 53 | export default LayerEvents 54 | -------------------------------------------------------------------------------- /modules/core/src/LoadImages.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import {Children, Image} from './' 5 | 6 | class LoadImages extends React.PureComponent { 7 | static propTypes = { 8 | children: PropTypes.node 9 | } 10 | 11 | static contextTypes = { 12 | map: PropTypes.object 13 | } 14 | 15 | state = { 16 | loaded: false, 17 | images: {} 18 | } 19 | 20 | constructor () { 21 | super() 22 | this.loadImage = this.loadImage.bind(this) 23 | } 24 | 25 | componentDidMount () { 26 | this.mounted = true 27 | this.loadImages(_.omit(this.props, 'children')) 28 | } 29 | 30 | componentWillReceiveProps (nextProps) { 31 | if (!_.isEqual( 32 | _.omit(this.props, 'children'), 33 | _.omit(nextProps, 'children') 34 | )) { 35 | this.loadImages(_.omit(this.props, 'children')) 36 | } 37 | } 38 | 39 | componentWillUnmount () { 40 | this.mounted = false 41 | } 42 | 43 | loadImages (images) { 44 | this.setState({loaded: false}) 45 | Promise.all(_.map(images, this.loadImage)).then((results) => { 46 | if (this.mounted) { 47 | this.setState({ 48 | loaded: true, 49 | images: _.fromPairs(results) 50 | }) 51 | } 52 | }) 53 | } 54 | 55 | loadImage (src, name) { 56 | let {map} = this.context 57 | return new Promise((resolve, reject) => { 58 | map.loadImage(src, (err, image) => { 59 | if (err) return reject(err) 60 | resolve([name, image]) 61 | }) 62 | }) 63 | } 64 | 65 | render () { 66 | return ( 67 | 68 | {_.map(this.state.images, (image, name) => ( 69 | 70 | ))} 71 | {this.state.loaded ? this.props.children : null} 72 | 73 | ) 74 | } 75 | } 76 | 77 | export default LoadImages 78 | -------------------------------------------------------------------------------- /modules/core/src/MapEvent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class MapEvent extends React.PureComponent { 5 | static propTypes = { 6 | type: PropTypes.string.isRequired, 7 | onChange: PropTypes.func.isRequired 8 | } 9 | 10 | static contextTypes = { 11 | map: PropTypes.object 12 | } 13 | 14 | componentDidMount () { 15 | let {map} = this.context 16 | map.on(this.props.type, this.props.onChange) 17 | } 18 | 19 | componentWillUnmount () { 20 | let {map} = this.context 21 | map.off(this.props.type, this.props.onChange) 22 | } 23 | 24 | componentWillReceiveProps (nextProps) { 25 | let {map} = this.context 26 | if ( 27 | (this.props.type !== nextProps.type) || 28 | (this.props.onChange !== nextProps.onChange) 29 | ) { 30 | map.off(this.props.type, this.props.onChange) 31 | map.on(nextProps.type, nextProps.onChange) 32 | } 33 | } 34 | 35 | render () { 36 | return null 37 | } 38 | } 39 | 40 | export default MapEvent 41 | -------------------------------------------------------------------------------- /modules/core/src/MapEvents.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import Children from './Children' 5 | import MapEvent from './MapEvent' 6 | 7 | class MapEvents extends React.PureComponent { 8 | static propTypes = { 9 | onResize: PropTypes.func, 10 | onDblClick: PropTypes.func, 11 | onClick: PropTypes.func, 12 | onMouseMove: PropTypes.func, 13 | onMoveStart: PropTypes.func, 14 | onMove: PropTypes.func, 15 | onMoveEnd: PropTypes.func, 16 | onMouseOut: PropTypes.func, 17 | onMouseDown: PropTypes.func, 18 | onMouseUp: PropTypes.func, 19 | onDragStart: PropTypes.func, 20 | onDrag: PropTypes.func, 21 | onDragEnd: PropTypes.func, 22 | onTouchMove: PropTypes.func, 23 | onTouchStart: PropTypes.func, 24 | onTouchEnd: PropTypes.func, 25 | onTouchCancel: PropTypes.func, 26 | onZoomStart: PropTypes.func, 27 | onZoom: PropTypes.func, 28 | onZoomEnd: PropTypes.func, 29 | onBoxZoomStart: PropTypes.func, 30 | onBoxZoomEnd: PropTypes.func, 31 | onBoxZoomCancel: PropTypes.func, 32 | onRotateStart: PropTypes.func, 33 | onRotate: PropTypes.func, 34 | onRotateEnd: PropTypes.func, 35 | onPitchStart: PropTypes.func, 36 | onPitch: PropTypes.func, 37 | onPitchEnd: PropTypes.func, 38 | onWebGLContextLost: PropTypes.func, 39 | onWebGLContextRestored: PropTypes.func, 40 | onRemove: PropTypes.func, 41 | onStyleData: PropTypes.func, 42 | onSourceData: PropTypes.func, 43 | onSourceDataLoading: PropTypes.func, 44 | onData: PropTypes.func, 45 | onDataLoading: PropTypes.func, 46 | onRender: PropTypes.func, 47 | onContextMenu: PropTypes.func, 48 | onStyleDataLoading: PropTypes.func, 49 | onError: PropTypes.func 50 | } 51 | 52 | static contextTypes = { 53 | map: PropTypes.object 54 | } 55 | 56 | render () { 57 | return ( 58 | 59 | {_.map(MapEvents.propTypes, (_, type) => ( 60 | this.props[type] ? ( 61 | 66 | ) : null 67 | ))} 68 | 69 | ) 70 | } 71 | } 72 | 73 | export default MapEvents 74 | -------------------------------------------------------------------------------- /modules/core/src/MapGL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A minor clean-up of react-mapbox-gl/map. 3 | */ 4 | import React from 'react' 5 | import PropTypes from 'prop-types' 6 | import _ from 'lodash' 7 | import MapEvents from './MapEvents' 8 | import MapOptions from './MapOptions' 9 | import MapPosition from './MapPosition' 10 | import MapInteraction from './MapInteraction' 11 | import Children from './Children' 12 | 13 | class MapGL extends React.Component { 14 | static propTypes = { 15 | containerStyle: PropTypes.object, 16 | renderUnsupported: PropTypes.func, 17 | onLoad: PropTypes.func, 18 | onStyleLoad: PropTypes.func 19 | // MapOptions 20 | // MapPosition 21 | // MapInteraction 22 | // MapEvents 23 | } 24 | 25 | static defaultProps = { 26 | containerStyle: { 27 | position: 'relative', 28 | width: '100%', 29 | paddingBottom: '50%' 30 | } 31 | } 32 | 33 | static contextTypes = { 34 | mapboxgl: PropTypes.object 35 | } 36 | 37 | static childContextTypes = { 38 | map: PropTypes.object 39 | } 40 | 41 | state = { 42 | unsupported: false, 43 | map: null 44 | } 45 | 46 | getChildContext = () => ({ 47 | map: this.state.map 48 | }) 49 | 50 | shouldComponentUpdate (nextProps, nextState) { 51 | return ( 52 | !_.isEqual(this.props, nextProps) || 53 | !_.isEqual(this.state, nextState) 54 | ) 55 | } 56 | 57 | componentDidMount () { 58 | let {mapboxgl} = this.context 59 | if (mapboxgl.supported()) { 60 | this.createMap() 61 | } else { 62 | this.setState({unsupported: true}) 63 | } 64 | } 65 | 66 | componentWillUnmount () { 67 | const {map} = this.state 68 | this.unmounted = true 69 | if (map) { 70 | map.off() 71 | // NOTE: We need to defer removing the map to after all 72 | // children have unmounted 73 | setImmediate(() => { 74 | map.remove() 75 | }) 76 | } 77 | } 78 | 79 | componentWillReceiveProps (nextProps) { 80 | let {map} = this.state 81 | if (map && !_.isEqual(this.props.containerStyle, nextProps.containerStyle)) { 82 | map.resize() 83 | } 84 | } 85 | 86 | createMap () { 87 | let {mapboxgl} = this.context 88 | 89 | // Build options from sub-components. 90 | const options = _.extend( 91 | {container: this.container}, 92 | MapOptions.getOptions(this.props), 93 | MapPosition.getOptions(this.props), 94 | MapInteraction.getOptions(this.props) 95 | ) 96 | 97 | // Create map. 98 | const map = new mapboxgl.Map(options) 99 | 100 | // On map load, trigger events and set state. 101 | map.on('load', (...args) => { 102 | if (!this.unmounted) { 103 | if (this.props.onLoad) { 104 | this.props.onLoad(...args) 105 | } 106 | this.setState({map}) 107 | } 108 | }) 109 | 110 | // Optionally handle style.load. 111 | if (this.props.onStyleLoad) { 112 | map.on('style.load', this.props.onStyleLoad) 113 | } 114 | 115 | return map 116 | } 117 | 118 | renderUnsupported () { 119 | return this.props.renderUnsupported ? ( 120 | this.props.renderUnsupported() 121 | ) : ( 122 |
123 | Your browser does not support WebGL-based maps. 124 |
125 | ) 126 | } 127 | 128 | render () { 129 | const {containerStyle, className} = this.props 130 | const {unsupported, map} = this.state 131 | return ( 132 |
{ this.container = x }} 134 | className={`react-mapbox--container ${className || ''}`} 135 | style={containerStyle} 136 | > 137 | {unsupported ? ( 138 | this.renderUnsupported() 139 | ) : map ? ( 140 | 141 | 142 | 143 | 144 | 145 | {this.props.children} 146 | 147 | ) : null} 148 |
149 | ) 150 | } 151 | } 152 | 153 | export default MapGL 154 | -------------------------------------------------------------------------------- /modules/core/src/MapInteraction.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class MapInteraction extends React.PureComponent { 6 | static propTypes = { 7 | interactive: PropTypes.bool, 8 | scrollZoom: PropTypes.bool, 9 | boxZoom: PropTypes.bool, 10 | dragRotate: PropTypes.bool, 11 | dragPan: PropTypes.bool, 12 | keyboard: PropTypes.bool, 13 | doubleClickZoom: PropTypes.bool, 14 | touchZoomRotate: PropTypes.bool 15 | } 16 | 17 | static defaultProps = { 18 | interactive: true, 19 | scrollZoom: true, 20 | boxZoom: true, 21 | dragRotate: true, 22 | dragPan: true, 23 | keyboard: true, 24 | doubleClickZoom: true, 25 | touchZoomRotate: true 26 | } 27 | 28 | static contextTypes = { 29 | map: PropTypes.object 30 | } 31 | 32 | // Called when the map is initally created. 33 | static getOptions (props) { 34 | return _.pick( 35 | _.defaults({}, props, MapInteraction.defaultProps), 36 | _.keys(MapInteraction.propTypes) 37 | ) 38 | } 39 | 40 | componentWillReceiveProps (nextProps) { 41 | const {map} = this.context 42 | const handlers = [ 43 | 'scrollZoom', 44 | 'boxZoom', 45 | 'dragRotate', 46 | 'dragPan', 47 | 'keyboard', 48 | 'doubleClickZoom', 49 | 'touchZoomRotate' 50 | ] 51 | _.each(handlers, (handler) => { 52 | if (this.props[handler] !== nextProps[handler]) { 53 | if (nextProps[handler]) { 54 | map[handler].enable() 55 | } else { 56 | map[handler].disable() 57 | } 58 | } 59 | }) 60 | } 61 | 62 | render () { 63 | return null 64 | } 65 | } 66 | 67 | export default MapInteraction 68 | -------------------------------------------------------------------------------- /modules/core/src/MapOptions.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class MapOptions extends React.PureComponent { 6 | static propTypes = { 7 | style: PropTypes.oneOfType([ 8 | PropTypes.string, 9 | PropTypes.object 10 | ]).isRequired, 11 | hash: PropTypes.bool, 12 | bearingSnap: PropTypes.number, 13 | attributionControl: PropTypes.bool, 14 | logoPosition: PropTypes.string, 15 | preserveDrawingBuffer: PropTypes.bool, 16 | refreshExpiredTiles: PropTypes.bool, 17 | trackResize: PropTypes.bool, 18 | renderWorldCopies: PropTypes.bool, 19 | 20 | // Not options, set with a method. 21 | showTileBoundaries: PropTypes.bool, 22 | showCollisionBoxes: PropTypes.bool, 23 | repaint: PropTypes.bool, 24 | rtlTextPlugin: PropTypes.string 25 | } 26 | 27 | static contextTypes = { 28 | map: PropTypes.object 29 | } 30 | 31 | // Called when the map is initally created. 32 | static getOptions (props) { 33 | return _.omit(_.pick(props, _.keys(MapOptions.propTypes)), [ 34 | 'showTileBoundaries', 35 | 'showCollisionBoxes', 36 | 'repaint', 37 | 'rtlTextPlugin' 38 | ]) 39 | } 40 | 41 | componentDidMount () { 42 | const {map} = this.context 43 | if (!_.isUndefined(this.props.showTileBoundaries)) { 44 | map.showTileBoundaries(this.props.showTileBoundaries) 45 | } 46 | if (!_.isUndefined(this.props.showCollisionBoxes)) { 47 | map.showCollisionBoxes(this.props.showCollisionBoxes) 48 | } 49 | if (!_.isUndefined(this.props.repaint)) { 50 | map.repaint(this.props.repaint) 51 | } 52 | if (!_.isUndefined(this.props.rtlTextPlugin)) { 53 | map.setRTLTextPlugin(this.props.rtlTextPlugin) 54 | } 55 | } 56 | 57 | componentWillReceiveProps (nextProps) { 58 | const {map} = this.context 59 | if (!_.isEqual(this.props.style, nextProps.style)) { 60 | map.setStyle(nextProps.style) 61 | } 62 | if (this.props.showTileBoundaries !== nextProps.showTileBoundaries) { 63 | map.showTileBoundaries(nextProps.showTileBoundaries) 64 | } 65 | if (this.props.showCollisionBoxes !== nextProps.showCollisionBoxes) { 66 | map.showCollisionBoxes(nextProps.showCollisionBoxes) 67 | } 68 | if (this.props.repaint !== nextProps.repaint) { 69 | map.repaint(nextProps.repaint) 70 | } 71 | if (this.props.rtlTextPlugin !== nextProps.rtlTextPlugin) { 72 | map.setRTLTextPlugin(nextProps.rtlTextPlugin) 73 | } 74 | } 75 | 76 | render () { 77 | return null 78 | } 79 | } 80 | 81 | export default MapOptions 82 | -------------------------------------------------------------------------------- /modules/core/src/MapPosition.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class MapPosition extends React.Component { 6 | static propTypes = { 7 | // Map position options. 8 | center: PropTypes.oneOfType([ 9 | PropTypes.arrayOf(PropTypes.number), 10 | PropTypes.object 11 | ]), 12 | zoom: PropTypes.number, 13 | minZoom: PropTypes.number, 14 | maxZoom: PropTypes.number, 15 | maxBounds: PropTypes.oneOfType([ 16 | PropTypes.array, 17 | PropTypes.object 18 | ]), 19 | bearing: PropTypes.number, 20 | pitch: PropTypes.number, 21 | 22 | // Custom position options. 23 | bbox: PropTypes.oneOfType([ 24 | PropTypes.array, 25 | PropTypes.object 26 | ]), 27 | padding: PropTypes.oneOfType([ 28 | PropTypes.number, 29 | PropTypes.object 30 | ]), 31 | moveMethod: PropTypes.oneOf([ 32 | 'jumpTo', 33 | 'easeTo', 34 | 'flyTo' 35 | ]), 36 | moveAround: PropTypes.array, 37 | moveAnimationOptions: PropTypes.object, 38 | moveFlyToOptions: PropTypes.object, 39 | 40 | // The positionRev provides a way to 'hard reset' the map 41 | // position. For example, if you want to reset the position to the 42 | // original props values (without changing the props values). An easy 43 | // way to use this would be to set it to a timestamp. 44 | positionRev: PropTypes.number 45 | } 46 | 47 | static defaultProps = { 48 | // Default map options. 49 | center: [ 50 | -0.2416815, 51 | 51.5285582 52 | ], 53 | zoom: 11, 54 | minZoom: 0, 55 | maxZoom: 20, 56 | bearing: 0, 57 | pitch: 0, 58 | 59 | // Default custom options. 60 | moveMethod: 'flyTo', 61 | moveAnimationOptions: {}, 62 | moveFlyToOptions: {}, 63 | positionRev: 0 64 | } 65 | 66 | static contextTypes = { 67 | map: PropTypes.object 68 | } 69 | 70 | // Called when the map is initally created. 71 | static getOptions (props) { 72 | let pickFrom = _.defaults({}, props, MapPosition.defaultProps) 73 | let picked = _.pick(pickFrom, _.keys(MapPosition.propTypes)) 74 | return _.omit(picked, [ 75 | 'bbox', 76 | 'padding', 77 | 'moveMethod', 78 | 'moveAround', 79 | 'moveAnimationOptions', 80 | 'moveFlyToOptions', 81 | 'positionRev' 82 | ]) 83 | } 84 | 85 | shouldComponentUpdate (nextProps, nextState) { 86 | return ( 87 | !_.isEqual(this.props, nextProps) || 88 | !_.isEqual(this.state, nextState) 89 | ) 90 | } 91 | 92 | componentDidMount () { 93 | let {map} = this.context 94 | if (this.props.bbox) { 95 | map.fitBounds(this.props.bbox, { 96 | padding: this.props.padding || 0, 97 | duration: 0 98 | }) 99 | } 100 | } 101 | 102 | componentWillReceiveProps (nextProps) { 103 | const {map} = this.context 104 | 105 | const didCenterUpdate = !_.isEqual(this.props.center, nextProps.center) 106 | const didZoomUpdate = this.props.zoom !== nextProps.zoom 107 | const didBearingUpdate = this.props.bearing !== nextProps.bearing 108 | const didPitchUpdate = this.props.pitch !== nextProps.pitch 109 | let cameraOptions = null 110 | 111 | // Position props changed. 112 | if (didZoomUpdate || didCenterUpdate || didBearingUpdate || didPitchUpdate) { 113 | cameraOptions = { 114 | center: didCenterUpdate ? nextProps.center : map.getCenter(), 115 | zoom: didZoomUpdate ? nextProps.zoom : map.getZoom(), 116 | bearing: didBearingUpdate ? nextProps.bearing : map.getBearing(), 117 | pitch: didPitchUpdate ? nextProps.pitch : map.getPitch(), 118 | around: nextProps.moveAround 119 | } 120 | } 121 | 122 | // PositionRev changed. 123 | if (this.props.positionRev !== nextProps.positionRev) { 124 | cameraOptions = { 125 | center: !_.isUndefined(nextProps.center) ? nextProps.center : map.getCenter(), 126 | zoom: !_.isUndefined(nextProps.zoom) ? nextProps.zoom : map.getZoom(), 127 | bearing: !_.isUndefined(nextProps.bearing) ? nextProps.bearing : map.getBearing(), 128 | pitch: !_.isUndefined(nextProps.pitch) ? nextProps.pitch : map.getPitch(), 129 | around: nextProps.moveAround 130 | } 131 | } 132 | 133 | if (cameraOptions) { 134 | map[nextProps.moveMethod](_.extend( 135 | cameraOptions, 136 | nextProps.moveMethod !== 'jumpTo' 137 | ? nextProps.moveAnimationOptions 138 | : {}, 139 | nextProps.moveMethod === 'flyTo' 140 | ? nextProps.moveFlyToOptions 141 | : {} 142 | )) 143 | } 144 | 145 | if (!_.isEqual(this.props.bbox, nextProps.bbox)) { 146 | map.fitBounds(nextProps.bbox, _.extend( 147 | { 148 | padding: nextProps.padding || 0, 149 | linear: nextProps.moveMethod !== 'flyTo' 150 | }, 151 | nextProps.moveAnimationOptions 152 | ? nextProps.moveAnimationOptions 153 | : {}, 154 | nextProps.moveMethod === 'flyTo' 155 | ? nextProps.moveFlyToOptions 156 | : {} 157 | )) 158 | } 159 | } 160 | 161 | render () { 162 | return null 163 | } 164 | } 165 | 166 | export default MapPosition 167 | -------------------------------------------------------------------------------- /modules/core/src/MapboxProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import loadCSS from './util/loadCSS' 4 | import loadScript from './util/loadScript' 5 | import pkg from '../package.json' 6 | 7 | class MapboxProvider extends React.PureComponent { 8 | static propTypes = { 9 | // We need an access token. 10 | accessToken: PropTypes.string.isRequired, 11 | 12 | // Either pass in the mapbox-gl module ... 13 | // ... or we'll try to grab mapboxgl from the window ... 14 | // ... or we'll load the js/css from mapbox's CDN. 15 | mapboxgl: PropTypes.object, 16 | js: PropTypes.bool, 17 | css: PropTypes.bool, 18 | cdn: PropTypes.string, 19 | version: PropTypes.string 20 | } 21 | 22 | static defaultProps = { 23 | js: true, 24 | css: true, 25 | cdn: 'https://api.mapbox.com/mapbox-gl-js', 26 | version: pkg.devDependencies['mapbox-gl'].slice(1) 27 | } 28 | 29 | static childContextTypes = { 30 | mapboxgl: PropTypes.object 31 | } 32 | 33 | state = { 34 | mapboxgl: null 35 | } 36 | 37 | getChildContext = () => ({ 38 | mapboxgl: this.state.mapboxgl 39 | }) 40 | 41 | componentDidMount () { 42 | let {accessToken} = this.props 43 | this.loadMapboxGL().then((mapboxgl) => { 44 | if (!this.unmounted) { 45 | mapboxgl.accessToken = accessToken 46 | this.setState({mapboxgl}) 47 | } 48 | }).catch((err) => { 49 | throw err 50 | }) 51 | } 52 | 53 | componentWillUnount () { 54 | this.unmounted = false 55 | } 56 | 57 | loadMapboxGL () { 58 | let {mapboxgl, js, css, version, cdn} = this.props 59 | return new Promise((resolve, reject) => { 60 | if (mapboxgl) { 61 | resolve(mapboxgl) 62 | } else if (window && window.mapboxgl) { 63 | resolve(window.mapboxgl) 64 | } else if (window) { 65 | Promise.all([ 66 | js 67 | ? loadScript(`${cdn}/v${version}/mapbox-gl.js`) 68 | : Promise.resolve(), 69 | css 70 | ? loadCSS(`${cdn}/v${version}/mapbox-gl.css`) 71 | : Promise.resolve() 72 | ]) 73 | .then(() => { 74 | resolve(window.mapboxgl) 75 | }) 76 | .catch((err) => { 77 | reject(err) 78 | }) 79 | } else { 80 | reject(new Error('Cannot load mapbox in a non-browser environment')) 81 | } 82 | }) 83 | } 84 | 85 | render () { 86 | return this.state.mapboxgl ? this.props.children : null 87 | } 88 | } 89 | 90 | export default MapboxProvider 91 | -------------------------------------------------------------------------------- /modules/core/src/Marker.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | import _ from 'lodash' 5 | 6 | class Marker extends React.Component { 7 | static propTypes = { 8 | coordinates: PropTypes.array, 9 | offset: PropTypes.array, 10 | children: PropTypes.node.isRequired 11 | } 12 | 13 | static defaultProps = { 14 | offset: [0, 0] 15 | } 16 | 17 | static contextTypes = { 18 | map: PropTypes.object, 19 | mapboxgl: PropTypes.object 20 | } 21 | 22 | shouldComponentUpdate (nextProps, nextState) { 23 | return ( 24 | !_.isEqual(this.props, nextProps) || 25 | !_.isEqual(this.state, nextState) 26 | ) 27 | } 28 | 29 | componentDidMount () { 30 | // Setup marker div. 31 | this.el = document.createElement('div') 32 | this.el.className = 'react-mapbox--marker' 33 | _.extend(this.el.style, { 34 | position: 'relative', 35 | width: 0, 36 | height: 0, 37 | overflow: 'visible' 38 | }) 39 | 40 | // Render children. 41 | this.renderChildren(this.props) 42 | 43 | // Add marker to map. 44 | this.addMarker(this.props) 45 | } 46 | 47 | componentWillUnmount () { 48 | if (this.component) { 49 | ReactDOM.unmountComponentAtNode(this.el) 50 | this.component = null 51 | } 52 | this.marker.remove() 53 | this.el = null 54 | } 55 | 56 | componentWillReceiveProps (nextProps) { 57 | if (!_.isEqual(this.props.coordinates, nextProps.coordinates)) { 58 | this.marker.setLngLat(nextProps.coordinates) 59 | } 60 | if (!_.isEqual(this.props.offset, nextProps.offset)) { 61 | this.removeMarker() 62 | this.addMarker(nextProps) 63 | } 64 | if (this.props.children !== nextProps.children) { 65 | this.renderChildren(nextProps) 66 | } 67 | } 68 | 69 | addMarker (props) { 70 | let {map, mapboxgl} = this.context 71 | this.marker = new mapboxgl.Marker(this.el, {offset: props.offset}) 72 | if (props.coordinates) { 73 | this.marker.setLngLat(props.coordinates) 74 | } 75 | this.marker.addTo(map) 76 | } 77 | 78 | removeMarker () { 79 | this.marker.remove() 80 | this.marker = null 81 | } 82 | 83 | renderChildren (props) { 84 | if (this.component) { 85 | ReactDOM.unmountComponentAtNode(this.el) 86 | } 87 | this.component = ReactDOM.unstable_renderSubtreeIntoContainer( 88 | this, 89 | props.children, 90 | this.el 91 | ) 92 | } 93 | 94 | render () { 95 | return null 96 | } 97 | } 98 | 99 | export default Marker 100 | -------------------------------------------------------------------------------- /modules/core/src/Popup.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | import _ from 'lodash' 5 | 6 | /** 7 | * Open a mapbox-gl 'native' popup. 8 | * 9 | * Note: It's usually not going to be a good idea to use the `closeButton` or 10 | * `closeOnClick` options, because they can conflict with the mounted state of 11 | * your popup. You should instead 'close' your own component by simply 12 | * unrendering it. If you want a close button, it would be better to render and 13 | * manage it yourself (tied to some state that controls the rendering of the 14 | * Popup). 15 | */ 16 | class Popup extends React.Component { 17 | static propTypes = { 18 | // Popup options. 19 | closeButton: PropTypes.bool, 20 | closeOnClick: PropTypes.bool, 21 | anchor: PropTypes.string, 22 | offset: PropTypes.oneOfType([ 23 | PropTypes.number, 24 | PropTypes.array, 25 | PropTypes.object 26 | ]), 27 | 28 | // Coordinates. 29 | coordinates: PropTypes.array, 30 | 31 | // Only one of the following. 32 | text: PropTypes.string, 33 | html: PropTypes.string, 34 | children: PropTypes.node, 35 | 36 | // Events. 37 | onClose: PropTypes.func 38 | } 39 | 40 | static defaultProps = { 41 | closeButton: false, 42 | closeOnClick: false 43 | } 44 | 45 | static contextTypes = { 46 | map: PropTypes.object, 47 | mapboxgl: PropTypes.object 48 | } 49 | 50 | shouldComponentUpdate (nextProps, nextState) { 51 | return ( 52 | !_.isEqual(this.props, nextProps) || 53 | !_.isEqual(this.state, nextState) 54 | ) 55 | } 56 | 57 | componentDidMount () { 58 | // Setup popup div. 59 | this.el = document.createElement('div') 60 | this.el.className = 'react-mapbox--popup' 61 | _.extend(this.el.style, { 62 | position: 'relative', 63 | display: 'inline-block' 64 | }) 65 | 66 | // Add popup to map. 67 | this.addPopup(this.props) 68 | } 69 | 70 | componentWillUnmount () { 71 | if (this.component) { 72 | ReactDOM.unmountComponentAtNode(this.el) 73 | this.component = null 74 | } 75 | this.removePopup() 76 | this.el = null 77 | } 78 | 79 | componentWillReceiveProps (nextProps) { 80 | // For options or content, we need to make a new popup. 81 | if ( 82 | !_.isEqual(this.getOptions(this.props), this.getOptions(nextProps)) || 83 | !_.isEqual(this.props.html, nextProps.html) || 84 | !_.isEqual(this.props.text, nextProps.text) || 85 | !_.isEqual(this.props.onClose, nextProps.onClose) 86 | ) { 87 | this.removePopup() 88 | this.addPopup(nextProps) 89 | return 90 | } 91 | 92 | // Otherwise update the current popup. 93 | if (!_.isEqual(this.props.coordinates, nextProps.coordinates)) { 94 | this.popup.setLngLat(nextProps.coordinates) 95 | } 96 | if (this.props.children !== nextProps.children) { 97 | this.renderChildren(nextProps) 98 | } 99 | } 100 | 101 | getOptions (props) { 102 | return _.omitBy(_.pick(props, [ 103 | 'closeButton', 104 | 'closeOnClick', 105 | 'anchor', 106 | 'offset' 107 | ]), _.isNil) 108 | } 109 | 110 | addPopup (props) { 111 | let {map, mapboxgl} = this.context 112 | this.popup = new mapboxgl.Popup(this.getOptions(props)) 113 | if (this.props.onClose) { 114 | this.popup.on('close', this.props.onClose) 115 | } 116 | if (props.coordinates) { 117 | this.popup.setLngLat(props.coordinates) 118 | } 119 | if (props.html) { 120 | this.popup.setHTML(props.html) 121 | } 122 | if (props.text) { 123 | this.popup.setText(props.text) 124 | } 125 | if (props.children) { 126 | this.renderChildren(props) 127 | this.popup.setDOMContent(this.el) 128 | } 129 | this.popup.addTo(map) 130 | } 131 | 132 | removePopup () { 133 | if (this.popup.isOpen()) { 134 | this.popup.off('close') 135 | this.popup.remove() 136 | } 137 | this.popup = null 138 | } 139 | 140 | renderChildren (props) { 141 | if (this.component) { 142 | ReactDOM.unmountComponentAtNode(this.el) 143 | } 144 | if (props.children) { 145 | this.component = ReactDOM.unstable_renderSubtreeIntoContainer( 146 | this, 147 | props.children, 148 | this.el 149 | ) 150 | } 151 | } 152 | 153 | render () { 154 | return null 155 | } 156 | } 157 | 158 | export default Popup 159 | -------------------------------------------------------------------------------- /modules/core/src/Source.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | 5 | class Source extends React.Component { 6 | static propTypes = { 7 | id: PropTypes.string.isRequired, 8 | type: PropTypes.oneOf([ 9 | 'canvas', 10 | 'geojson', 11 | 'image', 12 | 'raster', 13 | 'vector', 14 | 'video' 15 | ]).isRequired, 16 | data: PropTypes.oneOfType([ 17 | PropTypes.string, 18 | PropTypes.object 19 | ]), 20 | dataRev: PropTypes.number, 21 | url: PropTypes.string, 22 | tiles: PropTypes.array, 23 | tileSize: PropTypes.number, 24 | minzoom: PropTypes.number, 25 | maxzoom: PropTypes.number, 26 | coordinates: PropTypes.array, 27 | buffer: PropTypes.number, 28 | tolerance: PropTypes.number, 29 | cluster: PropTypes.bool, 30 | clusterRadius: PropTypes.number, 31 | clusterMaxZoom: PropTypes.number, 32 | canvas: PropTypes.string, 33 | animate: PropTypes.bool 34 | } 35 | 36 | static contextTypes = { 37 | map: PropTypes.object 38 | } 39 | 40 | shouldComponentUpdate (nextProps, nextState) { 41 | return ( 42 | !_.isEqual(this.props, nextProps) || 43 | !_.isEqual(this.state, nextState) 44 | ) 45 | } 46 | 47 | componentDidMount () { 48 | this.addSource(this.props) 49 | } 50 | 51 | componentWillUnmount () { 52 | this.removeSource(this.props) 53 | } 54 | 55 | componentWillReceiveProps (nextProps) { 56 | let {map} = this.context 57 | 58 | // If any of these props change, we have to just recreate the 59 | // source from scratch. 60 | let invalidate = [ 61 | 'id', 62 | 'type', 63 | 'url', 64 | 'tiles', 65 | 'tileSize', 66 | 'minzoom', 67 | 'maxzoom', 68 | 'buffer', 69 | 'tolerance', 70 | 'cluster', 71 | 'clusterRadius', 72 | 'clusterMaxZoom', 73 | 'canvas', 74 | 'animate' 75 | ] 76 | if (!_.isEqual( 77 | _.pick(this.props, invalidate), 78 | _.pick(nextProps, invalidate) 79 | )) { 80 | this.removeSource(this.props) 81 | this.addSource(nextProps) 82 | return 83 | } 84 | 85 | // The coordinates changed. 86 | if (!_.isEqual(this.props.coordinates, nextProps.coordinates)) { 87 | map.getSource(nextProps.id).setCoordinates(nextProps.coordinates) 88 | } 89 | 90 | // The data changed. 91 | if (!_.isEqual(this.props.data, nextProps.data)) { 92 | map.getSource(nextProps.id).setData(nextProps.data) 93 | } 94 | 95 | // The dataRev changed (perhaps a timestamp). Use this to 'refresh' 96 | // an external geojson source. 97 | if (this.props.dataRev !== nextProps.dataRev) { 98 | map.getSource(nextProps.id).setData(nextProps.data) 99 | } 100 | } 101 | 102 | addSource (props) { 103 | let {map} = this.context 104 | let options = {} 105 | 106 | // Grab basic options from props. 107 | _.extend(options, _.omitBy(_.pick(props, [ 108 | 'type', 109 | 'data', 110 | 'url', 111 | 'tiles', 112 | 'tileSize', 113 | 'minzoom', 114 | 'maxzoom', 115 | 'coordinates', 116 | 'buffer', 117 | 'tolerance', 118 | 'cluster', 119 | 'clusterRadius', 120 | 'clusterMaxZoom', 121 | 'canvas', 122 | 'animate' 123 | ]), _.isNil)) 124 | 125 | // Add the source. 126 | map.addSource(props.id, options) 127 | map.fire('_addSource', props.id) 128 | } 129 | 130 | removeSource (props) { 131 | let {map} = this.context 132 | map.removeSource(props.id) 133 | map.fire('_removeSource', props.id) 134 | } 135 | 136 | render () { 137 | return null 138 | } 139 | } 140 | 141 | export default Source 142 | -------------------------------------------------------------------------------- /modules/core/src/index.js: -------------------------------------------------------------------------------- 1 | export {default as Children} from './Children' 2 | export {default as Control} from './Control' 3 | export {default as Image} from './Image' 4 | export {default as Layer} from './Layer' 5 | export {default as LayerEvent} from './LayerEvent' 6 | export {default as LayerEvents} from './LayerEvents' 7 | export {default as LoadImages} from './LoadImages' 8 | export {default as MapboxProvider} from './MapboxProvider' 9 | export {default as MapEvent} from './MapEvent' 10 | export {default as MapEvents} from './MapEvents' 11 | export {default as MapGL} from './MapGL' 12 | export {default as MapInteraction} from './MapInteraction' 13 | export {default as MapOptions} from './MapOptions' 14 | export {default as MapPosition} from './MapPosition' 15 | export {default as Marker} from './Marker' 16 | export {default as Popup} from './Popup' 17 | export {default as Source} from './Source' 18 | -------------------------------------------------------------------------------- /modules/core/src/stories/about.story.docs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - About 3 | * 4 | * ## About the Examples 5 | * 6 | */ 7 | 8 | /** 9 | * The examples are all wrapped in the required MapboxProvider. You need to 10 | * do so in your own App as well. 11 | */ 12 | import React from 'react' 13 | import ReactDOM from 'react-dom' 14 | import {MapboxProvider} from '@react-mapboxgl/core' 15 | 16 | ReactDOM.render(( 17 | 18 | 19 | {/* ... the rest of your app */} 20 | 21 | 22 | ), document.getElementById('root')) 23 | 24 | 25 | /** 26 | * The default options fed into most of the maps are: 27 | */ 28 | const defaults = { 29 | style: 'mapbox://styles/mapbox/streets-v9', 30 | bbox: [[-123.881836, 25.063209], [-65.170898, 48.848451]], 31 | center: [-95.844727, 39.620499], 32 | zoom: 3, 33 | padding: 30, 34 | containerStyle: { 35 | position: 'fixed', 36 | top: 0, 37 | left: 0, 38 | right: 0, 39 | bottom: 0, 40 | zIndex: 1 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/core/src/stories/assets/giants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/modules/core/src/stories/assets/giants.png -------------------------------------------------------------------------------- /modules/core/src/stories/assets/green-pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/modules/core/src/stories/assets/green-pattern.jpg -------------------------------------------------------------------------------- /modules/core/src/stories/assets/nats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/modules/core/src/stories/assets/nats.png -------------------------------------------------------------------------------- /modules/core/src/stories/assets/orange-pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerraEclipse/react-mapboxgl/373e618fa9a32a262ac81adf1b8b12e986d305d4/modules/core/src/stories/assets/orange-pattern.jpg -------------------------------------------------------------------------------- /modules/core/src/stories/basic.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Basic 3 | * 4 | * ## Basic Map with Features 5 | * Render a map with geojson features. 6 | */ 7 | import React from 'react' 8 | import {mapDefaults} from '@react-mapboxgl/docs' 9 | import {MapGL, Layer} from '../' 10 | 11 | const Story = () => ( 12 | 13 | 52 | 53 | ) 54 | 55 | export default Story 56 | -------------------------------------------------------------------------------- /modules/core/src/stories/controls.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Controls 3 | * 4 | * ## Map with Controls 5 | * An example map with all of the possible mapbox-gl-js controls added. 6 | */ 7 | import React from 'react' 8 | import {mapDefaults} from '@react-mapboxgl/docs' 9 | import {MapGL, Control} from '../' 10 | 11 | const Story = () => ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | 20 | export default Story -------------------------------------------------------------------------------- /modules/core/src/stories/events.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Events 3 | * 4 | * ## Map Events 5 | * Toggle any of the possible map events to start logging them. 6 | */ 7 | import React from 'react' 8 | import _ from 'lodash' 9 | import {action} from '@storybook/addon-actions' 10 | import {mapDefaults, sanitizeMapEvent, Options, Checkbox} from '@react-mapboxgl/docs' 11 | import {MapGL, MapEvents} from '../' 12 | 13 | const eventHandlers = _.mapValues(MapEvents.propTypes, (t, name) => { 14 | return (e) => { 15 | action(name)(sanitizeMapEvent(e)) 16 | console.log(e) 17 | } 18 | }) 19 | 20 | class Story extends React.Component { 21 | state = {} 22 | render () { 23 | return ( 24 |
25 | { 28 | return this.state[name] 29 | })} 30 | /> 31 | 32 | {_.map(eventHandlers, (_, name) => ( 33 | { 37 | this.setState({[name]: e.currentTarget.checked}) 38 | }} 39 | checked={this.state[name] || false} 40 | /> 41 | ))} 42 | 43 |
44 | ) 45 | } 46 | } 47 | 48 | export default Story 49 | -------------------------------------------------------------------------------- /modules/core/src/stories/images.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Images 3 | * 4 | * ## Load and Use Custom Images 5 | * Loads images into the map sprite (limited size), then use them in 6 | * layer paint options. Assumes a webpack setup with `url-loader` for images. 7 | */ 8 | import React from 'react' 9 | import {mapDefaults, Overlay} from '@react-mapboxgl/docs' 10 | import {MapGL, LoadImages, Layer} from '../' 11 | 12 | class Story extends React.Component { 13 | state = { 14 | pattern: 'greenPattern' 15 | } 16 | 17 | constructor () { 18 | super() 19 | this.handleClick = this.handleClick.bind(this) 20 | } 21 | 22 | handleClick () { 23 | this.setState((state) => { 24 | state.pattern = (state.pattern === 'greenPattern') 25 | ? 'orangePattern' 26 | : 'greenPattern' 27 | return state 28 | }) 29 | } 30 | 31 | render () { 32 | return ( 33 | 34 | 40 | 75 | 114 | 115 | 116 | 117 | 118 | 119 | ) 120 | } 121 | } 122 | 123 | export default Story 124 | -------------------------------------------------------------------------------- /modules/core/src/stories/interaction-handlers.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Interaction Handlers 3 | * 4 | * ## Turn Map Interaction Handlers On/Off 5 | * Toggle any of the map interaction handlers. 6 | */ 7 | import React from 'react' 8 | import _ from 'lodash' 9 | import {action} from '@storybook/addon-actions' 10 | import {mapDefaults, Checkbox, Options} from '@react-mapboxgl/docs' 11 | import {MapGL} from '../' 12 | 13 | class Story extends React.Component { 14 | state = { 15 | scrollZoom: true, 16 | boxZoom: true, 17 | dragRotate: true, 18 | dragPan: true, 19 | keyboard: true, 20 | doubleClickZoom: true, 21 | touchZoomRotate: true 22 | } 23 | render () { 24 | return ( 25 |
26 | 30 | 31 | {_.map(this.state, (checked, name) => ( 32 | { 36 | action(name)(e.currentTarget.checked ? 'Enabled' : 'Disabled') 37 | this.setState({[name]: e.currentTarget.checked}) 38 | }} 39 | checked={checked} 40 | /> 41 | ))} 42 | 43 |
44 | ) 45 | } 46 | } 47 | 48 | export default Story 49 | -------------------------------------------------------------------------------- /modules/core/src/stories/marker.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Markers 3 | * 4 | * ## Add Markers to a Map 5 | * While not as performant as Layers for large data-sets, markers make 6 | * it easy to add arbitrary HTML to the map. 7 | */ 8 | import React from 'react' 9 | import {action} from '@storybook/addon-actions' 10 | import {mapDefaults, Overlay} from '@react-mapboxgl/docs' 11 | import {MapGL, Marker} from '../' 12 | 13 | const geojson = { 14 | 'type': 'FeatureCollection', 15 | 'features': [ 16 | { 17 | 'type': 'Feature', 18 | 'properties': { 19 | 'message': 'Foo', 20 | 'iconSize': [60, 60] 21 | }, 22 | 'geometry': { 23 | 'type': 'Point', 24 | 'coordinates': [ 25 | -66.324462890625, 26 | -16.024695711685304 27 | ] 28 | } 29 | }, 30 | { 31 | 'type': 'Feature', 32 | 'properties': { 33 | 'message': 'Bar', 34 | 'iconSize': [50, 50] 35 | }, 36 | 'geometry': { 37 | 'type': 'Point', 38 | 'coordinates': [ 39 | -61.2158203125, 40 | -15.97189158092897 41 | ] 42 | } 43 | }, 44 | { 45 | 'type': 'Feature', 46 | 'properties': { 47 | 'message': 'Baz', 48 | 'iconSize': [40, 40] 49 | }, 50 | 'geometry': { 51 | 'type': 'Point', 52 | 'coordinates': [ 53 | -63.29223632812499, 54 | -18.28151823530889 55 | ] 56 | } 57 | } 58 | ] 59 | } 60 | 61 | class Story extends React.Component { 62 | state = { 63 | size: 1 64 | } 65 | render () { 66 | let {size} = this.state 67 | return ( 68 | 74 | {geojson.features.map((feature, i) => { 75 | let [x, y] = feature.properties.iconSize 76 | return ( 77 | 78 |
action('click')(feature.properties.message)} 90 | /> 91 | 92 | ) 93 | })} 94 | 95 | Icons: 96 | 102 | 105 | 106 | 107 | ) 108 | } 109 | } 110 | 111 | export default Story 112 | -------------------------------------------------------------------------------- /modules/core/src/stories/popup.story.css: -------------------------------------------------------------------------------- 1 | .mapboxgl-popup { 2 | max-width: 400px; 3 | } -------------------------------------------------------------------------------- /modules/core/src/stories/popup.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core - Popups 3 | * 4 | * ## Add Popups to A Map 5 | * Adds text, html, and react children Popups to the map. 6 | */ 7 | import './popup.story.css' 8 | import React from 'react' 9 | import {mapDefaults, Options, Checkbox} from '@react-mapboxgl/docs' 10 | import {MapGL, Layer, Popup} from '../' 11 | 12 | const geojson = { 13 | 'type': 'FeatureCollection', 14 | 'features': [{ 15 | // Example of using text for content. 16 | 'type': 'Feature', 17 | 'properties': { 18 | 'id': 0, 19 | 'name': 'Make it Mount Pleasant (Text)', 20 | 'icon': 'theatre', 21 | 'text': 'Make it Mount Pleasant is a handmade and vintage market and afternoon of live entertainment and kids activities. 12:00-6:00 p.m.' 22 | }, 23 | 'geometry': { 24 | 'type': 'Point', 25 | 'coordinates': [-77.038659, 38.931567] 26 | } 27 | }, { 28 | // Example of using html for content. 29 | 'type': 'Feature', 30 | 'properties': { 31 | 'id': 1, 32 | 'name': 'Capital Pride Parade (HTML)', 33 | 'icon': 'star', 34 | 'html': `Capital Pride Parade

The annual Capital Pride Parade makes its way through Dupont this Saturday. 4:30 p.m. Free.

` 35 | }, 36 | 'geometry': { 37 | 'type': 'Point', 38 | 'coordinates': [-77.043444, 38.909664] 39 | } 40 | }, { 41 | // Example of a popup using children react components. 42 | 'type': 'Feature', 43 | 'properties': { 44 | 'id': 2, 45 | 'name': 'Big Backyard Bash (Children)', 46 | 'icon': 'bar', 47 | 'children': ( 48 |
49 | Big Backyard Beach Bash and Wine Fest 50 |

51 | EatBar (2761 Washington Boulevard Arlington VA) is throwing 52 | a Big Backyard Beach Bash and Wine Fest on Saturday, 57 | serving up conch fritters, fish tacos and crab sliders, and 58 | Red Apron hot dogs. 12:00-3:00 p.m. $25.grill hot dogs. 59 |

60 |
61 | ) 62 | }, 63 | 'geometry': { 64 | 'type': 'Point', 65 | 'coordinates': [-77.090372, 38.881189] 66 | } 67 | }] 68 | } 69 | 70 | class Story extends React.Component { 71 | state = {} 72 | render () { 73 | return ( 74 |
75 | 81 | 93 | 94 | {/* Render the popups if checked */} 95 | {geojson.features.map((feature) => { 96 | return this.state[feature.properties.name] ? ( 97 | 103 | {feature.properties.children || null} 104 | 105 | ) : null 106 | })} 107 | 108 | 109 | {geojson.features.map((feature) => ( 110 | { 114 | this.setState({[feature.properties.name]: e.currentTarget.checked}) 115 | }} 116 | checked={this.state[feature.properties.name]} 117 | style={{flex: 1}} 118 | /> 119 | ))} 120 | 121 |
122 | ) 123 | } 124 | } 125 | 126 | export default Story 127 | -------------------------------------------------------------------------------- /modules/core/src/util/__snapshots__/diff.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`diff can diff two objects 1`] = ` 4 | Array [ 5 | Object { 6 | "key": "favorite", 7 | "type": "remove", 8 | "value": true, 9 | }, 10 | Object { 11 | "key": "vegetarian", 12 | "type": "add", 13 | "value": false, 14 | }, 15 | Object { 16 | "key": "name", 17 | "type": "update", 18 | "value": "bacon quiche", 19 | }, 20 | Object { 21 | "key": "ingredients", 22 | "type": "update", 23 | "value": Array [ 24 | "eggs", 25 | "cream", 26 | "cheese", 27 | "bacon", 28 | ], 29 | }, 30 | Object { 31 | "key": "status", 32 | "type": "update", 33 | "value": Object { 34 | "ate": true, 35 | "baked": true, 36 | "prepared": true, 37 | }, 38 | }, 39 | ] 40 | `; 41 | -------------------------------------------------------------------------------- /modules/core/src/util/diff.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | /** 4 | * Produces a shallow diff of an object. Output is an array of 5 | * 'changes' where a change is either an 'add', 'remove', or 'update'. 6 | */ 7 | export default function diff (a, b) { 8 | // Throw is a or b are not objects. 9 | if (!_.isPlainObject(a)) { 10 | throw new Error('First parameter to diff() is not an object') 11 | } 12 | if (!_.isPlainObject(b)) { 13 | throw new Error('Second parameter to diff() is not an object') 14 | } 15 | 16 | let changes = [] 17 | let keysA = _.keys(a) 18 | let keysB = _.keys(b) 19 | 20 | // Find the items in A that are not in B. 21 | _.each(_.difference(keysA, keysB), (key) => { 22 | changes.push({type: 'remove', key: key, value: a[key]}) 23 | }) 24 | 25 | // Find the items in B that are not in A. 26 | _.each(_.difference(keysB, keysA), (key) => { 27 | changes.push({type: 'add', key: key, value: b[key]}) 28 | }) 29 | 30 | // Find the items that are in both, but have changed. 31 | _.each(_.intersection(keysA, keysB), (key) => { 32 | if (!_.isEqual(a[key], b[key])) { 33 | changes.push({type: 'update', key: key, value: b[key]}) 34 | } 35 | }) 36 | 37 | return changes 38 | } 39 | -------------------------------------------------------------------------------- /modules/core/src/util/diff.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import diff from './diff' 3 | 4 | describe('diff', () => { 5 | it('can diff two objects', () => { 6 | var a = { 7 | meal: 'dinner', 8 | name: 'quiche', 9 | ingredients: [ 10 | 'eggs', 11 | 'cream', 12 | 'cheese' 13 | ], 14 | status: { 15 | prepared: true, 16 | baked: false, 17 | ate: false 18 | }, 19 | favorite: true 20 | } 21 | var b = { 22 | meal: 'dinner', 23 | name: 'bacon quiche', 24 | ingredients: [ 25 | 'eggs', 26 | 'cream', 27 | 'cheese', 28 | 'bacon' 29 | ], 30 | status: { 31 | prepared: true, 32 | baked: true, 33 | ate: true 34 | }, 35 | vegetarian: false 36 | } 37 | var changes = diff(a, b) 38 | expect(changes).toMatchSnapshot() 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /modules/core/src/util/loadCSS.js: -------------------------------------------------------------------------------- 1 | export default function loadCSS (url) { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | var style = document.createElement('style') 5 | style.textContent = '@import "' + url + '"' 6 | 7 | var fi = setInterval(function () { 8 | try { 9 | // Only populated when file is loaded 10 | if (style.sheet.cssRules) { 11 | clearInterval(fi) 12 | resolve() 13 | } 14 | } catch (e) {} 15 | }, 10) 16 | 17 | document.head.appendChild(style) 18 | } catch (err) { 19 | reject(err) 20 | } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /modules/core/src/util/loadScript.js: -------------------------------------------------------------------------------- 1 | export default function loadScript (src) { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | var script = document.createElement('script') 5 | script.src = src 6 | script.onload = resolve 7 | script.onerror = reject 8 | document.head.appendChild(script) 9 | } catch (err) { 10 | reject(err) 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /modules/docs/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Terra Eclipse, Inc. 2 | http://www.terraeclipse.com/ 3 | 4 | Copyright 2017 Brian Link 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 19 | ------------------------------------------------------------------------- 20 | Apache License 21 | Version 2.0, January 2004 22 | http://www.apache.org/licenses/ 23 | 24 | 25 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 26 | 27 | 1. Definitions. 28 | 29 | "License" shall mean the terms and conditions for use, reproduction, 30 | and distribution as defined by Sections 1 through 9 of this document. 31 | 32 | "Licensor" shall mean the copyright owner or entity authorized by 33 | the copyright owner that is granting the License. 34 | 35 | "Legal Entity" shall mean the union of the acting entity and all 36 | other entities that control, are controlled by, or are under common 37 | control with that entity. For the purposes of this definition, 38 | "control" means (i) the power, direct or indirect, to cause the 39 | direction or management of such entity, whether by contract or 40 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 41 | outstanding shares, or (iii) beneficial ownership of such entity. 42 | 43 | "You" (or "Your") shall mean an individual or Legal Entity 44 | exercising permissions granted by this License. 45 | 46 | "Source" form shall mean the preferred form for making modifications, 47 | including but not limited to software source code, documentation 48 | source, and configuration files. 49 | 50 | "Object" form shall mean any form resulting from mechanical 51 | transformation or translation of a Source form, including but 52 | not limited to compiled object code, generated documentation, 53 | and conversions to other media types. 54 | 55 | "Work" shall mean the work of authorship, whether in Source or 56 | Object form, made available under the License, as indicated by a 57 | copyright notice that is included in or attached to the work 58 | (an example is provided in the Appendix below). 59 | 60 | "Derivative Works" shall mean any work, whether in Source or Object 61 | form, that is based on (or derived from) the Work and for which the 62 | editorial revisions, annotations, elaborations, or other modifications 63 | represent, as a whole, an original work of authorship. For the purposes 64 | of this License, Derivative Works shall not include works that remain 65 | separable from, or merely link (or bind by name) to the interfaces of, 66 | the Work and Derivative Works thereof. 67 | 68 | "Contribution" shall mean any work of authorship, including 69 | the original version of the Work and any modifications or additions 70 | to that Work or Derivative Works thereof, that is intentionally 71 | submitted to Licensor for inclusion in the Work by the copyright owner 72 | or by an individual or Legal Entity authorized to submit on behalf of 73 | the copyright owner. For the purposes of this definition, "submitted" 74 | means any form of electronic, verbal, or written communication sent 75 | to the Licensor or its representatives, including but not limited to 76 | communication on electronic mailing lists, source code control systems, 77 | and issue tracking systems that are managed by, or on behalf of, the 78 | Licensor for the purpose of discussing and improving the Work, but 79 | excluding communication that is conspicuously marked or otherwise 80 | designated in writing by the copyright owner as "Not a Contribution." 81 | 82 | "Contributor" shall mean Licensor and any individual or Legal Entity 83 | on behalf of whom a Contribution has been received by Licensor and 84 | subsequently incorporated within the Work. 85 | 86 | 2. Grant of Copyright License. Subject to the terms and conditions of 87 | this License, each Contributor hereby grants to You a perpetual, 88 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 89 | copyright license to reproduce, prepare Derivative Works of, 90 | publicly display, publicly perform, sublicense, and distribute the 91 | Work and such Derivative Works in Source or Object form. 92 | 93 | 3. Grant of Patent License. Subject to the terms and conditions of 94 | this License, each Contributor hereby grants to You a perpetual, 95 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 96 | (except as stated in this section) patent license to make, have made, 97 | use, offer to sell, sell, import, and otherwise transfer the Work, 98 | where such license applies only to those patent claims licensable 99 | by such Contributor that are necessarily infringed by their 100 | Contribution(s) alone or by combination of their Contribution(s) 101 | with the Work to which such Contribution(s) was submitted. If You 102 | institute patent litigation against any entity (including a 103 | cross-claim or counterclaim in a lawsuit) alleging that the Work 104 | or a Contribution incorporated within the Work constitutes direct 105 | or contributory patent infringement, then any patent licenses 106 | granted to You under this License for that Work shall terminate 107 | as of the date such litigation is filed. 108 | 109 | 4. Redistribution. You may reproduce and distribute copies of the 110 | Work or Derivative Works thereof in any medium, with or without 111 | modifications, and in Source or Object form, provided that You 112 | meet the following conditions: 113 | 114 | (a) You must give any other recipients of the Work or 115 | Derivative Works a copy of this License; and 116 | 117 | (b) You must cause any modified files to carry prominent notices 118 | stating that You changed the files; and 119 | 120 | (c) You must retain, in the Source form of any Derivative Works 121 | that You distribute, all copyright, patent, trademark, and 122 | attribution notices from the Source form of the Work, 123 | excluding those notices that do not pertain to any part of 124 | the Derivative Works; and 125 | 126 | (d) If the Work includes a "NOTICE" text file as part of its 127 | distribution, then any Derivative Works that You distribute must 128 | include a readable copy of the attribution notices contained 129 | within such NOTICE file, excluding those notices that do not 130 | pertain to any part of the Derivative Works, in at least one 131 | of the following places: within a NOTICE text file distributed 132 | as part of the Derivative Works; within the Source form or 133 | documentation, if provided along with the Derivative Works; or, 134 | within a display generated by the Derivative Works, if and 135 | wherever such third-party notices normally appear. The contents 136 | of the NOTICE file are for informational purposes only and 137 | do not modify the License. You may add Your own attribution 138 | notices within Derivative Works that You distribute, alongside 139 | or as an addendum to the NOTICE text from the Work, provided 140 | that such additional attribution notices cannot be construed 141 | as modifying the License. 142 | 143 | You may add Your own copyright statement to Your modifications and 144 | may provide additional or different license terms and conditions 145 | for use, reproduction, or distribution of Your modifications, or 146 | for any such Derivative Works as a whole, provided Your use, 147 | reproduction, and distribution of the Work otherwise complies with 148 | the conditions stated in this License. 149 | 150 | 5. Submission of Contributions. Unless You explicitly state otherwise, 151 | any Contribution intentionally submitted for inclusion in the Work 152 | by You to the Licensor shall be under the terms and conditions of 153 | this License, without any additional terms or conditions. 154 | Notwithstanding the above, nothing herein shall supersede or modify 155 | the terms of any separate license agreement you may have executed 156 | with Licensor regarding such Contributions. 157 | 158 | 6. Trademarks. This License does not grant permission to use the trade 159 | names, trademarks, service marks, or product names of the Licensor, 160 | except as required for reasonable and customary use in describing the 161 | origin of the Work and reproducing the content of the NOTICE file. 162 | 163 | 7. Disclaimer of Warranty. Unless required by applicable law or 164 | agreed to in writing, Licensor provides the Work (and each 165 | Contributor provides its Contributions) on an "AS IS" BASIS, 166 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 167 | implied, including, without limitation, any warranties or conditions 168 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 169 | PARTICULAR PURPOSE. You are solely responsible for determining the 170 | appropriateness of using or redistributing the Work and assume any 171 | risks associated with Your exercise of permissions under this License. 172 | 173 | 8. Limitation of Liability. In no event and under no legal theory, 174 | whether in tort (including negligence), contract, or otherwise, 175 | unless required by applicable law (such as deliberate and grossly 176 | negligent acts) or agreed to in writing, shall any Contributor be 177 | liable to You for damages, including any direct, indirect, special, 178 | incidental, or consequential damages of any character arising as a 179 | result of this License or out of the use or inability to use the 180 | Work (including but not limited to damages for loss of goodwill, 181 | work stoppage, computer failure or malfunction, or any and all 182 | other commercial damages or losses), even if such Contributor 183 | has been advised of the possibility of such damages. 184 | 185 | 9. Accepting Warranty or Additional Liability. While redistributing 186 | the Work or Derivative Works thereof, You may choose to offer, 187 | and charge a fee for, acceptance of support, warranty, indemnity, 188 | or other liability obligations and/or rights consistent with this 189 | License. However, in accepting such obligations, You may act only 190 | on Your own behalf and on Your sole responsibility, not on behalf 191 | of any other Contributor, and only if You agree to indemnify, 192 | defend, and hold each Contributor harmless for any liability 193 | incurred by, or claims asserted against, such Contributor by reason 194 | of your accepting any such warranty or additional liability. 195 | 196 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /modules/docs/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/docs 2 | ==================== 3 | 4 | This module provides documentation helpers for `@react-mapboxgl`. Please see 5 | the [main README](https://github.com/TerraEclipse/react-mapboxgl) or the [documentation](https://terraeclipse.github.io/react-mapboxgl). 6 | 7 | - - - 8 | 9 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 10 | 11 | Terra Eclipse, Inc. is a nationally recognized political technology and 12 | strategy firm located in Santa Cruz, CA and Washington, D.C. 13 | -------------------------------------------------------------------------------- /modules/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/docs", 3 | "version": "2.1.0", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Documentation (storybook) helpers for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "peerDependencies": { 25 | "lodash": "^4.17.4", 26 | "prop-types": "^15.5.10", 27 | "react": "^15.5.4" 28 | }, 29 | "devDependencies": { 30 | "lodash": "^4.17.4", 31 | "prop-types": "^15.5.10", 32 | "react": "^15.5.4" 33 | }, 34 | "author": "Brian Link", 35 | "license": "Apache-2.0", 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 42 | }, 43 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/docs#readme", 44 | "keywords": [ 45 | "react-component" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /modules/docs/src/components/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Checkbox (props) { 4 | return ( 5 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /modules/docs/src/components/Options.css: -------------------------------------------------------------------------------- 1 | .storybook-options { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | background: #555; 6 | margin: 20px -20px -20px -20px !important; 7 | } 8 | .storybook-options > * { 9 | flex: 0 0 25%; 10 | font-size: 0.85em; 11 | padding: 7px; 12 | border: 2px solid rgba(0, 0, 0, 0.1); 13 | background: rgba(0, 0, 0, 0.1); 14 | } -------------------------------------------------------------------------------- /modules/docs/src/components/Options.js: -------------------------------------------------------------------------------- 1 | import './Options.css' 2 | import React from 'react' 3 | 4 | export default function Options (props) { 5 | return ( 6 |
7 | {props.children} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /modules/docs/src/components/Overlay.css: -------------------------------------------------------------------------------- 1 | .storybook-overlay { 2 | position: absolute; 3 | top: 10px; 4 | left: 10px; 5 | padding: 8px 10px; 6 | background-color: rgba(0, 0, 0, 0.7); 7 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2); 8 | z-index: 10; 9 | color: #fff; 10 | } 11 | .storybook-overlay button { 12 | margin-left: 10px; 13 | } 14 | .storybook-overlay button:first-child { 15 | margin-left: 0; 16 | } -------------------------------------------------------------------------------- /modules/docs/src/components/Overlay.js: -------------------------------------------------------------------------------- 1 | import './Overlay.css' 2 | import React from 'react' 3 | 4 | export default function Overlay (props) { 5 | return ( 6 |
7 | {props.children} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /modules/docs/src/index.js: -------------------------------------------------------------------------------- 1 | export {default as Checkbox} from './components/Checkbox' 2 | export {default as Options} from './components/Options' 3 | export {default as Overlay} from './components/Overlay' 4 | export {default as mapDefaults} from './util/mapDefaults' 5 | export {default as sanitizeMapEvent} from './util/sanitizeMapEvent' 6 | -------------------------------------------------------------------------------- /modules/docs/src/util/mapDefaults.js: -------------------------------------------------------------------------------- 1 | export default { 2 | style: 'mapbox://styles/mapbox/streets-v9', 3 | bbox: [[-123.881836, 25.063209], [-65.170898, 48.848451]], 4 | center: [-95.844727, 39.620499], 5 | zoom: 3, 6 | padding: 30, 7 | containerStyle: { 8 | position: 'fixed', 9 | top: 0, 10 | left: 0, 11 | right: 0, 12 | bottom: 0, 13 | zIndex: 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modules/docs/src/util/sanitizeMapEvent.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | export default function sanitizeMapEvent (e) { 4 | return _.mapValues(e, (val, key) => { 5 | switch (key) { 6 | case 'originalEvent': 7 | case 'target': 8 | return `[${key}]` 9 | default: 10 | return val 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /modules/docs/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.5" 7 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 8 | 9 | core-js@^1.0.0: 10 | version "1.2.7" 11 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 12 | 13 | encoding@^0.1.11: 14 | version "0.1.12" 15 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 16 | dependencies: 17 | iconv-lite "~0.4.13" 18 | 19 | fbjs@^0.8.9: 20 | version "0.8.12" 21 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 22 | dependencies: 23 | core-js "^1.0.0" 24 | isomorphic-fetch "^2.1.1" 25 | loose-envify "^1.0.0" 26 | object-assign "^4.1.0" 27 | promise "^7.1.1" 28 | setimmediate "^1.0.5" 29 | ua-parser-js "^0.7.9" 30 | 31 | iconv-lite@~0.4.13: 32 | version "0.4.17" 33 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d" 34 | 35 | is-stream@^1.0.1: 36 | version "1.1.0" 37 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 38 | 39 | isomorphic-fetch@^2.1.1: 40 | version "2.2.1" 41 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 42 | dependencies: 43 | node-fetch "^1.0.1" 44 | whatwg-fetch ">=0.10.0" 45 | 46 | js-tokens@^3.0.0: 47 | version "3.0.1" 48 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 49 | 50 | lodash@^4.17.4: 51 | version "4.17.4" 52 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 53 | 54 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 55 | version "1.3.1" 56 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 57 | dependencies: 58 | js-tokens "^3.0.0" 59 | 60 | node-fetch@^1.0.1: 61 | version "1.7.1" 62 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 63 | dependencies: 64 | encoding "^0.1.11" 65 | is-stream "^1.0.1" 66 | 67 | object-assign@^4.1.0: 68 | version "4.1.1" 69 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 70 | 71 | promise@^7.1.1: 72 | version "7.1.1" 73 | resolved "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 74 | dependencies: 75 | asap "~2.0.3" 76 | 77 | prop-types@^15.5.10, prop-types@^15.5.7: 78 | version "15.5.10" 79 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" 80 | dependencies: 81 | fbjs "^0.8.9" 82 | loose-envify "^1.3.1" 83 | 84 | react@^15.5.4: 85 | version "15.5.4" 86 | resolved "https://registry.npmjs.org/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" 87 | dependencies: 88 | fbjs "^0.8.9" 89 | loose-envify "^1.1.0" 90 | object-assign "^4.1.0" 91 | prop-types "^15.5.7" 92 | 93 | setimmediate@^1.0.5: 94 | version "1.0.5" 95 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 96 | 97 | ua-parser-js@^0.7.9: 98 | version "0.7.12" 99 | resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 100 | 101 | whatwg-fetch@>=0.10.0: 102 | version "2.0.3" 103 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 104 | -------------------------------------------------------------------------------- /modules/hover/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/hover/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/hover 2 | ========================== 3 | 4 | The `Hover` component binds listeners for the appropriate map and layer events, 5 | and tracks changes in the 'hovering' status of features in a layer. You can 6 | either handle hover changes with the `onHoverOver` and `onHoverOut` props or 7 | pass a function as the children who will be updated with `{features, properties}`, 8 | where `features` is an array of hovered features, and `properties` is an array 9 | containing the unique property values of the hovered layers (useful shortcut for filters). 10 | 11 | Usage 12 | ----- 13 | 14 | For an example usage, [see the storybook](https://terraeclipse.github.io/react-mapboxgl/?selectedKind=Hover&selectedStory=Example). 15 | 16 | - - - 17 | 18 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 19 | 20 | Terra Eclipse, Inc. is a nationally recognized political technology and 21 | strategy firm located in Santa Cruz, CA and Washington, D.C. 22 | -------------------------------------------------------------------------------- /modules/hover/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/hover", 3 | "version": "2.1.6", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Hover component for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "dependencies": { 25 | "@react-mapboxgl/core": "^2.1.3", 26 | "@react-mapboxgl/docs": "^2.1.0", 27 | "@terraeclipse/throttle-raf-decorator": "^1.0.4" 28 | }, 29 | "peerDependencies": { 30 | "lodash": "^4.17.4", 31 | "prop-types": "^15.5.10", 32 | "react": "^15.5.4" 33 | }, 34 | "devDependencies": { 35 | "lodash": "^4.17.4", 36 | "prop-types": "^15.5.10", 37 | "react": "^15.5.4" 38 | }, 39 | "author": "Brian Link", 40 | "license": "Apache-2.0", 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 47 | }, 48 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/hover#readme", 49 | "keywords": [ 50 | "react-component" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /modules/hover/src/Hover.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import {Children, LayerEvent} from '@react-mapboxgl/core' 5 | 6 | class Hover extends React.Component { 7 | static propTypes = { 8 | layer: PropTypes.string.isRequired, 9 | property: PropTypes.string.isRequired, 10 | cursor: PropTypes.string, 11 | onHoverOver: PropTypes.func, 12 | onHoverOut: PropTypes.func, 13 | children: PropTypes.func 14 | } 15 | 16 | static defaultProps = { 17 | cursor: 'pointer' 18 | } 19 | 20 | static contextTypes = { 21 | map: PropTypes.object 22 | } 23 | 24 | state = { 25 | properties: [], 26 | features: [] 27 | } 28 | 29 | constructor () { 30 | super() 31 | this.handleMouseEnter = this.handleMouseEnter.bind(this) 32 | this.handleMouseMove = this.handleMouseMove.bind(this) 33 | this.handleMouseLeave = this.handleMouseLeave.bind(this) 34 | } 35 | 36 | handleMouseEnter (e) { 37 | let propertyPath = `properties.${this.props.property}` 38 | let properties = _.map(e.features, propertyPath) 39 | 40 | if (this.props.cursor) { 41 | this.context.map.getCanvas().style.cursor = this.props.cursor 42 | } 43 | 44 | if (this.props.onHoverOut) { 45 | _.each(e.features, (feature) => { 46 | this.props.onHoverOver(e, feature) 47 | }) 48 | } 49 | 50 | this.setState({ 51 | properties: properties, 52 | features: e.features 53 | }) 54 | } 55 | 56 | handleMouseMove (e) { 57 | let propertyPath = `properties.${this.props.property}` 58 | let properties = _.map(e.features, propertyPath) 59 | let over = _.difference(properties, this.state.properties) 60 | let out = _.difference(this.state.properties, properties) 61 | if (over.length || out.length) { 62 | if (out.length && this.props.onHoverOut) { 63 | _.each(out, (property) => { 64 | this.props.onHoverOut(e, _.find(this.state.features, [propertyPath, property])) 65 | }) 66 | } 67 | if (over.length && this.props.onHoverOver) { 68 | _.each(over, (property) => { 69 | this.props.onHoverOver(e, _.find(e.features, [propertyPath, property])) 70 | }) 71 | } 72 | if (this.props.cursor) { 73 | this.context.map.getCanvas().style.cursor = this.props.cursor 74 | } 75 | this.setState({ 76 | properties: properties, 77 | features: e.features 78 | }) 79 | } 80 | } 81 | 82 | handleMouseLeave (e) { 83 | if (this.state.properties && this.props.onHoverOut) { 84 | _.each(this.state.features, (feature) => { 85 | this.props.onHoverOut(e, feature) 86 | }) 87 | } 88 | if (this.props.cursor) { 89 | this.context.map.getCanvas().style.cursor = '' 90 | } 91 | this.setState({ 92 | properties: [], 93 | features: [] 94 | }) 95 | } 96 | 97 | render () { 98 | return ( 99 | 100 | 105 | 110 | 115 | {this.props.children 116 | ? (typeof this.props.children === 'function') 117 | ? this.props.children(this.state) 118 | : this.props.children 119 | : null 120 | } 121 | 122 | ) 123 | } 124 | } 125 | 126 | export default Hover 127 | -------------------------------------------------------------------------------- /modules/hover/src/Hover.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hover - Example 3 | * 4 | * ## Hovering Features 5 | * Hover over map features to change the fill color. 6 | */ 7 | import React from 'react' 8 | import {mapDefaults} from '@react-mapboxgl/docs' 9 | import {MapGL, Source, Layer} from '@react-mapboxgl/core' 10 | import Hover from './' 11 | 12 | const Story = () => { 13 | return ( 14 | 15 | {/* Source to be used by layers (U.S. state polygons) */} 16 | 21 | 22 | {/* State fill layer */} 23 | 32 | 33 | {/* State borders layer */} 34 | 43 | 44 | {/* Declarative handler for hovering a layer's features. 45 | 46 | This component optionally allows a function as the 47 | *children*, similar to how libraries like react-motion do. You can 48 | leverage that to filter layers or otherwise modify them. 49 | 50 | The *property* should be a member of `feature.properties` that 51 | uniquely identifies each feature. Used to track actively hovering 52 | features. 53 | */} 54 | 55 | {({properties: names}) => ( 56 | 70 | )} 71 | 72 | 73 | ) 74 | } 75 | 76 | export default Story 77 | -------------------------------------------------------------------------------- /modules/hover/src/index.js: -------------------------------------------------------------------------------- 1 | import Hover from './Hover' 2 | export default Hover 3 | -------------------------------------------------------------------------------- /modules/hover/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@react-mapboxgl/core@^2.0.3": 6 | version "2.0.3" 7 | resolved "https://registry.npmjs.org/@react-mapboxgl/core/-/core-2.0.3.tgz#f76c3c4d83c3da29a528bfe393bb9482ade76b46" 8 | dependencies: 9 | "@react-mapboxgl/docs" "^2.0.0" 10 | 11 | "@react-mapboxgl/docs@^2.0.0": 12 | version "2.0.0" 13 | resolved "https://registry.npmjs.org/@react-mapboxgl/docs/-/docs-2.0.0.tgz#c4110f34f423a05e55cc7cb8e0646dc44a1be2c5" 14 | 15 | "@terraeclipse/throttle-raf-decorator@^1.0.4": 16 | version "1.0.4" 17 | resolved "https://registry.npmjs.org/@terraeclipse/throttle-raf-decorator/-/throttle-raf-decorator-1.0.4.tgz#c23c37c20f5a433904d2284e1224fe51fde3f653" 18 | dependencies: 19 | raf "^3.3.2" 20 | 21 | asap@~2.0.3: 22 | version "2.0.5" 23 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 24 | 25 | core-js@^1.0.0: 26 | version "1.2.7" 27 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 28 | 29 | encoding@^0.1.11: 30 | version "0.1.12" 31 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 32 | dependencies: 33 | iconv-lite "~0.4.13" 34 | 35 | fbjs@^0.8.9: 36 | version "0.8.12" 37 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 38 | dependencies: 39 | core-js "^1.0.0" 40 | isomorphic-fetch "^2.1.1" 41 | loose-envify "^1.0.0" 42 | object-assign "^4.1.0" 43 | promise "^7.1.1" 44 | setimmediate "^1.0.5" 45 | ua-parser-js "^0.7.9" 46 | 47 | iconv-lite@~0.4.13: 48 | version "0.4.17" 49 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d" 50 | 51 | is-stream@^1.0.1: 52 | version "1.1.0" 53 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 54 | 55 | isomorphic-fetch@^2.1.1: 56 | version "2.2.1" 57 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 58 | dependencies: 59 | node-fetch "^1.0.1" 60 | whatwg-fetch ">=0.10.0" 61 | 62 | js-tokens@^3.0.0: 63 | version "3.0.1" 64 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 65 | 66 | lodash@^4.17.4: 67 | version "4.17.4" 68 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 69 | 70 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 71 | version "1.3.1" 72 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 73 | dependencies: 74 | js-tokens "^3.0.0" 75 | 76 | node-fetch@^1.0.1: 77 | version "1.7.1" 78 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 79 | dependencies: 80 | encoding "^0.1.11" 81 | is-stream "^1.0.1" 82 | 83 | object-assign@^4.1.0: 84 | version "4.1.1" 85 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 86 | 87 | performance-now@^2.1.0: 88 | version "2.1.0" 89 | resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 90 | 91 | promise@^7.1.1: 92 | version "7.1.1" 93 | resolved "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 94 | dependencies: 95 | asap "~2.0.3" 96 | 97 | prop-types@^15.5.10, prop-types@^15.5.7: 98 | version "15.5.10" 99 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" 100 | dependencies: 101 | fbjs "^0.8.9" 102 | loose-envify "^1.3.1" 103 | 104 | raf@^3.3.2: 105 | version "3.3.2" 106 | resolved "https://registry.npmjs.org/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27" 107 | dependencies: 108 | performance-now "^2.1.0" 109 | 110 | react@^15.5.4: 111 | version "15.5.4" 112 | resolved "https://registry.npmjs.org/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" 113 | dependencies: 114 | fbjs "^0.8.9" 115 | loose-envify "^1.1.0" 116 | object-assign "^4.1.0" 117 | prop-types "^15.5.7" 118 | 119 | setimmediate@^1.0.5: 120 | version "1.0.5" 121 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 122 | 123 | ua-parser-js@^0.7.9: 124 | version "0.7.12" 125 | resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 126 | 127 | whatwg-fetch@>=0.10.0: 128 | version "2.0.3" 129 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 130 | -------------------------------------------------------------------------------- /modules/toggle/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /es 3 | /lib 4 | /node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /modules/toggle/README.md: -------------------------------------------------------------------------------- 1 | @react-mapboxgl/toggle 2 | ====================== 3 | 4 | The `Toggle` component binds listeners for the proper map and layer click events 5 | needed to 'toggle' features. You can either respond to toggled features with 6 | the `onToggle` prop, or pass a function as the children which will receive `{features, properties}`, 7 | where `features` is an array of toggled features, and `properties` is an array 8 | containing the unique property values of the toggled layers (useful shortcut for filters). 9 | 10 | Usage 11 | ----- 12 | 13 | For an example usage, [see the storybook](https://terraeclipse.github.io/react-mapboxgl/?selectedKind=Toggle&selectedStory=Example). 14 | 15 | - - - 16 | 17 | #### Developed by [TerraEclipse](https://github.com/TerraEclipse) 18 | 19 | Terra Eclipse, Inc. is a nationally recognized political technology and 20 | strategy firm located in Santa Cruz, CA and Washington, D.C. 21 | -------------------------------------------------------------------------------- /modules/toggle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/toggle", 3 | "version": "2.1.4", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Toggle component for @react-mapboxgl", 8 | "standard": { 9 | "parser": "babel-eslint" 10 | }, 11 | "main": "lib/index.js", 12 | "module": "es/index.js", 13 | "files": [ 14 | "es", 15 | "lib" 16 | ], 17 | "scripts": { 18 | "prepublish": "yarn run build", 19 | "build": "../../tools/build-module", 20 | "clean": "../../tools/clean-module", 21 | "test": "../../tools/test-module", 22 | "coverage": "../../tools/test-module --coverage" 23 | }, 24 | "dependencies": { 25 | "@react-mapboxgl/click": "^2.1.4", 26 | "@react-mapboxgl/core": "^2.1.3", 27 | "@react-mapboxgl/docs": "^2.1.0" 28 | }, 29 | "peerDependencies": { 30 | "lodash": "^4.17.4", 31 | "prop-types": "^15.5.10", 32 | "react": "^15.5.4" 33 | }, 34 | "devDependencies": { 35 | "lodash": "^4.17.4", 36 | "prop-types": "^15.5.10", 37 | "react": "^15.5.4" 38 | }, 39 | "author": "Brian Link", 40 | "license": "Apache-2.0", 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/TerraEclipse/react-mapboxgl.git" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/TerraEclipse/react-mapboxgl/issues" 47 | }, 48 | "homepage": "https://github.com/TerraEclipse/react-mapboxgl/tree/master/modules/toggle#readme", 49 | "keywords": [ 50 | "react-component" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /modules/toggle/src/Toggle.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import {Children} from '@react-mapboxgl/core' 5 | import Click from '@react-mapboxgl/click' 6 | 7 | class Toggle extends React.Component { 8 | static propTypes = { 9 | layer: PropTypes.string.isRequired, 10 | property: PropTypes.string.isRequired, 11 | multiple: PropTypes.bool, 12 | clickEvent: PropTypes.string, 13 | avoidDoubleClick: PropTypes.bool, 14 | doubleClickSpeed: PropTypes.number, 15 | closeOnClickOutside: PropTypes.bool, 16 | onToggle: PropTypes.func, 17 | children: PropTypes.func 18 | } 19 | 20 | static defaultProps = { 21 | multiple: false, 22 | closeOnClickOutside: true 23 | } 24 | 25 | static contextTypes = { 26 | map: PropTypes.object 27 | } 28 | 29 | state = { 30 | features: {} 31 | } 32 | 33 | constructor () { 34 | super() 35 | this.handleClickLayer = this.handleClickLayer.bind(this) 36 | this.handleClickMap = this.handleClickMap.bind(this) 37 | this.toggleFeature = this.toggleFeature.bind(this) 38 | } 39 | 40 | componentWillReceiveProps (nextProps) { 41 | if (this.props.layer !== nextProps.layer) { 42 | this.setState({features: {}}) 43 | } 44 | if (this.props.multiple !== nextProps.multiple) { 45 | if (!nextProps.multiple && !_.isEmpty(this.state.features)) { 46 | let key = _.keys(this.state.features).pop() 47 | this.setState({features: { 48 | [key]: this.state.features[key] 49 | }}) 50 | } 51 | } 52 | } 53 | 54 | handleClickLayer (e) { 55 | if (this.props.multiple) { 56 | _.each(e.features, this.toggleFeature) 57 | } else if (e.features.length) { 58 | this.toggleFeature(e.features[0]) 59 | } 60 | } 61 | 62 | handleClickMap (e) { 63 | let {map} = this.context 64 | let features = map.queryRenderedFeatures(e.point, { 65 | layers: [this.props.layer] 66 | }) 67 | if (!features.length) { 68 | if (this.props.closeOnClickOutside) { 69 | this.setState({features: {}}) 70 | } 71 | } else if (this.props.multiple) { 72 | _.each(features, this.toggleFeature) 73 | } else { 74 | this.toggleFeature(features[0]) 75 | } 76 | } 77 | 78 | toggleFeature (feature) { 79 | let {features} = this.state 80 | let property = this.getProperty(feature) 81 | let isOn = true 82 | 83 | if (this.props.multiple) { 84 | if (this.state.features[property]) { 85 | isOn = false 86 | delete features[property] 87 | } else { 88 | features[property] = feature 89 | } 90 | } else { 91 | if (features[property]) { 92 | isOn = false 93 | features = {} 94 | } else { 95 | features = {[property]: feature} 96 | } 97 | } 98 | 99 | if (this.props.onToggle) { 100 | this.props.onToggle(feature, isOn) 101 | } 102 | 103 | this.setState({features}) 104 | } 105 | 106 | getProperty (feature) { 107 | let propertyPath = `properties.${this.props.property}` 108 | let property = _.get(feature, propertyPath) 109 | if (typeof property !== 'undefined') { 110 | return property 111 | } else { 112 | throw new Error('Could not find property for feature') 113 | } 114 | } 115 | 116 | render () { 117 | return ( 118 | 119 | {this.props.closeOnClickOutside ? ( 120 | 126 | ) : ( 127 | 134 | )} 135 | {this.props.children 136 | ? (typeof this.props.children === 'function') 137 | ? this.props.children({ 138 | features: _.values(this.state.features), 139 | properties: _.keys(this.state.features) 140 | }) 141 | : this.props.children 142 | : null 143 | } 144 | 145 | ) 146 | } 147 | } 148 | 149 | export default Toggle 150 | -------------------------------------------------------------------------------- /modules/toggle/src/Toggle.story.src.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggle - Example 3 | * 4 | * ## Toggle Features in a Layer 5 | * 'Toggles' features in a layer, using them in a children render function to add a fill color. 6 | */ 7 | import React from 'react' 8 | import {mapDefaults, Overlay, Checkbox} from '@react-mapboxgl/docs' 9 | import {MapGL, Source, Layer} from '@react-mapboxgl/core' 10 | import Toggle from './' 11 | 12 | class Story extends React.Component { 13 | state = { 14 | multiple: false 15 | } 16 | 17 | render () { 18 | return ( 19 | 20 | 25 | 34 | 43 | 49 | {({properties: names}) => ( 50 | 65 | )} 66 | 67 | 68 | { 71 | this.setState((state) => { 72 | state.multiple = !state.multiple 73 | }) 74 | }} 75 | checked={this.state.multiple} 76 | /> 77 | 78 | 79 | ) 80 | } 81 | } 82 | 83 | export default Story 84 | -------------------------------------------------------------------------------- /modules/toggle/src/index.js: -------------------------------------------------------------------------------- 1 | import Toggle from './Toggle' 2 | export default Toggle 3 | -------------------------------------------------------------------------------- /modules/toggle/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@react-mapboxgl/click@^2.0.3": 6 | version "2.0.3" 7 | resolved "https://registry.npmjs.org/@react-mapboxgl/click/-/click-2.0.3.tgz#92b7252ec72df857d1583891b229f4df59b63ae5" 8 | dependencies: 9 | "@react-mapboxgl/core" "^2.0.3" 10 | "@react-mapboxgl/docs" "^2.0.0" 11 | 12 | "@react-mapboxgl/core@^2.0.3": 13 | version "2.0.3" 14 | resolved "https://registry.npmjs.org/@react-mapboxgl/core/-/core-2.0.3.tgz#f76c3c4d83c3da29a528bfe393bb9482ade76b46" 15 | dependencies: 16 | "@react-mapboxgl/docs" "^2.0.0" 17 | 18 | "@react-mapboxgl/docs@^2.0.0": 19 | version "2.0.0" 20 | resolved "https://registry.npmjs.org/@react-mapboxgl/docs/-/docs-2.0.0.tgz#c4110f34f423a05e55cc7cb8e0646dc44a1be2c5" 21 | 22 | asap@~2.0.3: 23 | version "2.0.5" 24 | resolved "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 25 | 26 | core-js@^1.0.0: 27 | version "1.2.7" 28 | resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 29 | 30 | encoding@^0.1.11: 31 | version "0.1.12" 32 | resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 33 | dependencies: 34 | iconv-lite "~0.4.13" 35 | 36 | fbjs@^0.8.9: 37 | version "0.8.12" 38 | resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 39 | dependencies: 40 | core-js "^1.0.0" 41 | isomorphic-fetch "^2.1.1" 42 | loose-envify "^1.0.0" 43 | object-assign "^4.1.0" 44 | promise "^7.1.1" 45 | setimmediate "^1.0.5" 46 | ua-parser-js "^0.7.9" 47 | 48 | iconv-lite@~0.4.13: 49 | version "0.4.17" 50 | resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d" 51 | 52 | is-stream@^1.0.1: 53 | version "1.1.0" 54 | resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 55 | 56 | isomorphic-fetch@^2.1.1: 57 | version "2.2.1" 58 | resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 59 | dependencies: 60 | node-fetch "^1.0.1" 61 | whatwg-fetch ">=0.10.0" 62 | 63 | js-tokens@^3.0.0: 64 | version "3.0.1" 65 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 66 | 67 | lodash@^4.17.4: 68 | version "4.17.4" 69 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 70 | 71 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 72 | version "1.3.1" 73 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 74 | dependencies: 75 | js-tokens "^3.0.0" 76 | 77 | node-fetch@^1.0.1: 78 | version "1.7.1" 79 | resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 80 | dependencies: 81 | encoding "^0.1.11" 82 | is-stream "^1.0.1" 83 | 84 | object-assign@^4.1.0: 85 | version "4.1.1" 86 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 87 | 88 | promise@^7.1.1: 89 | version "7.1.1" 90 | resolved "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 91 | dependencies: 92 | asap "~2.0.3" 93 | 94 | prop-types@^15.5.10, prop-types@^15.5.7: 95 | version "15.5.10" 96 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" 97 | dependencies: 98 | fbjs "^0.8.9" 99 | loose-envify "^1.3.1" 100 | 101 | react@^15.5.4: 102 | version "15.5.4" 103 | resolved "https://registry.npmjs.org/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" 104 | dependencies: 105 | fbjs "^0.8.9" 106 | loose-envify "^1.1.0" 107 | object-assign "^4.1.0" 108 | prop-types "^15.5.7" 109 | 110 | setimmediate@^1.0.5: 111 | version "1.0.5" 112 | resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 113 | 114 | ua-parser-js@^0.7.9: 115 | version "0.7.12" 116 | resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 117 | 118 | whatwg-fetch@>=0.10.0: 119 | version "2.0.3" 120 | resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mapboxgl/repo", 3 | "private": true, 4 | "standard": { 5 | "parser": "babel-eslint" 6 | }, 7 | "scripts": { 8 | "install": "yarn run bootstrap", 9 | "test": "yarn run exec -- yarn run test", 10 | "lerna": "lerna", 11 | "bootstrap": "lerna bootstrap", 12 | "exec": "lerna exec --concurrency 1", 13 | "clean": "lerna run clean", 14 | "do": "./tools/module-do", 15 | "nuke": "yarn run clean && lerna clean --yes", 16 | "reinstall": "yarn run nuke && yarn run lerna bootstrap", 17 | "storybook": "NODE_ENV=develop BABEL_ENV=storybook start-storybook -p 3005 -c ./tools/.storybook", 18 | "docs": "BABEL_ENV=commonjs build-storybook -c ./tools/.storybook -o ./docs", 19 | "view-coverage": "./tools/view-coverage" 20 | }, 21 | "devDependencies": { 22 | "@storybook/addon-actions": "^3.0.1", 23 | "@storybook/addon-info": "^3.0.1", 24 | "@storybook/addon-options": "^3.0.1", 25 | "@storybook/react": "^3.0.1", 26 | "babel-cli": "^6.24.1", 27 | "babel-eslint": "^7.2.3", 28 | "babel-jest": "^20.0.1", 29 | "babel-plugin-lodash": "^3.2.11", 30 | "babel-plugin-module-resolver": "^3.0.0-beta.3", 31 | "babel-plugin-transform-class-properties": "^6.24.1", 32 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 33 | "babel-plugin-transform-react-jsx": "^6.24.1", 34 | "babel-preset-env": "^1.5.2", 35 | "babel-register": "^6.24.1", 36 | "babel-runtime": "^6.23.0", 37 | "enzyme": "^2.8.2", 38 | "jest": "^20.0.1", 39 | "lerna": "^2.0.0-rc.4", 40 | "lodash": "^4.17.4", 41 | "prism-themes": "^1.0.0", 42 | "prismjs-loader": "^0.0.4", 43 | "prop-types": "^15.5.10", 44 | "raw-loader": "^0.5.1", 45 | "react": "^15.5.4", 46 | "react-display-name": "^0.2.0", 47 | "react-dom": "^15.5.4", 48 | "react-markdown": "^2.5.0", 49 | "react-test-renderer": "^15.5.4", 50 | "regenerator-runtime": "^0.10.5", 51 | "rimraf": "^2.6.1", 52 | "snazzy": "^7.0.0", 53 | "standard": "^10.0.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tools/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import {setAddon} from '@storybook/react' 2 | import infoAddon from '@storybook/addon-info' 3 | 4 | // Registration-based addons. 5 | import '@storybook/addon-actions/register' 6 | import '@storybook/addon-options/register' 7 | 8 | // Set other addons. 9 | setAddon(infoAddon) 10 | -------------------------------------------------------------------------------- /tools/.storybook/components/WithDocs.css: -------------------------------------------------------------------------------- 1 | body.has-storybook-with-docs { 2 | background: #63626d; 3 | } 4 | body.has-storybook-with-docs * { 5 | box-sizing: border-box; 6 | } 7 | .storybook-with-docs { 8 | max-width: 800px; 9 | margin: 20px auto; 10 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 11 | font-weight: 300; 12 | border-bottom: 2px solid #ccc; 13 | } 14 | .storybook-with-docs:last-child { 15 | border-bottom: none; 16 | } 17 | 18 | /* Typography of the whole thing */ 19 | .storybook-with-docs *:last-child { 20 | margin-bottom: 0; 21 | } 22 | .storybook-with-docs h2 { 23 | font-size: 1.4em; 24 | font-weight: 500; 25 | margin: 0 0 0.5em 0; 26 | } 27 | .storybook-with-docs h3 { 28 | font-size: 1.2em; 29 | font-weight: 200; 30 | margin: 0 0 0.5em 0; 31 | } 32 | .storybook-with-docs p { 33 | margin: 0 0 1em 0; 34 | } 35 | .storybook-with-docs code { 36 | padding: 1px 3px; 37 | background: rgba(0, 0, 0, 0.2); 38 | vertical-align: baseline; 39 | } 40 | .storybook-with-docs ul li, 41 | .storybook-with-docs ol li { 42 | margin-bottom: 1em; 43 | } 44 | 45 | /* Labels */ 46 | .storybook-with-docs .labeled { 47 | position: relative; 48 | } 49 | .storybook-with-docs .labeled .label { 50 | position: absolute; 51 | top: 0; 52 | right: 10px; 53 | padding: 5px 10px; 54 | font-size: 0.6em; 55 | text-transform: uppercase; 56 | background: rgba(200, 200, 200, 0.6); 57 | letter-spacing: 0.1em; 58 | color: #555; 59 | z-index: 20; 60 | } 61 | 62 | /* Description */ 63 | .storybook-with-docs .description { 64 | font-size: 0.9em; 65 | margin: 20px 10px; 66 | } 67 | .storybook-with-docs .description * { 68 | color: #fff; 69 | } 70 | 71 | /* Example */ 72 | .storybook-with-docs .story { 73 | position: relative; 74 | margin: 20px 10px; 75 | padding: 20px; 76 | background: #fff; 77 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15); 78 | } 79 | 80 | /* Source code */ 81 | .storybook-with-docs .source { 82 | margin: 20px 10px 40px 10px; 83 | } 84 | .storybook-with-docs .source pre { 85 | margin: 0; 86 | padding: 10px; 87 | overflow-x: auto; 88 | background: #fffcf7; 89 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15); 90 | } 91 | 92 | /* Special-case styles */ 93 | .storybook-with-docs .story .btn { 94 | padding: 5px 10px; 95 | font-size: 1em; 96 | background: #555; 97 | border: 1px solid rgba(0, 0, 0, 0.3); 98 | border-radius: 3px; 99 | box-shadow: inset 0 0 1px rgba(255, 255, 255, 0.6); 100 | color: #fff; 101 | cursor: pointer; 102 | } 103 | .storybook-with-docs .story .btn:hover { 104 | background: #666; 105 | } 106 | .storybook-with-docs .react-mapbox--container { 107 | position: relative !important; 108 | margin: -20px; 109 | padding-bottom: 55%; 110 | } -------------------------------------------------------------------------------- /tools/.storybook/components/WithDocs.js: -------------------------------------------------------------------------------- 1 | import './WithDocs.css' 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | import ReactMarkdown from 'react-markdown' 5 | 6 | import 'prismjs/themes/prism.css' 7 | 8 | class WithSource extends React.Component { 9 | static propTypes = { 10 | description: PropTypes.string, 11 | blocks: PropTypes.array, 12 | children: PropTypes.node 13 | } 14 | 15 | shouldComponentUpdate (nextProps) { 16 | return false 17 | } 18 | 19 | componentDidMount () { 20 | document.body.className += ' has-storybook-with-docs' 21 | } 22 | 23 | componentWillUnmount () { 24 | document.body.className = document.body.className.replace('has-storybook-with-docs', '') 25 | } 26 | 27 | render () { 28 | return ( 29 |
30 | 31 | {this.props.blocks.map((block, i) => ( 32 |
33 | {block.markdown ? ( 34 | 35 | ) : null} 36 | {block.code ? ( 37 |
38 |
39 |               
40 | ) : null} 41 |
42 | ))} 43 |
44 | ) 45 | } 46 | } 47 | 48 | export default WithSource 49 | -------------------------------------------------------------------------------- /tools/.storybook/components/WithSource.css: -------------------------------------------------------------------------------- 1 | body.has-storybook-with-source { 2 | background: #63626d; 3 | } 4 | body.has-storybook-with-source * { 5 | box-sizing: border-box; 6 | } 7 | .storybook-with-source { 8 | max-width: 800px; 9 | margin: 20px auto; 10 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 11 | font-weight: 300; 12 | border-bottom: 2px solid #ccc; 13 | } 14 | .storybook-with-source:last-child { 15 | border-bottom: none; 16 | } 17 | 18 | /* Typography of the whole thing */ 19 | .storybook-with-source *:last-child { 20 | margin-bottom: 0; 21 | } 22 | .storybook-with-source h2 { 23 | font-size: 1.4em; 24 | font-weight: 500; 25 | margin: 0 0 0.5em 0; 26 | } 27 | .storybook-with-source h3 { 28 | font-size: 1.2em; 29 | font-weight: 200; 30 | margin: 0 0 0.5em 0; 31 | } 32 | .storybook-with-source p { 33 | margin: 0 0 1em 0; 34 | } 35 | .storybook-with-source code { 36 | padding: 1px 3px; 37 | background: rgba(0, 0, 0, 0.2); 38 | vertical-align: baseline; 39 | } 40 | .storybook-with-source ul li, 41 | .storybook-with-source ol li { 42 | margin-bottom: 1em; 43 | } 44 | 45 | /* Labels */ 46 | .storybook-with-source .labeled { 47 | position: relative; 48 | } 49 | .storybook-with-source .labeled .label { 50 | position: absolute; 51 | top: 0; 52 | right: 10px; 53 | padding: 5px 10px; 54 | font-size: 0.6em; 55 | text-transform: uppercase; 56 | background: rgba(200, 200, 200, 0.6); 57 | letter-spacing: 0.1em; 58 | color: #555; 59 | z-index: 20; 60 | } 61 | 62 | /* Description */ 63 | .storybook-with-source .description { 64 | font-size: 0.9em; 65 | margin: 20px 10px; 66 | } 67 | .storybook-with-source .description * { 68 | color: #fff; 69 | } 70 | 71 | /* Example */ 72 | .storybook-with-source .story { 73 | position: relative; 74 | margin: 20px 10px; 75 | padding: 20px; 76 | background: #fff; 77 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15); 78 | } 79 | 80 | /* Source code */ 81 | .storybook-with-source .source { 82 | margin: 20px 10px 40px 10px; 83 | } 84 | .storybook-with-source .source pre { 85 | margin: 0; 86 | padding: 10px; 87 | overflow-x: auto; 88 | background: #fffcf7; 89 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15); 90 | } 91 | 92 | /* Special-case styles */ 93 | .storybook-with-source .story .btn { 94 | padding: 5px 10px; 95 | font-size: 1em; 96 | background: #555; 97 | border: 1px solid rgba(0, 0, 0, 0.3); 98 | border-radius: 3px; 99 | box-shadow: inset 0 0 1px rgba(255, 255, 255, 0.6); 100 | color: #fff; 101 | cursor: pointer; 102 | } 103 | .storybook-with-source .story .btn:hover { 104 | background: #666; 105 | } 106 | .storybook-with-source .react-mapbox--container { 107 | position: relative !important; 108 | margin: -20px; 109 | padding-bottom: 55%; 110 | } -------------------------------------------------------------------------------- /tools/.storybook/components/WithSource.js: -------------------------------------------------------------------------------- 1 | import './WithSource.css' 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | import ReactMarkdown from 'react-markdown' 5 | 6 | import 'prismjs/themes/prism.css' 7 | 8 | class WithSource extends React.Component { 9 | static propTypes = { 10 | description: PropTypes.string, 11 | source: PropTypes.string, 12 | children: PropTypes.node 13 | } 14 | 15 | shouldComponentUpdate (nextProps) { 16 | return false 17 | } 18 | 19 | componentDidMount () { 20 | document.body.className += ' has-storybook-with-source' 21 | } 22 | 23 | componentWillUnmount () { 24 | document.body.className = document.body.className.replace('has-storybook-with-source', '') 25 | } 26 | 27 | render () { 28 | return ( 29 |
30 | {this.props.description ? ( 31 | 32 | ) : null} 33 |
34 |
Example
35 |
36 | {this.props.children} 37 |
38 |
39 | {this.props.source ? ( 40 |
41 |
Source
42 |
43 |
44 |             
45 |
46 | ) : null} 47 |
48 | ) 49 | } 50 | } 51 | 52 | export default WithSource 53 | -------------------------------------------------------------------------------- /tools/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import {configure} from '@storybook/react' 2 | import {setOptions} from '@storybook/addon-options' 3 | import loadStories from './util/loadStories' 4 | import loadDocsStories from './util/loadDocsStories' 5 | import loadSourceStories from './util/loadSourceStories' 6 | 7 | // Set storybook options. 8 | setOptions({ 9 | name: '@react-mapboxgl', 10 | url: 'https://github.com/TerraEclipse/react-mapboxgl', 11 | showSearchBox: false, 12 | downPanelInRight: false 13 | }) 14 | 15 | // Configure storybook. 16 | configure(() => { 17 | loadStories() 18 | loadDocsStories() 19 | loadSourceStories() 20 | }, module) 21 | -------------------------------------------------------------------------------- /tools/.storybook/util/loadDocsStories.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {storiesOf} from '@storybook/react' 3 | import parseComment from './parseComment' 4 | import parseBlocks from './parseBlocks' 5 | import WithDocs from '../components/WithDocs' 6 | 7 | /** 8 | * Load `*.story.docs.js` files, which render with as docs. 9 | */ 10 | export default function loadDocsStories () { 11 | // Create webpack require contexts for 'source' stories and load them. 12 | const reqSourceRaw = require.context('!!raw-loader!../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.docs\.js$/) 13 | const reqSourcePrism = require.context('!!prismjs-loader?lang=jsx!../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.docs\.js$/) 14 | reqSourceRaw.keys().forEach((filepath) => { 15 | let raw = reqSourceRaw(filepath) 16 | let source = reqSourcePrism(filepath) 17 | let meta = parseComment(raw) 18 | let blocks = parseBlocks(raw, source) 19 | 20 | // Remove the top docblock from the source because its redundant with the 21 | // rendered markdown description. 22 | source = source.replace(/^]+>[^<]*<\/span>/, '') 23 | source = source.trim() 24 | 25 | // Add the story. 26 | storiesOf(meta.category).add(meta.label, () => ( 27 | 31 | )) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /tools/.storybook/util/loadSourceStories.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {storiesOf} from '@storybook/react' 3 | import parseComment from './parseComment' 4 | import WithSource from '../components/WithSource' 5 | import MapboxProvider from '../../../modules/core/src/MapboxProvider' 6 | 7 | // Mapbox access token. 8 | const TOKEN = 'pk.eyJ1IjoidGVycmEiLCJhIjoiVmNta3lMSSJ9.V4vST11PV1hulV2Mf9DqdQ' 9 | 10 | /** 11 | * Load `*.story.src.js` files, which render with description, 12 | * example, and source code. 13 | */ 14 | export default function loadSourceStories () { 15 | // Create webpack require contexts for 'source' stories and load them. 16 | const reqSourceStory = require.context('../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.src\.js$/) 17 | const reqSourceRaw = require.context('!!raw-loader!../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.src\.js$/) 18 | const reqSourcePrism = require.context('!!prismjs-loader?lang=jsx!../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.src\.js$/) 19 | reqSourceStory.keys().forEach((filepath) => { 20 | let Story = reqSourceStory(filepath).default 21 | let raw = reqSourceRaw(filepath) 22 | let source = reqSourcePrism(filepath) 23 | let meta = parseComment(raw) 24 | 25 | // Remove the docblock from the source because its redundant with the 26 | // rendered markdown description. 27 | source = source.replace(/^]+>[^<]*<\/span>/, '') 28 | source = source.trim() 29 | 30 | // Add the story. 31 | storiesOf(meta.category).add(meta.label, () => ( 32 | 36 | 37 | 38 | 39 | 40 | )) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /tools/.storybook/util/loadStories.js: -------------------------------------------------------------------------------- 1 | import {storiesOf} from '@storybook/react' 2 | import {action} from '@storybook/addon-actions' 3 | 4 | /** 5 | * Load regular `*.story.js` files. 6 | */ 7 | export default function loadStories () { 8 | // Create webpack require context for stories and load them. 9 | const reqStory = require.context('../../../modules', true, /\.\/[^/]+\/src\/.*\.story\.js$/) 10 | reqStory.keys().forEach((filepath) => { 11 | reqStory(filepath).default({storiesOf, action}) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /tools/.storybook/util/parseBlocks.js: -------------------------------------------------------------------------------- 1 | // Helper to parse comment -> code blocks from source. 2 | export default function parseBlocks (raw, source) { 3 | let blocks = [] 4 | let rawRegex = /\/\*\*[^*]*\*+(?:[^*/][^*]*\*+)*\//m 5 | let sourceRegex = /^]+>\/\*\*[^<]*<\/span>/m 6 | 7 | // Remove the first comment from the raw. 8 | raw = raw.replace(rawRegex, '') 9 | raw = raw.trim() 10 | 11 | // Remove the top docblock from the source. 12 | source = source.replace(sourceRegex, '') 13 | source = source.trim() 14 | 15 | // Parse out the comment blocks from the raw. 16 | while (raw.length) { 17 | let result = rawRegex.exec(raw) 18 | if (result) { 19 | blocks.push({comment: result[0]}) 20 | raw = raw.slice(result.index + result[0].length) 21 | } else { 22 | break 23 | } 24 | } 25 | 26 | // Parse out the code blocks from source. 27 | let block = 0 28 | while (source.length) { 29 | let result = sourceRegex.exec(source) 30 | if (result) { 31 | if (result.index === 0) { 32 | source = source.slice(result[0].length) 33 | } else { 34 | blocks[block++].code = source.slice(0, result.index) 35 | source = source.slice(result.index + result[0].length) 36 | } 37 | } else { 38 | blocks[block++].code = source 39 | break 40 | } 41 | } 42 | 43 | // Parse block comments into markdown. 44 | blocks.forEach((block) => { 45 | if (block.comment) { 46 | // Split into lines without comment syntax. 47 | var lines = block.comment.split('\r\n').map((line) => { 48 | return line.replace(/^(\/\*\*?\s?)|(\s?\*\*?\/?\s?)/gm, '') 49 | }) 50 | 51 | // Join back into markdown. 52 | block.markdown = lines.join('\n') 53 | } 54 | }) 55 | 56 | // Trim newlines from stuff. 57 | blocks.forEach((block) => { 58 | if (block.markdown) { 59 | block.markdown = block.markdown.trim() 60 | } 61 | if (block.code) { 62 | block.code = block.code.trim() 63 | } 64 | }) 65 | 66 | return blocks 67 | } 68 | -------------------------------------------------------------------------------- /tools/.storybook/util/parseComment.js: -------------------------------------------------------------------------------- 1 | // Helper to parse meta from top comment block. 2 | export default function parseMeta (src) { 3 | var regex = /\/(\*)[^*]*\*+(?:[^*/][^*]*\*+)*\//gm 4 | var blocks = src.match(regex) 5 | if (blocks) { 6 | // Split into lines without comment syntax. 7 | var lines = blocks[0].split('\n').map((line) => { 8 | return line.replace(/^(\/\*\*?\s?)|(\s?\*\*?\/?\s?)/g, '') 9 | }) 10 | 11 | // Strip empty lines. 12 | while (lines[0] === '') { 13 | lines.shift() 14 | } 15 | 16 | // Parse category and label (and remove it). 17 | var [category, label] = lines[0].split(/\s-\s/).map((part) => part.trim()) 18 | lines.shift() 19 | if (!category || !label) { 20 | throw new Error('Could not parse category or label from docblock') 21 | } 22 | 23 | // Strip empty lines. 24 | while (lines.length && lines[0] === '') { 25 | lines.shift() 26 | } 27 | 28 | // Join the rest into a description. 29 | var description = lines.join('\n') 30 | 31 | return {category, label, description} 32 | } else { 33 | return false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tools/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'mock-file' 2 | -------------------------------------------------------------------------------- /tools/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /tools/build-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path') 3 | const exec = require('./exec') 4 | 5 | function buildModule (cwd) { 6 | const pkg = require(path.join(cwd, 'package.json')) 7 | const src = path.join(cwd, 'src') 8 | const cjs = path.join(cwd, 'lib') 9 | const es = path.join(cwd, 'es') 10 | const babelIgnore = [ 11 | '**/*.story.js', 12 | '**/*.story.docs.js', 13 | '**/*.story.src.js', 14 | '**/test/**', 15 | '**/*.test.js', 16 | '**/__snapshots__/**', 17 | '**/__fixtures__/**', 18 | '**/__mocks__/**' 19 | ].join(',') 20 | 21 | console.log(`Building ${pkg.name} ...`) 22 | 23 | console.log(' - Building CommonJS modules') 24 | exec(`rimraf ${cjs}`) 25 | exec(`babel --quiet --copy-files --ignore ${babelIgnore} --out-dir ${cjs} ${src}`, { 26 | cwd: cwd, 27 | env: { 28 | NODE_ENV: process.env.NODE_ENV || 'production', 29 | BABEL_ENV: 'commonjs' 30 | } 31 | }) 32 | 33 | console.log(' - Building ES modules') 34 | exec(`rimraf ${es}`) 35 | exec(`babel --quiet --copy-files --ignore ${babelIgnore} --out-dir ${es} ${src}`, { 36 | cwd: cwd, 37 | env: { 38 | NODE_ENV: process.env.NODE_ENV || 'production', 39 | BABEL_ENV: 'es' 40 | } 41 | }) 42 | } 43 | 44 | // Are we running this directly? 45 | if (process.argv[0].match(/bin\/node$/) && process.argv[1].match(/build-module$/)) { 46 | buildModule(process.cwd()) 47 | } 48 | 49 | module.exports = buildModule 50 | -------------------------------------------------------------------------------- /tools/clean-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path') 3 | const exec = require('./exec') 4 | 5 | function cleanModule (cwd) { 6 | // Ceanup error logs. 7 | function cleanBuild () { 8 | return Promise.resolve().then(() => { 9 | exec(`rimraf ${path.join(cwd, 'lib')}`) 10 | exec(`rimraf ${path.join(cwd, 'es')}`) 11 | }) 12 | } 13 | 14 | // Ceanup error logs. 15 | function cleanErrorLogs () { 16 | return Promise.resolve().then(() => { 17 | exec(`rimraf ${process.cwd()}/lerna-debug.log`) 18 | exec(`rimraf ${process.cwd()}/npm-debug.log`) 19 | }) 20 | } 21 | 22 | // Run all the tasks. 23 | Promise.all([ 24 | cleanBuild(), 25 | cleanErrorLogs() 26 | ]).then(() => { 27 | console.log() 28 | console.log('Module cleaned!') 29 | console.log() 30 | }) 31 | } 32 | 33 | // Are we running this directly? 34 | if (process.argv[0].match(/bin\/node$/) && process.argv[1].match(/clean-module$/)) { 35 | cleanModule(process.cwd()) 36 | } 37 | 38 | module.exports = cleanModule 39 | -------------------------------------------------------------------------------- /tools/exec.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const path = require('path') 3 | const execSync = require('child_process').execSync 4 | 5 | module.exports = function (command, options = {}) { 6 | execSync(command, _.defaultsDeep({}, options, { 7 | stdio: 'inherit', 8 | env: { 9 | AC_TOOLS: true, 10 | PATH: path.resolve(__dirname, '../node_modules/.bin') + ':' + process.env.PATH 11 | } 12 | }, {env: process.env})) 13 | } 14 | -------------------------------------------------------------------------------- /tools/module-do: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path') 3 | const exec = require('./exec') 4 | 5 | // Using this because `lerna run --scope` requires '@terraeclipse/' prefix. 6 | function moduleDo (mod, cmd, ...rest) { 7 | const modDir = path.resolve(__dirname, '../modules', mod) 8 | exec(`yarn run ${cmd} -- ${rest.join(' ')}`, {cwd: modDir}) 9 | } 10 | 11 | // Are we running this directly? 12 | if (process.argv[0].match(/bin\/node$/) && process.argv[1].match(/module-do$/)) { 13 | if (!process.argv[2]) { 14 | console.error('Error: Please specify a module name') 15 | } else if (!process.argv[3]) { 16 | console.error('Error: Please specify a cmd') 17 | } else { 18 | moduleDo(...process.argv.slice(2)) 19 | } 20 | } 21 | 22 | module.exports = moduleDo 23 | -------------------------------------------------------------------------------- /tools/test-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path') 3 | const exec = require('./exec') 4 | 5 | function testModule (cwd, args = []) { 6 | const rootDir = path.resolve(__dirname, '../') 7 | const relCwd = path.relative(rootDir, cwd) 8 | let cmd 9 | 10 | // Add our args. 11 | args = args.concat([ 12 | `--roots ${relCwd}/src`, 13 | `--collectCoverageFrom ${relCwd}/src/**/*.js`, 14 | `--coverageDirectory ${relCwd}/coverage` 15 | ]) 16 | 17 | // Create and print the cmd. 18 | cmd = `jest ${args.join(' ')}` 19 | console.log(cmd) 20 | 21 | exec(cmd, { 22 | cwd: rootDir, 23 | env: { 24 | NODE_ENV: 'test', 25 | BABEL_ENV: 'node' 26 | } 27 | }) 28 | } 29 | 30 | // Are we running this directly? 31 | if (process.argv[0].match(/bin\/node$/) && process.argv[1].match(/test-module$/)) { 32 | testModule(process.cwd(), process.argv.slice(2)) 33 | } 34 | 35 | module.exports = testModule 36 | -------------------------------------------------------------------------------- /tools/view-coverage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path') 3 | const exec = require('./exec') 4 | 5 | function viewCoverage (mod) { 6 | exec(`open ${path.resolve(__dirname, '../modules', mod, 'coverage/lcov-report/index.html')}`) 7 | } 8 | 9 | // Are we running this directly? 10 | if (process.argv[0].match(/bin\/node$/) && process.argv[1].match(/view-coverage$/)) { 11 | if (!process.argv[2]) { 12 | console.error('Error: Please specify a module name') 13 | } else { 14 | viewCoverage(...process.argv.slice(2)) 15 | } 16 | } 17 | 18 | module.exports = viewCoverage 19 | --------------------------------------------------------------------------------