From e057fcaea11ea0907297a0f7a094533728a754b9 Mon Sep 17 00:00:00 2001 From: pjsier Date: Sun, 1 Oct 2017 14:49:32 -0500 Subject: [PATCH 1/9] Add data-driven styling to editor Builds off of the ZoomSpecField component with separate options for handling data-driven properties. Reuses most of the zoom field functionality with tweaks that I tried to keep as small as possible, and the layout is based off of comments on the existing issue. --- src/components/fields/ZoomSpecField.jsx | 195 +++++++++++++++++++++--- src/styles/_components.scss | 4 +- src/styles/_zoomproperty.scss | 60 ++++++++ 3 files changed, 238 insertions(+), 21 deletions(-) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx index 9f8e755e..3cb867c1 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/ZoomSpecField.jsx @@ -4,17 +4,24 @@ 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 + 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 @@ -38,7 +45,15 @@ export default class ZoomSpecProperty extends React.Component { addStop() { const stops = this.props.value.stops.slice(0) const lastStop = stops[stops.length - 1] - stops.push([lastStop[0] + 1, lastStop[1]]) + if (typeof lastStop[0] === "object") { + stops.push([ + {zoom: lastStop[0].zoom, value: lastStop[0].value}, + lastStop[1] + ]) + } + else { + stops.push([lastStop[0] + 1, lastStop[1]]) + } const changedValue = { ...this.props.value, @@ -74,9 +89,21 @@ export default class ZoomSpecProperty extends React.Component { this.props.onChange(this.props.fieldName, zoomFunc) } - changeStop(changeIdx, zoomLevel, value) { + makeDataFunction() { + const dataFunc = { + property: "", + type: "exponential", + 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] = [zoomLevel, value] + stops[changeIdx] = [stopData, value] const changedValue = { ...this.props.value, stops: stops, @@ -84,6 +111,111 @@ export default class ZoomSpecProperty extends React.Component { this.props.onChange(this.props.fieldName, changedValue) } + changeDataProperty(propName, propVal) { + this.props.value[propName] = propVal + this.props.onChange(this.props.fieldName, this.props.value) + } + + getDataInput(value, dataLevel, zoomLevel) { + const dataProps = { + label: "Data value", + value: dataLevel, + onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value) + } + if (this.props.value.type === "categorical") { + return + } + else { + return + } + } + + 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 = + return +
+ this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)} + min={0} + max={22} + /> +
+
+ {this.getDataInput(value, dataLevel, zoomLevel)} +
+
+ this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)} + /> +
+
+ }) + + return
+
+ +
+ +
+ this.changeDataProperty("property", propVal)} + /> +
+
+
+ +
+ this.changeDataProperty("type", propVal)} + options={["exponential", "interval", "categorical", "identity"]} + /> +
+
+
+ +
+ this.changeStop(idx, { zoom: zoomLevel, value: dataLevel }, newValue)} + /> +
+
+
+
+ {dataFields} + +
+ } + renderZoomProperty() { const zoomFields = this.props.value.stops.map((stop, idx) => { const zoomLevel = stop[0] @@ -129,14 +261,17 @@ export default class ZoomSpecProperty extends React.Component { } renderProperty() { - let zoomBtn = null + let functionBtn = null if(this.props.fieldSpec['zoom-function']) { - zoomBtn = + functionBtn = } return @@ -144,23 +279,45 @@ export default class ZoomSpecProperty extends React.Component { 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
- {isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()} + {specField}
} } -function MakeZoomFunctionButton(props) { - return +function MakeFunctionButtons(props) { + return
+ + +
} function DeleteStopButton(props) { diff --git a/src/styles/_components.scss b/src/styles/_components.scss index b4032a5d..67310214 100644 --- a/src/styles/_components.scss +++ b/src/styles/_components.scss @@ -104,13 +104,13 @@ .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%; } } diff --git a/src/styles/_zoomproperty.scss b/src/styles/_zoomproperty.scss index 22984961..a5b8900a 100644 --- a/src/styles/_zoomproperty.scss +++ b/src/styles/_zoomproperty.scss @@ -67,3 +67,63 @@ .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%; + } +} \ No newline at end of file From d731fb2caeaa3df45e13f605b8fc7f0ffec9b85f Mon Sep 17 00:00:00 2001 From: pjsier Date: Sun, 1 Oct 2017 19:17:43 -0500 Subject: [PATCH 2/9] Fix scss linter errors --- src/styles/_zoomproperty.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/styles/_zoomproperty.scss b/src/styles/_zoomproperty.scss index a5b8900a..d6510c9b 100644 --- a/src/styles/_zoomproperty.scss +++ b/src/styles/_zoomproperty.scss @@ -83,6 +83,7 @@ .maputnik-data-spec-block { overflow: auto; } + .maputnik-data-spec-property { .maputnik-input-block-label { width: 30%; @@ -91,6 +92,7 @@ .maputnik-input-block-content { width: 70%; } + .maputnik-data-spec-property-group { margin-bottom: 3%; @@ -98,9 +100,11 @@ width: 25%; color: $color-lowgray; } + .maputnik-doc-wrapper:hover { color: inherit; } + .maputnik-data-spec-property-input { width: 75%; display: inline-block; @@ -123,7 +127,8 @@ width: 18%; margin-right: 3%; } + .maputnik-data-spec-property-stop-data { width: 78%; } -} \ No newline at end of file +} From 9e52b0b7dc96bbbc75f87bff95bddddf104ef367 Mon Sep 17 00:00:00 2001 From: pjsier Date: Sun, 1 Oct 2017 21:07:55 -0500 Subject: [PATCH 3/9] Remove default from data properties It looks like default is not supported in this version of the style spec, so pending the PR to update it I'm removing it as an input. --- src/components/fields/ZoomSpecField.jsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx index 3cb867c1..ab1cd79c 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/ZoomSpecField.jsx @@ -190,20 +190,6 @@ export default class ZoomSpecProperty extends React.Component { /> -
- -
- this.changeStop(idx, { zoom: zoomLevel, value: dataLevel }, newValue)} - /> -
-
{dataFields} From 2ffb3e73e1e610ab41dc8a1ef12b0bc16448f5f5 Mon Sep 17 00:00:00 2001 From: pjsier Date: Thu, 5 Oct 2017 05:50:47 -0500 Subject: [PATCH 4/9] Re-add default field after style update --- src/components/fields/ZoomSpecField.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx index ab1cd79c..42883c0b 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/ZoomSpecField.jsx @@ -190,6 +190,20 @@ export default class ZoomSpecProperty extends React.Component { /> +
+ +
+ this.changeDataProperty("default", propVal)} + /> +
+
{dataFields} From 1d29f670652609a20f0d3aea0f2b7e3e5671ff8a Mon Sep 17 00:00:00 2001 From: pjsier Date: Thu, 5 Oct 2017 06:08:55 -0500 Subject: [PATCH 5/9] Check for property-function support on data styles --- src/components/fields/ZoomSpecField.jsx | 46 ++++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx index 42883c0b..44686dc1 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/ZoomSpecField.jsx @@ -261,13 +261,11 @@ export default class ZoomSpecProperty extends React.Component { } renderProperty() { - let functionBtn = null - if(this.props.fieldSpec['zoom-function']) { - functionBtn = - } + const functionBtn = return - - - + + if (props.fieldSpec['property-function']) { + makeDataButton = + } + return
{makeZoomButton}{makeDataButton}
+ } + else { + return null + } } function DeleteStopButton(props) { From 148f64c2618e68a8f9e4d8943316c6ed095b9f35 Mon Sep 17 00:00:00 2001 From: pjsier Date: Tue, 10 Oct 2017 10:30:06 -0500 Subject: [PATCH 6/9] Restrict data function types, reorder buttons Checking the Mapbox style spec properties to see whether or not exponential should be allowed as the property type, defaulting to categorical which appears to work for either type. Also re-orders zoom and data function buttons, aligning zoom right if data not supplied. --- src/components/fields/ZoomSpecField.jsx | 23 ++++++++++++++++------- src/styles/_components.scss | 5 +++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/ZoomSpecField.jsx index 44686dc1..b045c057 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/ZoomSpecField.jsx @@ -47,7 +47,7 @@ export default class ZoomSpecProperty extends React.Component { const lastStop = stops[stops.length - 1] if (typeof lastStop[0] === "object") { stops.push([ - {zoom: lastStop[0].zoom, value: lastStop[0].value}, + {zoom: lastStop[0].zoom + 1, value: lastStop[0].value}, lastStop[1] ]) } @@ -89,10 +89,19 @@ export default class ZoomSpecProperty extends React.Component { 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: "exponential", + type: "categorical", stops: [ [{zoom: 6, value: 0}, this.props.value], [{zoom: 10, value: 0}, this.props.value] @@ -180,13 +189,13 @@ export default class ZoomSpecProperty extends React.Component {
this.changeDataProperty("type", propVal)} - options={["exponential", "interval", "categorical", "identity"]} + options={this.getDataFunctionTypes(this.props.fieldSpec.function)} />
@@ -307,7 +316,7 @@ function MakeFunctionButtons(props) { /> - if (props.fieldSpec['property-function']) { + if (props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(props.fieldSpec['function']) !== -1) { makeDataButton = } - return
{makeZoomButton}{makeDataButton}
+ return
{makeDataButton}{makeZoomButton}
} else { return null diff --git a/src/styles/_components.scss b/src/styles/_components.scss index 67310214..12415faa 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 @@ -112,6 +113,10 @@ display: inline-block; width: 15%; } + + .maputnik-input-block-action > div { + text-align: right; + } } // SPACE HELPER From 4af7a7122006fac05ebd3ca24eeee727e14b6bd6 Mon Sep 17 00:00:00 2001 From: pjsier Date: Tue, 10 Oct 2017 14:23:20 -0500 Subject: [PATCH 7/9] Rename ZoomSpecField to FunctionSpecField --- .../fields/{ZoomSpecField.jsx => FunctionSpecField.jsx} | 2 +- src/components/fields/PropertyGroup.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/components/fields/{ZoomSpecField.jsx => FunctionSpecField.jsx} (99%) diff --git a/src/components/fields/ZoomSpecField.jsx b/src/components/fields/FunctionSpecField.jsx similarity index 99% rename from src/components/fields/ZoomSpecField.jsx rename to src/components/fields/FunctionSpecField.jsx index b045c057..3adc77cc 100644 --- a/src/components/fields/ZoomSpecField.jsx +++ b/src/components/fields/FunctionSpecField.jsx @@ -27,7 +27,7 @@ function isDataField(value) { /** 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 { +export default class FunctionSpecProperty extends React.Component { static propTypes = { onChange: React.PropTypes.func.isRequired, fieldName: React.PropTypes.string.isRequired, diff --git a/src/components/fields/PropertyGroup.jsx b/src/components/fields/PropertyGroup.jsx index 8de8b342..fc178929 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 Date: Wed, 11 Oct 2017 05:58:32 -0500 Subject: [PATCH 8/9] Fix default field bug --- src/components/fields/FunctionSpecField.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx index 3adc77cc..c85cb7a4 100644 --- a/src/components/fields/FunctionSpecField.jsx +++ b/src/components/fields/FunctionSpecField.jsx @@ -121,7 +121,12 @@ export default class FunctionSpecProperty extends React.Component { } changeDataProperty(propName, propVal) { - this.props.value[propName] = propVal + if (propVal) { + this.props.value[propName] = propVal + } + else { + delete this.props.value[propName] + } this.props.onChange(this.props.fieldName, this.props.value) } @@ -209,7 +214,7 @@ export default class FunctionSpecProperty extends React.Component { fieldName={this.props.fieldName} fieldSpec={this.props.fieldSpec} value={this.props.value.default} - onChange={propVal => this.changeDataProperty("default", propVal)} + onChange={(_, propVal) => this.changeDataProperty("default", propVal)} /> From fa0067ce7bcfe412ed58e62428d6e1aaf9db747b Mon Sep 17 00:00:00 2001 From: pjsier Date: Wed, 11 Oct 2017 08:01:55 -0500 Subject: [PATCH 9/9] Update mapbox deps, clarify data prop scope --- package.json | 4 ++-- src/components/fields/FunctionSpecField.jsx | 24 ++++++++------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 5471a5dd..efe05ab5 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", "mousetrap": "^1.6.0", "ol-mapbox-style": "1.0.1", diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx index c85cb7a4..2b7383b9 100644 --- a/src/components/fields/FunctionSpecField.jsx +++ b/src/components/fields/FunctionSpecField.jsx @@ -130,26 +130,20 @@ export default class FunctionSpecProperty extends React.Component { this.props.onChange(this.props.fieldName, this.props.value) } - getDataInput(value, dataLevel, zoomLevel) { - const dataProps = { - label: "Data value", - value: dataLevel, - onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value) - } - if (this.props.value.type === "categorical") { - return - } - else { - return - } - } - 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.getDataInput(value, dataLevel, zoomLevel)} + {dataInput}