diff --git a/package.json b/package.json
index 4f59683..aed9b82 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
"license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
- "@mapbox/mapbox-gl-style-spec": "^9.0.0",
"@mapbox/mapbox-gl-rtl-text": "^0.1.0",
+ "@mapbox/mapbox-gl-style-spec": "^9.0.1",
"classnames": "^2.2.5",
"codemirror": "^5.18.2",
"color": "^1.0.3",
@@ -32,7 +32,7 @@
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.4.0",
"lodash.throttle": "^4.1.1",
- "mapbox-gl": "^0.34.0",
+ "mapbox-gl": "^0.40.1",
"mapbox-gl-inspect": "^1.2.3",
"maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.0",
diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx
new file mode 100644
index 0000000..2b7383b
--- /dev/null
+++ b/src/components/fields/FunctionSpecField.jsx
@@ -0,0 +1,352 @@
+import React from 'react'
+import Color from 'color'
+
+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 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 MdInsertChart from 'react-icons/lib/md/insert-chart'
+
+import capitalize from 'lodash.capitalize'
+
+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: 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,
+ React.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)
+ }
+
+ getDataFunctionTypes(functionType) {
+ if (functionType === "interpolated") {
+ return ["categorical", "interval", "exponential"]
+ }
+ else {
+ return ["categorical", "interval"]
+ }
+ }
+
+ 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)
+ }
+
+ 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)
+ }
+
+ renderDataProperty() {
+ 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)
+ }
+ const dataInput = this.props.value.type === "categorical" ? :
+
+ 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}
+
+
+ }
+
+ 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() {
+ const functionBtn =
+ return
+
+
+ }
+
+ render() {
+ const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
+ let specField
+ if (isZoomField(this.props.value)) {
+ specField = this.renderZoomProperty()
+ }
+ else if (isDataField(this.props.value)) {
+ specField = this.renderDataProperty()
+ }
+ else {
+ specField = this.renderProperty()
+ }
+ return
+ {specField}
+
+ }
+}
+
+function MakeFunctionButtons(props) {
+ let makeZoomButton, makeDataButton
+ if (props.fieldSpec['zoom-function']) {
+ makeZoomButton =
+
+ if (props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(props.fieldSpec['function']) !== -1) {
+ makeDataButton =
+ }
+ return {makeDataButton}{makeZoomButton}
+ }
+ else {
+ return null
+ }
+}
+
+function DeleteStopButton(props) {
+ return
+}
+
+function labelFromFieldName(fieldName) {
+ let label = fieldName.split('-').slice(1).join(' ')
+ return capitalize(label)
+}
diff --git a/src/components/fields/PropertyGroup.jsx b/src/components/fields/PropertyGroup.jsx
index 8de8b34..fc17892 100644
--- a/src/components/fields/PropertyGroup.jsx
+++ b/src/components/fields/PropertyGroup.jsx
@@ -1,6 +1,6 @@
import React from 'react'
-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
@@ -54,7 +54,7 @@ export default class PropertyGroup extends React.Component {
const layout = this.props.layer.layout || {}
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
- return {
- 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/styles/_components.scss b/src/styles/_components.scss
index b4032a5..12415fa 100644
--- a/src/styles/_components.scss
+++ b/src/styles/_components.scss
@@ -40,6 +40,7 @@
.maputnik-doc-target:hover .maputnik-doc-popup {
display: block;
+ text-align: left;
}
// BUTTON
@@ -104,13 +105,17 @@
.maputnik-action-block {
.maputnik-input-block-label {
display: inline-block;
- width: 43%;
+ width: 35%;
}
.maputnik-input-block-action {
vertical-align: top;
display: inline-block;
- width: 7%;
+ width: 15%;
+ }
+
+ .maputnik-input-block-action > div {
+ text-align: right;
}
}
diff --git a/src/styles/_zoomproperty.scss b/src/styles/_zoomproperty.scss
index 2298496..d6510c9 100644
--- a/src/styles/_zoomproperty.scss
+++ b/src/styles/_zoomproperty.scss
@@ -67,3 +67,68 @@
.maputnik-zoom-spec-property .maputnik-input-block:not(:first-child) .maputnik-input-block-label {
visibility: hidden;
}
+
+// DATA FUNC
+.maputnik-make-data-function {
+ background-color: transparent;
+ display: inline-block;
+ padding-bottom: 0;
+ padding-top: 0;
+ vertical-align: middle;
+
+ @extend .maputnik-icon-button;
+}
+
+// DATA PROPERTY
+.maputnik-data-spec-block {
+ overflow: auto;
+}
+
+.maputnik-data-spec-property {
+ .maputnik-input-block-label {
+ width: 30%;
+ }
+
+ .maputnik-input-block-content {
+ width: 70%;
+ }
+
+ .maputnik-data-spec-property-group {
+ margin-bottom: 3%;
+
+ .maputnik-doc-wrapper {
+ width: 25%;
+ color: $color-lowgray;
+ }
+
+ .maputnik-doc-wrapper:hover {
+ color: inherit;
+ }
+
+ .maputnik-data-spec-property-input {
+ width: 75%;
+ display: inline-block;
+
+ .maputnik-string {
+ margin-bottom: 3%;
+ }
+ }
+ }
+}
+
+.maputnik-data-spec-block {
+ .maputnik-data-spec-property-stop-edit,
+ .maputnik-data-spec-property-stop-data {
+ display: inline-block;
+ margin-bottom: 3%;
+ }
+
+ .maputnik-data-spec-property-stop-edit {
+ width: 18%;
+ margin-right: 3%;
+ }
+
+ .maputnik-data-spec-property-stop-data {
+ width: 78%;
+ }
+}