@@ -99,7 +106,7 @@ class ColorField extends React.Component {
this.colorInput = input}
onClick={this.togglePicker.bind(this)}
style={this.props.style}
name={this.props.name}
diff --git a/src/components/fields/DocLabel.jsx b/src/components/fields/DocLabel.jsx
index d13efb6..9af6a86 100644
--- a/src/components/fields/DocLabel.jsx
+++ b/src/components/fields/DocLabel.jsx
@@ -1,12 +1,13 @@
import React from 'react'
+import PropTypes from 'prop-types'
export default class DocLabel extends React.Component {
static propTypes = {
- label: React.PropTypes.oneOfType([
- React.PropTypes.object,
- React.PropTypes.string
+ label: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.string
]).isRequired,
- doc: React.PropTypes.string.isRequired,
+ doc: PropTypes.string.isRequired,
}
render() {
diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx
new file mode 100644
index 0000000..9772a33
--- /dev/null
+++ b/src/components/fields/FunctionSpecField.jsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import SpecProperty from './_SpecProperty'
+import DataProperty from './_DataProperty'
+import ZoomProperty from './_ZoomProperty'
+
+
+function isZoomField(value) {
+ return typeof value === 'object' && value.stops && typeof value.property === 'undefined'
+}
+
+function isDataField(value) {
+ return typeof value === 'object' && value.stops && typeof value.property !== 'undefined'
+}
+
+/** Supports displaying spec field for zoom function objects
+ * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
+ */
+export default class FunctionSpecProperty extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ fieldName: PropTypes.string.isRequired,
+ fieldSpec: PropTypes.object.isRequired,
+
+ value: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.bool,
+ PropTypes.array
+ ]),
+ }
+
+ addStop() {
+ const stops = this.props.value.stops.slice(0)
+ const lastStop = stops[stops.length - 1]
+ if (typeof lastStop[0] === "object") {
+ stops.push([
+ {zoom: lastStop[0].zoom + 1, value: lastStop[0].value},
+ lastStop[1]
+ ])
+ }
+ else {
+ stops.push([lastStop[0] + 1, lastStop[1]])
+ }
+
+ const changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ deleteStop(stopIdx) {
+ const stops = this.props.value.stops.slice(0)
+ stops.splice(stopIdx, 1)
+
+ let changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+
+ if(stops.length === 1) {
+ changedValue = stops[0][1]
+ }
+
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ makeZoomFunction() {
+ const zoomFunc = {
+ stops: [
+ [6, this.props.value],
+ [10, this.props.value]
+ ]
+ }
+ this.props.onChange(this.props.fieldName, zoomFunc)
+ }
+
+ makeDataFunction() {
+ const dataFunc = {
+ property: "",
+ type: "categorical",
+ stops: [
+ [{zoom: 6, value: 0}, this.props.value],
+ [{zoom: 10, value: 0}, this.props.value]
+ ]
+ }
+ this.props.onChange(this.props.fieldName, dataFunc)
+ }
+
+ render() {
+ const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
+ let specField;
+
+ if (isZoomField(this.props.value)) {
+ specField = (
+
+ )
+ }
+ else if (isDataField(this.props.value)) {
+ specField = (
+
+ )
+ }
+ else {
+ specField = (
+
+ )
+ }
+
+ return
+ {specField}
+
+ }
+}
+
diff --git a/src/components/fields/PropertyGroup.jsx b/src/components/fields/PropertyGroup.jsx
index 8de8b34..5d3c72c 100644
--- a/src/components/fields/PropertyGroup.jsx
+++ b/src/components/fields/PropertyGroup.jsx
@@ -1,6 +1,7 @@
import React from 'react'
+import PropTypes from 'prop-types'
-import ZoomSpecField from './ZoomSpecField'
+import FunctionSpecField from './FunctionSpecField'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
/** Extract field spec by {@fieldName} from the {@layerType} in the
@@ -35,10 +36,10 @@ function getGroupName(spec, layerType, fieldName) {
export default class PropertyGroup extends React.Component {
static propTypes = {
- layer: React.PropTypes.object.isRequired,
- groupFields: React.PropTypes.array.isRequired,
- onChange: React.PropTypes.func.isRequired,
- spec: React.PropTypes.object.isRequired,
+ layer: PropTypes.object.isRequired,
+ groupFields: PropTypes.array.isRequired,
+ onChange: PropTypes.func.isRequired,
+ spec: PropTypes.object.isRequired,
}
onPropertyChange(property, newValue) {
@@ -54,7 +55,7 @@ export default class PropertyGroup extends React.Component {
const layout = this.props.layer.layout || {}
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
- return
} else {
- return
+ if (this.props.fieldSpec.length) {
+ return
+ } else {
+ return
+ }
}
default: return null
}
diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx
deleted file mode 100644
index 42c838e..0000000
--- a/src/components/fields/ZoomSpecField.jsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react'
-import Color from 'color'
-
-import Button from '../Button'
-import SpecField from './SpecField'
-import NumberInput from '../inputs/NumberInput'
-import DocLabel from './DocLabel'
-import InputBlock from '../inputs/InputBlock'
-
-import AddIcon from 'react-icons/lib/md/add-circle-outline'
-import DeleteIcon from 'react-icons/lib/md/delete'
-import FunctionIcon from 'react-icons/lib/md/functions'
-
-import capitalize from 'lodash.capitalize'
-
-function isZoomField(value) {
- return typeof value === 'object' && value.stops
-}
-
-/** Supports displaying spec field for zoom function objects
- * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
- */
-export default class ZoomSpecProperty extends React.Component {
- static propTypes = {
- onChange: React.PropTypes.func.isRequired,
- fieldName: React.PropTypes.string.isRequired,
- fieldSpec: React.PropTypes.object.isRequired,
-
- value: React.PropTypes.oneOfType([
- React.PropTypes.object,
- React.PropTypes.string,
- React.PropTypes.number,
- React.PropTypes.bool,
- ]),
- }
-
- addStop() {
- const stops = this.props.value.stops.slice(0)
- const lastStop = stops[stops.length - 1]
- stops.push([lastStop[0] + 1, lastStop[1]])
-
- const changedValue = {
- ...this.props.value,
- stops: stops,
- }
-
- this.props.onChange(this.props.fieldName, changedValue)
- }
-
- deleteStop(stopIdx) {
- const stops = this.props.value.stops.slice(0)
- stops.splice(stopIdx, 1)
-
- let changedValue = {
- ...this.props.value,
- stops: stops,
- }
-
- if(stops.length === 1) {
- changedValue = stops[0][1]
- }
-
- this.props.onChange(this.props.fieldName, changedValue)
- }
-
- makeZoomFunction() {
- const zoomFunc = {
- stops: [
- [6, this.props.value],
- [10, this.props.value]
- ]
- }
- this.props.onChange(this.props.fieldName, zoomFunc)
- }
-
- changeStop(changeIdx, zoomLevel, value) {
- const stops = this.props.value.stops.slice(0)
- stops[changeIdx] = [zoomLevel, value]
- const changedValue = {
- ...this.props.value,
- stops: stops,
- }
- this.props.onChange(this.props.fieldName, changedValue)
- }
-
- renderZoomProperty() {
- const zoomFields = this.props.value.stops.map((stop, idx) => {
- const zoomLevel = stop[0]
- const value = stop[1]
- const deleteStopBtn=
-
- return
-
-
- this.changeStop(idx, changedStop, value)}
- min={0}
- max={22}
- />
-
-
- this.changeStop(idx, zoomLevel, newValue)}
- />
-
-
-
- })
-
- return
- {zoomFields}
-
-
- }
-
- renderProperty() {
- let zoomBtn = null
- if(this.props.fieldSpec['zoom-function']) {
- zoomBtn =
- }
- return
-
-
- }
-
- render() {
- const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
- return
- {isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()}
-
- }
-}
-
-function MakeZoomFunctionButton(props) {
- return
-}
-
-function DeleteStopButton(props) {
- return
-}
-
-function labelFromFieldName(fieldName) {
- let label = fieldName.split('-').slice(1).join(' ')
- return capitalize(label)
-}
diff --git a/src/components/fields/_DataProperty.jsx b/src/components/fields/_DataProperty.jsx
new file mode 100644
index 0000000..a4aefa2
--- /dev/null
+++ b/src/components/fields/_DataProperty.jsx
@@ -0,0 +1,176 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import Button from '../Button'
+import SpecField from './SpecField'
+import NumberInput from '../inputs/NumberInput'
+import StringInput from '../inputs/StringInput'
+import SelectInput from '../inputs/SelectInput'
+import DocLabel from './DocLabel'
+import InputBlock from '../inputs/InputBlock'
+
+import labelFromFieldName from './_labelFromFieldName'
+import DeleteStopButton from './_DeleteStopButton'
+
+
+export default class DataProperty extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func,
+ onDeleteStop: PropTypes.func,
+ onAddStop: PropTypes.func,
+ fieldName: PropTypes.string,
+ fieldSpec: PropTypes.object,
+ value: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.bool,
+ PropTypes.array
+ ]),
+ }
+
+ getFieldFunctionType(fieldSpec) {
+ if (fieldSpec.function === "interpolated") {
+ return "exponential"
+ }
+ if (fieldSpec.type === "number") {
+ return "interval"
+ }
+ return "categorical"
+ }
+
+ getDataFunctionTypes(functionType) {
+ if (functionType === "interpolated") {
+ return ["categorical", "interval", "exponential"]
+ }
+ else {
+ return ["categorical", "interval"]
+ }
+ }
+
+
+ changeStop(changeIdx, stopData, value) {
+ const stops = this.props.value.stops.slice(0)
+ stops[changeIdx] = [stopData, value]
+ const changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ changeDataProperty(propName, propVal) {
+ if (propVal) {
+ this.props.value[propName] = propVal
+ }
+ else {
+ delete this.props.value[propName]
+ }
+ this.props.onChange(this.props.fieldName, this.props.value)
+ }
+
+ render() {
+ if (typeof this.props.value.type === "undefined") {
+ this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
+ }
+
+ const dataFields = this.props.value.stops.map((stop, idx) => {
+ const zoomLevel = stop[0].zoom
+ const dataLevel = stop[0].value
+ const value = stop[1]
+ const deleteStopBtn =
+
+ const dataProps = {
+ label: "Data value",
+ value: dataLevel,
+ onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
+ }
+
+ let dataInput;
+ if(this.props.value.type === "categorical") {
+ dataInput =
+ }
+ else {
+ dataInput =
+ }
+
+ return
+
+ this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
+ min={0}
+ max={22}
+ />
+
+
+ {dataInput}
+
+
+ this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
+ />
+
+
+ })
+
+ return
+
+
+
+
+
+ this.changeDataProperty("property", propVal)}
+ />
+
+
+
+
+
+ this.changeDataProperty("type", propVal)}
+ options={this.getDataFunctionTypes(this.props.fieldSpec.function)}
+ />
+
+
+
+
+
+ this.changeDataProperty("default", propVal)}
+ />
+
+
+
+
+ {dataFields}
+
+
+ }
+}
diff --git a/src/components/fields/_DeleteStopButton.jsx b/src/components/fields/_DeleteStopButton.jsx
new file mode 100644
index 0000000..ef64f43
--- /dev/null
+++ b/src/components/fields/_DeleteStopButton.jsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import DocLabel from './DocLabel'
+import Button from '../Button'
+import DeleteIcon from 'react-icons/lib/md/delete'
+
+
+export default class DeleteStopButton extends React.Component {
+ static propTypes = {
+ onClick: PropTypes.func,
+ }
+
+ render() {
+ return
+ }
+}
diff --git a/src/components/fields/_FunctionButtons.jsx b/src/components/fields/_FunctionButtons.jsx
new file mode 100644
index 0000000..9e66398
--- /dev/null
+++ b/src/components/fields/_FunctionButtons.jsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import DocLabel from './DocLabel'
+import Button from '../Button'
+import FunctionIcon from 'react-icons/lib/md/functions'
+import MdInsertChart from 'react-icons/lib/md/insert-chart'
+
+
+export default class FunctionButtons extends React.Component {
+ static propTypes = {
+ fieldSpec: PropTypes.object,
+ onZoomClick: PropTypes.func,
+ onDataClick: PropTypes.func,
+ }
+
+ render() {
+ let makeZoomButton, makeDataButton
+ if (this.props.fieldSpec['zoom-function']) {
+ makeZoomButton =
+
+ if (this.props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(this.props.fieldSpec['function']) !== -1) {
+ makeDataButton =
+ }
+ return
{makeDataButton}{makeZoomButton}
+ }
+ else {
+ return null
+ }
+ }
+}
diff --git a/src/components/fields/_SpecProperty.jsx b/src/components/fields/_SpecProperty.jsx
new file mode 100644
index 0000000..0e0fd9f
--- /dev/null
+++ b/src/components/fields/_SpecProperty.jsx
@@ -0,0 +1,34 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import SpecField from './SpecField'
+import FunctionButtons from './_FunctionButtons'
+import InputBlock from '../inputs/InputBlock'
+
+import labelFromFieldName from './_labelFromFieldName'
+
+
+export default class SpecProperty extends React.Component {
+ static propTypes = {
+ onZoomClick: PropTypes.func.isRequired,
+ onDataClick: PropTypes.func.isRequired,
+ fieldName: PropTypes.string,
+ fieldSpec: PropTypes.object
+ }
+
+ render() {
+ const functionBtn =
+
+ return
+
+
+ }
+}
diff --git a/src/components/fields/_ZoomProperty.jsx b/src/components/fields/_ZoomProperty.jsx
new file mode 100644
index 0000000..5659b14
--- /dev/null
+++ b/src/components/fields/_ZoomProperty.jsx
@@ -0,0 +1,161 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import Button from '../Button'
+import SpecField from './SpecField'
+import NumberInput from '../inputs/NumberInput'
+import InputBlock from '../inputs/InputBlock'
+
+import DeleteStopButton from './_DeleteStopButton'
+import labelFromFieldName from './_labelFromFieldName'
+
+import docUid from '../../libs/document-uid'
+import sortNumerically from '../../libs/sort-numerically'
+
+
+export default class ZoomProperty extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func,
+ onDeleteStop: PropTypes.func,
+ onAddStop: PropTypes.func,
+ fieldName: PropTypes.string,
+ fieldSpec: PropTypes.object,
+ value: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.bool,
+ PropTypes.array
+ ]),
+ }
+
+
+ constructor() {
+ super()
+ this.state = {
+ refs: {}
+ }
+ }
+
+ componentWillMount() {
+ this.setState({
+ refs: this.setStopRefs(this.props)
+ })
+ }
+
+ /**
+ * We cache a reference for each stop by its index.
+ *
+ * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus.
+ */
+ setStopRefs(props) {
+ // This is initialsed below only if required to improved performance.
+ let newRefs;
+
+ if(props.value && props.value.stops) {
+ props.value.stops.forEach((val, idx) => {
+ if(!this.state.refs.hasOwnProperty(idx)) {
+ if(!newRefs) {
+ newRefs = {...this.state.refs};
+ }
+ newRefs[idx] = docUid("stop-");
+ }
+ })
+ }
+
+ return newRefs;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const newRefs = this.setStopRefs(nextProps);
+ if(newRefs) {
+ this.setState({
+ refs: newRefs
+ })
+ }
+ }
+
+ // Order the stops altering the refs to reflect their new position.
+ orderStopsByZoom(stops) {
+ const mappedWithRef = stops
+ .map((stop, idx) => {
+ return {
+ ref: this.state.refs[idx],
+ data: stop
+ }
+ })
+ // Sort by zoom
+ .sort((a, b) => sortNumerically(a.data[0], b.data[0]));
+
+ // Fetch the new position of the stops
+ const newRefs = {};
+ mappedWithRef
+ .forEach((stop, idx) =>{
+ newRefs[idx] = stop.ref;
+ })
+
+ this.setState({
+ refs: newRefs
+ });
+
+ return mappedWithRef.map((item) => item.data);
+ }
+
+ changeZoomStop(changeIdx, stopData, value) {
+ const stops = this.props.value.stops.slice(0);
+ stops[changeIdx] = [stopData, value];
+
+ const orderedStops = this.orderStopsByZoom(stops);
+
+ const changedValue = {
+ ...this.props.value,
+ stops: orderedStops
+ }
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ render() {
+ const zoomFields = this.props.value.stops.map((stop, idx) => {
+ const zoomLevel = stop[0]
+ const key = this.state.refs[idx];
+ const value = stop[1]
+ const deleteStopBtn=
+
+ return
+
+
+ this.changeZoomStop(idx, changedStop, value)}
+ min={0}
+ max={22}
+ />
+
+
+ this.changeZoomStop(idx, zoomLevel, newValue)}
+ />
+
+
+
+ });
+
+ return
+ {zoomFields}
+
+
+ }
+}
diff --git a/src/components/fields/_labelFromFieldName.js b/src/components/fields/_labelFromFieldName.js
new file mode 100644
index 0000000..fea405f
--- /dev/null
+++ b/src/components/fields/_labelFromFieldName.js
@@ -0,0 +1,6 @@
+import capitalize from 'lodash.capitalize'
+
+export default function labelFromFieldName(fieldName) {
+ let label = fieldName.split('-').slice(1).join(' ')
+ return capitalize(label)
+}
diff --git a/src/components/filter/FilterEditor.jsx b/src/components/filter/FilterEditor.jsx
index fcfe7d8..73cb2f1 100644
--- a/src/components/filter/FilterEditor.jsx
+++ b/src/components/filter/FilterEditor.jsx
@@ -1,7 +1,8 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { combiningFilterOps } from '../../libs/filterops.js'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import DocLabel from '../fields/DocLabel'
import SelectInput from '../inputs/SelectInput'
import SingleFilterEditor from './SingleFilterEditor'
@@ -26,9 +27,9 @@ function hasNestedCombiningFilter(filter) {
export default class CombiningFilterEditor extends React.Component {
static propTypes = {
/** Properties of the vector layer and the available fields */
- properties: React.PropTypes.object,
- filter: React.PropTypes.array,
- onChange: React.PropTypes.func.isRequired,
+ properties: PropTypes.object,
+ filter: PropTypes.array,
+ onChange: PropTypes.func.isRequired,
}
// Convert filter to combining filter
@@ -91,7 +92,7 @@ export default class CombiningFilterEditor extends React.Component {
case 'raster': return
+ case 'hillshade': return
+ case 'heatmap': return
case 'fill': return
case 'background': return
case 'line': return
diff --git a/src/components/icons/LineIcon.jsx b/src/components/icons/LineIcon.jsx
index ab5acbd..67e5403 100644
--- a/src/components/icons/LineIcon.jsx
+++ b/src/components/icons/LineIcon.jsx
@@ -6,7 +6,7 @@ export default class FillIcon extends React.Component {
render() {
return (
-
+
)
}
diff --git a/src/components/icons/SymbolIcon.jsx b/src/components/icons/SymbolIcon.jsx
index 941d9b9..961ba4e 100644
--- a/src/components/icons/SymbolIcon.jsx
+++ b/src/components/icons/SymbolIcon.jsx
@@ -6,8 +6,8 @@ export default class SymbolIcon extends React.Component {
render() {
return (
-
-
+
+
)
diff --git a/src/components/inputs/ArrayInput.jsx b/src/components/inputs/ArrayInput.jsx
index cbf3a25..69a01d5 100644
--- a/src/components/inputs/ArrayInput.jsx
+++ b/src/components/inputs/ArrayInput.jsx
@@ -1,14 +1,15 @@
import React from 'react'
+import PropTypes from 'prop-types'
import StringInput from './StringInput'
import NumberInput from './NumberInput'
class ArrayInput extends React.Component {
static propTypes = {
- value: React.PropTypes.array,
- type: React.PropTypes.string,
- length: React.PropTypes.number,
- default: React.PropTypes.array,
- onChange: React.PropTypes.func,
+ value: PropTypes.array,
+ type: PropTypes.string,
+ length: PropTypes.number,
+ default: PropTypes.array,
+ onChange: PropTypes.func,
}
changeValue(idx, newValue) {
diff --git a/src/components/inputs/AutocompleteInput.jsx b/src/components/inputs/AutocompleteInput.jsx
index c2b4c1f..a81ab51 100644
--- a/src/components/inputs/AutocompleteInput.jsx
+++ b/src/components/inputs/AutocompleteInput.jsx
@@ -1,13 +1,17 @@
import React from 'react'
+import PropTypes from 'prop-types'
import classnames from 'classnames'
import Autocomplete from 'react-autocomplete'
+const MAX_HEIGHT = 140;
+
class AutocompleteInput extends React.Component {
static propTypes = {
- value: React.PropTypes.string,
- options: React.PropTypes.array,
- onChange: React.PropTypes.func,
+ value: PropTypes.string,
+ options: PropTypes.array,
+ onChange: PropTypes.func,
+ keepMenuWithinWindowBounds: PropTypes.bool
}
static defaultProps = {
@@ -15,35 +19,73 @@ class AutocompleteInput extends React.Component {
options: [],
}
- render() {
- const AutocompleteMenu = (items, value, style) =>
+ constructor(props) {
+ super(props);
+ this.state = {
+ maxHeight: MAX_HEIGHT
+ };
+ }
- return
{
+ this.autocompleteMenuEl = el;
}}
- renderMenu={AutocompleteMenu}
- inputProps={{
- className: "maputnik-string"
- }}
- value={this.props.value}
- items={this.props.options}
- getItemValue={(item) => item[0]}
- onSelect={v => this.props.onChange(v)}
- onChange={(e, v) => this.props.onChange(v)}
- renderItem={(item, isHighlighted) => (
-
- {item[1]}
-
- )}
- />
+ >
+ item[0]}
+ onSelect={v => this.props.onChange(v)}
+ onChange={(e, v) => this.props.onChange(v)}
+ shouldItemRender={(item, value) => {
+ return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
+ }}
+ renderItem={(item, isHighlighted) => (
+
+ {item[1]}
+
+ )}
+ />
+
}
}
diff --git a/src/components/inputs/CheckboxInput.jsx b/src/components/inputs/CheckboxInput.jsx
index cfe5b3d..94e8902 100644
--- a/src/components/inputs/CheckboxInput.jsx
+++ b/src/components/inputs/CheckboxInput.jsx
@@ -1,10 +1,11 @@
import React from 'react'
+import PropTypes from 'prop-types'
class CheckboxInput extends React.Component {
static propTypes = {
- value: React.PropTypes.bool.isRequired,
- style: React.PropTypes.object,
- onChange: React.PropTypes.func,
+ value: PropTypes.bool.isRequired,
+ style: PropTypes.object,
+ onChange: PropTypes.func,
}
render() {
diff --git a/src/components/inputs/DynamicArrayInput.jsx b/src/components/inputs/DynamicArrayInput.jsx
new file mode 100644
index 0000000..5596e24
--- /dev/null
+++ b/src/components/inputs/DynamicArrayInput.jsx
@@ -0,0 +1,106 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import StringInput from './StringInput'
+import NumberInput from './NumberInput'
+import Button from '../Button'
+import DeleteIcon from 'react-icons/lib/md/delete'
+import DocLabel from '../fields/DocLabel'
+
+
+class DynamicArrayInput extends React.Component {
+ static propTypes = {
+ value: PropTypes.array,
+ type: PropTypes.string,
+ default: PropTypes.array,
+ onChange: PropTypes.func,
+ style: PropTypes.object,
+ }
+
+ changeValue(idx, newValue) {
+ console.log(idx, newValue)
+ const values = this.values.slice(0)
+ values[idx] = newValue
+ this.props.onChange(values)
+ }
+
+ get values() {
+ return this.props.value || this.props.default || []
+ }
+
+ addValue() {
+ const values = this.values.slice(0)
+ if (this.props.type === 'number') {
+ values.push(0)
+ } else {
+ values.push("")
+ }
+
+
+ this.props.onChange(values)
+ }
+
+ deleteValue(valueIdx) {
+ const values = this.values.slice(0)
+ values.splice(valueIdx, 1)
+
+ this.props.onChange(values)
+ }
+
+ render() {
+ const inputs = this.values.map((v, i) => {
+ const deleteValueBtn=
+ const input = this.props.type === 'number'
+ ?
+ :
+
+ return
+
+ {deleteValueBtn}
+
+
+ {input}
+
+
+ })
+
+ return
+ {inputs}
+
+
+ }
+}
+
+class DeleteValueButton extends React.Component {
+ static propTypes = {
+ onClick: PropTypes.func,
+ }
+
+ render() {
+ return
+ }
+}
+
+export default DynamicArrayInput
diff --git a/src/components/inputs/FontInput.jsx b/src/components/inputs/FontInput.jsx
index fa1b372..8c52083 100644
--- a/src/components/inputs/FontInput.jsx
+++ b/src/components/inputs/FontInput.jsx
@@ -1,12 +1,14 @@
import React from 'react'
+import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput'
class FontInput extends React.Component {
static propTypes = {
- value: React.PropTypes.array.isRequired,
- fonts: React.PropTypes.array,
- style: React.PropTypes.object,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.array.isRequired,
+ default: PropTypes.array,
+ fonts: PropTypes.array,
+ style: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
}
static defaultProps = {
diff --git a/src/components/inputs/IconInput.jsx b/src/components/inputs/IconInput.jsx
index 9f6bc33..daff7e7 100644
--- a/src/components/inputs/IconInput.jsx
+++ b/src/components/inputs/IconInput.jsx
@@ -1,13 +1,14 @@
import React from 'react'
+import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput'
class IconInput extends React.Component {
static propTypes = {
- value: React.PropTypes.array,
- icons: React.PropTypes.array,
- style: React.PropTypes.object,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.array,
+ icons: PropTypes.array,
+ style: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
}
static defaultProps = {
diff --git a/src/components/inputs/InputBlock.jsx b/src/components/inputs/InputBlock.jsx
index b537a52..8fff767 100644
--- a/src/components/inputs/InputBlock.jsx
+++ b/src/components/inputs/InputBlock.jsx
@@ -1,18 +1,20 @@
import React from 'react'
+import PropTypes from 'prop-types'
import classnames from 'classnames'
import DocLabel from '../fields/DocLabel'
/** Wrap a component with a label */
class InputBlock extends React.Component {
static propTypes = {
- label: React.PropTypes.oneOfType([
- React.PropTypes.string,
- React.PropTypes.element,
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.element,
]).isRequired,
- doc: React.PropTypes.string,
- action: React.PropTypes.element,
- children: React.PropTypes.element.isRequired,
- style: React.PropTypes.object,
+ doc: PropTypes.string,
+ action: PropTypes.element,
+ children: PropTypes.node.isRequired,
+ style: PropTypes.object,
+ onChange: PropTypes.func,
}
onChange(e) {
diff --git a/src/components/inputs/MultiButtonInput.jsx b/src/components/inputs/MultiButtonInput.jsx
index 20265be..4b8891d 100644
--- a/src/components/inputs/MultiButtonInput.jsx
+++ b/src/components/inputs/MultiButtonInput.jsx
@@ -1,12 +1,13 @@
import React from 'react'
+import PropTypes from 'prop-types'
import classnames from 'classnames'
import Button from '../Button'
class MultiButtonInput extends React.Component {
static propTypes = {
- value: React.PropTypes.string.isRequired,
- options: React.PropTypes.array.isRequired,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.string.isRequired,
+ options: PropTypes.array.isRequired,
+ onChange: PropTypes.func.isRequired,
}
render() {
diff --git a/src/components/inputs/NumberInput.jsx b/src/components/inputs/NumberInput.jsx
index 9ae6dd4..c57af00 100644
--- a/src/components/inputs/NumberInput.jsx
+++ b/src/components/inputs/NumberInput.jsx
@@ -1,12 +1,13 @@
import React from 'react'
+import PropTypes from 'prop-types'
class NumberInput extends React.Component {
static propTypes = {
- value: React.PropTypes.number,
- default: React.PropTypes.number,
- min: React.PropTypes.number,
- max: React.PropTypes.number,
- onChange: React.PropTypes.func,
+ value: PropTypes.number,
+ default: PropTypes.number,
+ min: PropTypes.number,
+ max: PropTypes.number,
+ onChange: PropTypes.func,
}
constructor(props) {
diff --git a/src/components/inputs/SelectInput.jsx b/src/components/inputs/SelectInput.jsx
index 13a8b27..145c78f 100644
--- a/src/components/inputs/SelectInput.jsx
+++ b/src/components/inputs/SelectInput.jsx
@@ -1,11 +1,12 @@
import React from 'react'
+import PropTypes from 'prop-types'
class SelectInput extends React.Component {
static propTypes = {
- value: React.PropTypes.string.isRequired,
- options: React.PropTypes.array.isRequired,
- style: React.PropTypes.object,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.string.isRequired,
+ options: PropTypes.array.isRequired,
+ style: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
}
diff --git a/src/components/inputs/StringInput.jsx b/src/components/inputs/StringInput.jsx
index 5b7753e..314dde8 100644
--- a/src/components/inputs/StringInput.jsx
+++ b/src/components/inputs/StringInput.jsx
@@ -1,11 +1,13 @@
import React from 'react'
+import PropTypes from 'prop-types'
class StringInput extends React.Component {
static propTypes = {
- value: React.PropTypes.string,
- style: React.PropTypes.object,
- default: React.PropTypes.string,
- onChange: React.PropTypes.func,
+ value: PropTypes.string,
+ style: PropTypes.object,
+ default: PropTypes.string,
+ onChange: PropTypes.func,
+ multi: PropTypes.bool,
}
constructor(props) {
diff --git a/src/components/layers/Collapser.jsx b/src/components/layers/Collapser.jsx
index 19410a5..186c441 100644
--- a/src/components/layers/Collapser.jsx
+++ b/src/components/layers/Collapser.jsx
@@ -1,11 +1,12 @@
import React from 'react'
+import PropTypes from 'prop-types'
import CollapseOpenIcon from 'react-icons/lib/md/arrow-drop-down'
import CollapseCloseIcon from 'react-icons/lib/md/arrow-drop-up'
export default class Collapser extends React.Component {
static propTypes = {
- isCollapsed: React.PropTypes.bool.isRequired,
- style: React.PropTypes.object,
+ isCollapsed: PropTypes.bool.isRequired,
+ style: PropTypes.object,
}
render() {
diff --git a/src/components/layers/CommentBlock.jsx b/src/components/layers/CommentBlock.jsx
index 996ca1c..961bd7d 100644
--- a/src/components/layers/CommentBlock.jsx
+++ b/src/components/layers/CommentBlock.jsx
@@ -1,16 +1,17 @@
import React from 'react'
+import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
class MetadataBlock extends React.Component {
static propTypes = {
- value: React.PropTypes.string,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
}
render() {
- return
+ return
this.onCodeUpdate(value)}
onFocusChange={focused => focused ? true : this.resetValue()}
options={codeMirrorOptions}
/>
diff --git a/src/components/layers/LayerEditor.jsx b/src/components/layers/LayerEditor.jsx
index 4494b85..4764986 100644
--- a/src/components/layers/LayerEditor.jsx
+++ b/src/components/layers/LayerEditor.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import JSONEditor from './JSONEditor'
import FilterEditor from '../filter/FilterEditor'
@@ -43,12 +44,12 @@ function layoutGroups(layerType) {
/** Layer editor supporting multiple types of layers. */
export default class LayerEditor extends React.Component {
static propTypes = {
- layer: React.PropTypes.object.isRequired,
- sources: React.PropTypes.object,
- vectorLayers: React.PropTypes.object,
- spec: React.PropTypes.object.isRequired,
- onLayerChanged: React.PropTypes.func,
- onLayerIdChange: React.PropTypes.func,
+ layer: PropTypes.object.isRequired,
+ sources: PropTypes.object,
+ vectorLayers: PropTypes.object,
+ spec: PropTypes.object.isRequired,
+ onLayerChanged: PropTypes.func,
+ onLayerIdChange: PropTypes.func,
}
static defaultProps = {
@@ -58,7 +59,7 @@ export default class LayerEditor extends React.Component {
}
static childContextTypes = {
- reactIconBase: React.PropTypes.object
+ reactIconBase: PropTypes.object
}
constructor(props) {
@@ -116,6 +117,11 @@ export default class LayerEditor extends React.Component {
comment = this.props.layer.metadata['maputnik:comment']
}
+ let sourceLayerIds;
+ if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
+ sourceLayerIds = this.props.sources[this.props.layer.source].layers;
+ }
+
switch(type) {
case 'layer': return
this.changeProperty(null, 'source', v)}
/>
}
- {this.props.layer.type !== 'raster' && this.props.layer.type !== 'background' && this.changeProperty(null, 'source-layer', v)}
/>
diff --git a/src/components/layers/LayerEditorGroup.jsx b/src/components/layers/LayerEditorGroup.jsx
index 5c4f86f..7c82fc1 100644
--- a/src/components/layers/LayerEditorGroup.jsx
+++ b/src/components/layers/LayerEditorGroup.jsx
@@ -1,13 +1,14 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Collapse from 'react-collapse'
import Collapser from './Collapser'
export default class LayerEditorGroup extends React.Component {
static propTypes = {
- title: React.PropTypes.string.isRequired,
- isActive: React.PropTypes.bool.isRequired,
- children: React.PropTypes.element.isRequired,
- onActiveToggle: React.PropTypes.func.isRequired
+ title: PropTypes.string.isRequired,
+ isActive: PropTypes.bool.isRequired,
+ children: PropTypes.element.isRequired,
+ onActiveToggle: PropTypes.func.isRequired
}
render() {
diff --git a/src/components/layers/LayerIdBlock.jsx b/src/components/layers/LayerIdBlock.jsx
index 7cdc9d2..77abb11 100644
--- a/src/components/layers/LayerIdBlock.jsx
+++ b/src/components/layers/LayerIdBlock.jsx
@@ -1,17 +1,18 @@
import React from 'react'
+import PropTypes from 'prop-types'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
class LayerIdBlock extends React.Component {
static propTypes = {
- value: React.PropTypes.string.isRequired,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
}
render() {
- return
+ return
{
+ const groupPrefix = layerPrefix(layers[0].id)
+ const lookupKey = [groupPrefix, idx].join('-')
+
+
+ if (layers.length > 1) {
+ newGroups[lookupKey] = this.state.areAllGroupsExpanded
+ }
+
+ layers.forEach((layer) => {
+ idx += 1
+ })
+ });
+
+ this.setState({
+ collapsedGroups: newGroups,
+ areAllGroupsExpanded: !this.state.areAllGroupsExpanded
+ })
+ }
+
groupedLayers() {
const groups = []
for (let i = 0; i < this.props.layers.length; i++) {
@@ -137,7 +164,7 @@ class LayerListContainer extends React.Component {
const grp =
listItems.push(grp)
@@ -145,9 +172,10 @@ class LayerListContainer extends React.Component {
layers.forEach((layer, idxInGroup) => {
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
+
const listItem = 1 && this.isCollapsed(groupPrefix, groupIdx),
+ 'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1
})}
index={idx}
@@ -177,11 +205,24 @@ class LayerListContainer extends React.Component {
Layers
-
+
+
{listItems}
diff --git a/src/components/layers/LayerListGroup.jsx b/src/components/layers/LayerListGroup.jsx
index 09476b8..ab36ab0 100644
--- a/src/components/layers/LayerListGroup.jsx
+++ b/src/components/layers/LayerListGroup.jsx
@@ -1,16 +1,16 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Collapser from './Collapser'
-export default class LayerEditorGroup extends React.Component {
+export default class LayerListGroup extends React.Component {
static propTypes = {
- title: React.PropTypes.string.isRequired,
- children: React.PropTypes.element.isRequired,
- isActive: React.PropTypes.bool.isRequired,
- onActiveToggle: React.PropTypes.func.isRequired
+ title: PropTypes.string.isRequired,
+ isActive: PropTypes.bool.isRequired,
+ onActiveToggle: PropTypes.func.isRequired
}
render() {
- return
+ return
-
this.props.onActiveToggle(!this.props.isActive)}
>
@@ -21,6 +21,6 @@ export default class LayerEditorGroup extends React.Component {
isCollapsed={this.props.isActive}
/>
-
+
}
}
diff --git a/src/components/layers/LayerListItem.jsx b/src/components/layers/LayerListItem.jsx
index 6b1420e..4d31cf1 100644
--- a/src/components/layers/LayerListItem.jsx
+++ b/src/components/layers/LayerListItem.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Color from 'color'
import classnames from 'classnames'
@@ -30,8 +31,8 @@ class LayerTypeDragHandle extends React.Component {
class IconAction extends React.Component {
static propTypes = {
- action: React.PropTypes.string.isRequired,
- onClick: React.PropTypes.func.isRequired,
+ action: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired,
}
renderIcon() {
@@ -57,16 +58,16 @@ class IconAction extends React.Component {
@SortableElement
class LayerListItem extends React.Component {
static propTypes = {
- layerId: React.PropTypes.string.isRequired,
- layerType: React.PropTypes.string.isRequired,
- isSelected: React.PropTypes.bool,
- visibility: React.PropTypes.string,
- className: React.PropTypes.string,
+ layerId: PropTypes.string.isRequired,
+ layerType: PropTypes.string.isRequired,
+ isSelected: PropTypes.bool,
+ visibility: PropTypes.string,
+ className: PropTypes.string,
- onLayerSelect: React.PropTypes.func.isRequired,
- onLayerCopy: React.PropTypes.func,
- onLayerDestroy: React.PropTypes.func,
- onLayerVisibilityToggle: React.PropTypes.func,
+ onLayerSelect: PropTypes.func.isRequired,
+ onLayerCopy: PropTypes.func,
+ onLayerDestroy: PropTypes.func,
+ onLayerVisibilityToggle: PropTypes.func,
}
static defaultProps = {
@@ -78,7 +79,7 @@ class LayerListItem extends React.Component {
}
static childContextTypes = {
- reactIconBase: React.PropTypes.object
+ reactIconBase: PropTypes.object
}
getChildContext() {
diff --git a/src/components/layers/LayerSourceBlock.jsx b/src/components/layers/LayerSourceBlock.jsx
index ea60b8a..e2070a4 100644
--- a/src/components/layers/LayerSourceBlock.jsx
+++ b/src/components/layers/LayerSourceBlock.jsx
@@ -1,6 +1,7 @@
import React from 'react'
+import PropTypes from 'prop-types'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
@@ -8,9 +9,9 @@ import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceBlock extends React.Component {
static propTypes = {
- value: React.PropTypes.string,
- onChange: React.PropTypes.func,
- sourceIds: React.PropTypes.array,
+ value: PropTypes.string,
+ onChange: PropTypes.func,
+ sourceIds: PropTypes.array,
}
static defaultProps = {
@@ -19,7 +20,7 @@ class LayerSourceBlock extends React.Component {
}
render() {
- return
+ return
{},
sourceLayerIds: [],
+ isFixed: false
}
render() {
- return
+ return
[l, l])}
diff --git a/src/components/layers/LayerTypeBlock.jsx b/src/components/layers/LayerTypeBlock.jsx
index 8286900..4b203f7 100644
--- a/src/components/layers/LayerTypeBlock.jsx
+++ b/src/components/layers/LayerTypeBlock.jsx
@@ -1,17 +1,18 @@
import React from 'react'
+import PropTypes from 'prop-types'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import SelectInput from '../inputs/SelectInput'
class LayerTypeBlock extends React.Component {
static propTypes = {
- value: React.PropTypes.string.isRequired,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
}
render() {
- return
+ return
+ return
}
diff --git a/src/components/layers/MinZoomBlock.jsx b/src/components/layers/MinZoomBlock.jsx
index 9585d67..d5c3f9c 100644
--- a/src/components/layers/MinZoomBlock.jsx
+++ b/src/components/layers/MinZoomBlock.jsx
@@ -1,23 +1,24 @@
import React from 'react'
+import PropTypes from 'prop-types'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'
class MinZoomBlock extends React.Component {
static propTypes = {
- value: React.PropTypes.number.isRequired,
- onChange: React.PropTypes.func.isRequired,
+ value: PropTypes.number,
+ onChange: PropTypes.func.isRequired,
}
render() {
- return
+ return
}
diff --git a/src/components/map/FeatureLayerPopup.jsx b/src/components/map/FeatureLayerPopup.jsx
index 26099b3..5d14d01 100644
--- a/src/components/map/FeatureLayerPopup.jsx
+++ b/src/components/map/FeatureLayerPopup.jsx
@@ -1,19 +1,38 @@
import React from 'react'
+import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import LayerIcon from '../icons/LayerIcon'
-
function groupFeaturesBySourceLayer(features) {
const sources = {}
+
+ let returnedFeatures = {};
+
features.forEach(feature => {
- sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
- sources[feature.layer['source-layer']].push(feature)
+ if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
+ returnedFeatures[feature.layer.id]++
+
+ const featureObject = sources[feature.layer['source-layer']].find(f => f.layer.id === feature.layer.id)
+
+ featureObject.counter = returnedFeatures[feature.layer.id]
+ } else {
+ sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
+ sources[feature.layer['source-layer']].push(feature)
+
+ returnedFeatures[feature.layer.id] = 1
+ }
})
+
return sources
}
class FeatureLayerPopup extends React.Component {
+ static propTypes = {
+ onLayerSelect: PropTypes.func.isRequired,
+ features: PropTypes.array
+ }
+
render() {
const sources = groupFeaturesBySourceLayer(this.props.features)
@@ -22,6 +41,9 @@ class FeatureLayerPopup extends React.Component {
return
})
return
diff --git a/src/components/map/FeaturePropertyPopup.jsx b/src/components/map/FeaturePropertyPopup.jsx
index c83c64d..5d5c9e5 100644
--- a/src/components/map/FeaturePropertyPopup.jsx
+++ b/src/components/map/FeaturePropertyPopup.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
@@ -22,7 +23,7 @@ function renderProperties(feature) {
function renderFeature(feature) {
return
-
{feature.layer['source-layer']}
+
{feature.layer['source-layer']}{feature.inspectModeCounter && × {feature.inspectModeCounter}}
@@ -30,10 +31,36 @@ function renderFeature(feature) {
}
+function removeDuplicatedFeatures(features) {
+ let uniqueFeatures = [];
+
+ features.forEach(feature => {
+ const featureIndex = uniqueFeatures.findIndex(feature2 => {
+ return feature.layer['source-layer'] === feature2.layer['source-layer']
+ && JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
+ })
+
+ if(featureIndex === -1) {
+ uniqueFeatures.push(feature)
+ } else {
+ if(uniqueFeatures[featureIndex].hasOwnProperty('counter')) {
+ uniqueFeatures[featureIndex].inspectModeCounter++
+ } else {
+ uniqueFeatures[featureIndex].inspectModeCounter = 2
+ }
+ }
+ })
+
+ return uniqueFeatures
+}
+
class FeaturePropertyPopup extends React.Component {
+ static propTypes = {
+ features: PropTypes.array
+ }
render() {
- const features = this.props.features
+ const features = removeDuplicatedFeatures(this.props.features)
return
{features.map(renderFeature)}
diff --git a/src/components/map/MapboxGlMap.jsx b/src/components/map/MapboxGlMap.jsx
index b26bca3..5dc4e0d 100644
--- a/src/components/map/MapboxGlMap.jsx
+++ b/src/components/map/MapboxGlMap.jsx
@@ -1,25 +1,20 @@
import React from 'react'
+import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
-import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js'
+import MapboxGl from 'mapbox-gl'
import MapboxInspect from 'mapbox-gl-inspect'
import FeatureLayerPopup from './FeatureLayerPopup'
import FeaturePropertyPopup from './FeaturePropertyPopup'
-import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color'
import style from '../../libs/style.js'
import tokens from '../../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors'
import Color from 'color'
+import ZoomControl from '../../libs/zoomcontrol'
import { colorHighlightedLayer } from '../../libs/highlight'
import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css'
import '../../libs/mapbox-rtl'
-function renderLayerPopup(features) {
- var mountNode = document.createElement('div');
- ReactDOM.render(
, mountNode)
- return mountNode.innerHTML;
-}
-
function renderPropertyPopup(features) {
var mountNode = document.createElement('div');
ReactDOM.render(
, mountNode)
@@ -43,7 +38,7 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const sources = {}
Object.keys(originalMapStyle.sources).forEach(sourceId => {
const source = originalMapStyle.sources[sourceId]
- if(source.type !== 'raster') {
+ if(source.type !== 'raster' && source.type !== 'raster-dem') {
sources[sourceId] = source
}
})
@@ -58,15 +53,17 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
export default class MapboxGlMap extends React.Component {
static propTypes = {
- onDataChange: React.PropTypes.func,
- mapStyle: React.PropTypes.object.isRequired,
- inspectModeEnabled: React.PropTypes.bool.isRequired,
- highlightedLayer: React.PropTypes.object,
+ onDataChange: PropTypes.func,
+ onLayerSelect: PropTypes.func.isRequired,
+ mapStyle: PropTypes.object.isRequired,
+ inspectModeEnabled: PropTypes.bool.isRequired,
+ highlightedLayer: PropTypes.object,
}
static defaultProps = {
onMapLoaded: () => {},
onDataChange: () => {},
+ onLayerSelect: () => {},
mapboxAccessToken: tokens.mapbox,
}
@@ -110,6 +107,9 @@ export default class MapboxGlMap extends React.Component {
hash: true,
})
+ const zoom = new ZoomControl;
+ map.addControl(zoom, 'top-right');
+
const nav = new MapboxGl.NavigationControl();
map.addControl(nav, 'top-right');
@@ -129,7 +129,9 @@ export default class MapboxGlMap extends React.Component {
if(this.props.inspectModeEnabled) {
return renderPropertyPopup(features)
} else {
- return renderLayerPopup(features)
+ var mountNode = document.createElement('div');
+ ReactDOM.render(
, mountNode)
+ return mountNode
}
}
})
diff --git a/src/components/map/OpenLayers3Map.jsx b/src/components/map/OpenLayers3Map.jsx
index 28fa73a..bb67432 100644
--- a/src/components/map/OpenLayers3Map.jsx
+++ b/src/components/map/OpenLayers3Map.jsx
@@ -1,59 +1,17 @@
import React from 'react'
+import PropTypes from 'prop-types'
import style from '../../libs/style.js'
import isEqual from 'lodash.isequal'
import { loadJSON } from '../../libs/urlopen'
+import 'ol/ol.css'
-function suitableVectorSource(mapStyle) {
- const sources = Object.keys(mapStyle.sources)
- .map(sourceId => {
- return {
- id: sourceId,
- source: mapStyle.sources[sourceId]
- }
- })
- .filter(({source}) => source.type === 'vector')
- return sources[0]
-}
-
-function toVectorLayer(source, tilegrid, cb) {
- function newMVTLayer(tileUrl) {
- const ol = require('openlayers')
- return new ol.layer.VectorTile({
- source: new ol.source.VectorTile({
- format: new ol.format.MVT(),
- tileGrid: tilegrid,
- tilePixelRatio: 8,
- url: tileUrl
- })
- })
- }
-
- if(!source.tiles) {
- sourceFromTileJSON(source.url, tileSource => {
- cb(newMVTLayer(tileSource.tiles[0]))
- })
- } else {
- cb(newMVTLayer(source.tiles[0]))
- }
-}
-
-function sourceFromTileJSON(url, cb) {
- loadJSON(url, null, tilejson => {
- if(!tilejson) return
- cb({
- type: 'vector',
- tiles: tilejson.tiles,
- minzoom: tilejson.minzoom,
- maxzoom: tilejson.maxzoom,
- })
- })
-}
class OpenLayers3Map extends React.Component {
static propTypes = {
- onDataChange: React.PropTypes.func,
- mapStyle: React.PropTypes.object.isRequired,
- accessToken: React.PropTypes.string,
+ onDataChange: PropTypes.func,
+ mapStyle: PropTypes.object.isRequired,
+ accessToken: PropTypes.string,
+ style: PropTypes.object,
}
static defaultProps = {
@@ -63,48 +21,17 @@ class OpenLayers3Map extends React.Component {
constructor(props) {
super(props)
- this.tilegrid = null
- this.resolutions = null
- this.layer = null
this.map = null
}
updateStyle(newMapStyle) {
- const oldSource = suitableVectorSource(this.props.mapStyle)
- const newSource = suitableVectorSource(newMapStyle)
- const resolutions = this.resolutions
-
- function setStyleFunc(map, layer) {
- const olms = require('ol-mapbox-style')
- const styleFunc = olms.getStyleFunction(newMapStyle, newSource.id, resolutions)
- layer.setStyle(styleFunc)
- //NOTE: We need to mark the source as changed in order
- //to trigger a rerender
- layer.getSource().changed()
- map.render()
- }
-
- if(newSource) {
- if(this.layer && !isEqual(oldSource, newSource)) {
- this.map.removeLayer(this.layer)
- this.layer = null
- }
-
- if(!this.layer) {
- toVectorLayer(newSource.source, this.tilegrid, vectorLayer => {
- this.layer = vectorLayer
- this.map.addLayer(this.layer)
- setStyleFunc(this.map, this.layer)
- })
- } else {
- setStyleFunc(this.map, this.layer)
- }
- }
+ const olms = require('ol-mapbox-style');
+ const styleFunc = olms.apply(this.map, newMapStyle)
}
componentWillReceiveProps(nextProps) {
- require.ensure(["openlayers", "ol-mapbox-style"], () => {
- if(!this.map || !this.resolutions) return
+ require.ensure(["ol", "ol-mapbox-style"], () => {
+ if(!this.map) return
this.updateStyle(nextProps.mapStyle)
})
}
@@ -112,24 +39,22 @@ class OpenLayers3Map extends React.Component {
componentDidMount() {
//Load OpenLayers dynamically once we need it
//TODO: Make this more convenient
- require.ensure(["openlayers", "ol-mapbox-style"], ()=> {
+ require.ensure(["ol", "ol/map", "ol/view", "ol/control/zoom", "ol-mapbox-style"], ()=> {
console.log('Loaded OpenLayers3 renderer')
- const ol = require('openlayers')
- const olms = require('ol-mapbox-style')
+ const olMap = require('ol/map').default
+ const olView = require('ol/view').default
+ const olZoom = require('ol/control/zoom').default
- this.tilegrid = ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22})
- this.resolutions = this.tilegrid.getResolutions()
-
- const map = new ol.Map({
+ const map = new olMap({
target: this.container,
layers: [],
- view: new ol.View({
+ view: new olView({
zoom: 2,
center: [52.5, -78.4]
})
})
- map.addControl(new ol.control.Zoom())
+ map.addControl(new olZoom())
this.map = map
this.updateStyle(this.props.mapStyle)
})
@@ -140,10 +65,10 @@ class OpenLayers3Map extends React.Component {
ref={x => this.container = x}
style={{
position: "fixed",
- top: 0,
+ top: 40,
right: 0,
bottom: 0,
- height: "100%",
+ height: 'calc(100% - 40px)',
width: "75%",
backgroundColor: '#fff',
...this.props.style,
diff --git a/src/components/modals/AddModal.jsx b/src/components/modals/AddModal.jsx
index d08a539..e89ca00 100644
--- a/src/components/modals/AddModal.jsx
+++ b/src/components/modals/AddModal.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Button from '../Button'
import InputBlock from '../inputs/InputBlock'
@@ -13,13 +14,13 @@ import LayerSourceLayerBlock from '../layers/LayerSourceLayerBlock'
class AddModal extends React.Component {
static propTypes = {
- layers: React.PropTypes.array.isRequired,
- onLayersChange: React.PropTypes.func.isRequired,
- isOpen: React.PropTypes.bool.isRequired,
- onOpenToggle: React.PropTypes.func.isRequired,
+ layers: PropTypes.array.isRequired,
+ onLayersChange: PropTypes.func.isRequired,
+ isOpen: PropTypes.bool.isRequired,
+ onOpenToggle: PropTypes.func.isRequired,
// A dict of source id's and the available source layers
- sources: React.PropTypes.object.isRequired,
+ sources: PropTypes.object.isRequired,
}
addLayer() {
@@ -55,18 +56,65 @@ class AddModal extends React.Component {
}
}
- componentWillReceiveProps(nextProps) {
- const sourceIds = Object.keys(nextProps.sources)
- if(!this.state.source && sourceIds.length > 0) {
+ componentWillUpdate(nextProps, nextState) {
+ // Check if source is valid for new type
+ const oldType = this.state.type;
+ const newType = nextState.type;
+
+ const availableSourcesOld = this.getSources(oldType);
+ const availableSourcesNew = this.getSources(newType);
+
+ if(
+ // Type has changed
+ oldType !== newType
+ && this.state.source !== ""
+ // Was a valid source previously
+ && availableSourcesOld.indexOf(this.state.source) > -1
+ // And is not a valid source now
+ && availableSourcesNew.indexOf(nextState.source) < 0
+ ) {
+ // Clear the source
this.setState({
- source: sourceIds[0],
- 'source-layer': this.state['source-layer'] || (nextProps.sources[sourceIds[0]] || [])[0]
- })
+ source: ""
+ });
}
}
+ getLayersForSource(source) {
+ const sourceObj = this.props.sources[source] || {};
+ return sourceObj.layers || [];
+ }
+
+ getSources(type) {
+ const sources = [];
+
+ const types = {
+ vector: [
+ "fill",
+ "line",
+ "symbol",
+ "circle",
+ "fill-extrusion"
+ ],
+ raster: [
+ "raster"
+ ]
+ }
+
+ for(let [key, val] of Object.entries(this.props.sources)) {
+ if(types[val.type] && types[val.type].indexOf(type) > -1) {
+ sources.push(key);
+ }
+ }
+
+ return sources;
+ }
+
render() {
+ const sources = this.getSources(this.state.type);
+ const layers = this.getLayersForSource(this.state.source);
+
return
{this.state.type !== 'background' &&
this.setState({ source: v })}
/>
}
- {this.state.type !== 'background' && this.state.type !== 'raster' &&
+ {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
this.setState({ 'source-layer': v })}
/>
diff --git a/src/components/modals/ExportModal.jsx b/src/components/modals/ExportModal.jsx
index 0a06571..bb107b4 100644
--- a/src/components/modals/ExportModal.jsx
+++ b/src/components/modals/ExportModal.jsx
@@ -1,7 +1,8 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { saveAs } from 'file-saver'
-import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
+import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
@@ -9,21 +10,23 @@ import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button'
import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download'
+import TiClipboard from 'react-icons/lib/ti/clipboard'
import style from '../../libs/style.js'
-import formatStyle from 'mapbox-gl-style-spec/lib/format'
import GitHub from 'github-api'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
class Gist extends React.Component {
static propTypes = {
- mapStyle: React.PropTypes.object.isRequired,
- onStyleChanged: React.PropTypes.func.isRequired,
+ mapStyle: PropTypes.object.isRequired,
+ onStyleChanged: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
preview: false,
+ public: false,
saving: false,
latestGist: null,
}
@@ -41,11 +44,14 @@ class Gist extends React.Component {
...this.state,
saving: true
});
- const preview = this.state.preview && (this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token'];
+
+ const preview = this.state.preview;
+
+ const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
const mapStyleStr = preview ?
- formatStyle(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
- formatStyle(stripAccessTokens(this.props.mapStyle));
+ styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
+ styleSpec.format(stripAccessTokens(this.props.mapStyle));
const styleTitle = this.props.mapStyle.name || 'Style';
const htmlStr = `
@@ -54,8 +60,8 @@ class Gist extends React.Component {
`+styleTitle+` Preview
-
-
+
+
- Loading...
+