From 7cfe0563bc1c5a0a704950f01cdd61dc50c05461 Mon Sep 17 00:00:00 2001 From: orangemug Date: Sun, 12 Apr 2020 16:25:32 +0100 Subject: [PATCH 1/2] Added support for identity functions. --- src/components/fields/FunctionSpecField.jsx | 59 ++++-- src/components/fields/_DataProperty.jsx | 205 ++++++++++++-------- src/components/util/spec-helper.js | 18 ++ 3 files changed, 176 insertions(+), 106 deletions(-) create mode 100644 src/components/util/spec-helper.js diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx index cc8fb16..38304c4 100644 --- a/src/components/fields/FunctionSpecField.jsx +++ b/src/components/fields/FunctionSpecField.jsx @@ -6,12 +6,21 @@ import DataProperty from './_DataProperty' import ZoomProperty from './_ZoomProperty' import ExpressionProperty from './_ExpressionProperty' import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec'; +import {findDefaultFromSpec} from '../util/spec-helper'; function isLiteralExpression (value) { return (Array.isArray(value) && value.length === 2 && value[0] === "literal"); } +function isGetExpression (value) { + return ( + Array.isArray(value) && + value.length === 2 && + value[0] === "get" + ); +} + function isZoomField(value) { return ( typeof(value) === 'object' && @@ -28,7 +37,15 @@ function isZoomField(value) { ); } -function isDataField(value) { +function isIdentityProperty (value) { + return ( + typeof(value) === 'object' && + value.type === "identity" && + value.hasOwnProperty("property") + ); +} + +function isDataStopProperty (value) { return ( typeof(value) === 'object' && value.stops && @@ -45,6 +62,13 @@ function isDataField(value) { ); } +function isDataField(value) { + return ( + isIdentityProperty(value) || + isDataStopProperty(value) + ); +} + function isPrimative (value) { const valid = ["string", "boolean", "number"]; return valid.includes(typeof(value)); @@ -78,24 +102,6 @@ function getDataType (value, fieldSpec={}) { } } -/** - * If we don't have a default value just make one up - */ -function findDefaultFromSpec (spec) { - if (spec.hasOwnProperty('default')) { - return spec.default; - } - - const defaults = { - 'color': '#000000', - 'string': '', - 'boolean': false, - 'number': 0, - 'array': [], - } - - return defaults[spec.type] || ''; -} /** Supports displaying spec field for zoom function objects * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property @@ -206,7 +212,16 @@ export default class FunctionSpecProperty extends React.Component { undoExpression = () => { const {value, fieldName} = this.props; - if (isLiteralExpression(value)) { + if (isGetExpression(value)) { + this.props.onChange(fieldName, { + "type": "identity", + "property": value[1] + }); + this.setState({ + dataType: "value", + }); + } + else if (isLiteralExpression(value)) { this.props.onChange(fieldName, value[1]); this.setState({ dataType: "value", @@ -217,6 +232,7 @@ export default class FunctionSpecProperty extends React.Component { canUndo = () => { const {value, fieldSpec} = this.props; return ( + isGetExpression(value) || isLiteralExpression(value) || isPrimative(value) || (Array.isArray(value) && fieldSpec.type === "array") @@ -230,6 +246,9 @@ export default class FunctionSpecProperty extends React.Component { if (typeof(value) === "object" && 'stops' in value) { expression = styleFunction.convertFunction(value, fieldSpec); } + else if (isIdentityProperty(value)) { + expression = ["get", value.property]; + } else { expression = ["literal", value || this.props.fieldSpec.default]; } diff --git a/src/components/fields/_DataProperty.jsx b/src/components/fields/_DataProperty.jsx index 80961b7..37d7701 100644 --- a/src/components/fields/_DataProperty.jsx +++ b/src/components/fields/_DataProperty.jsx @@ -10,6 +10,7 @@ import DocLabel from './DocLabel' import InputBlock from '../inputs/InputBlock' import docUid from '../../libs/document-uid' import sortNumerically from '../../libs/sort-numerically' +import {findDefaultFromSpec} from '../util/spec-helper'; import labelFromFieldName from './_labelFromFieldName' import DeleteStopButton from './_DeleteStopButton' @@ -89,10 +90,10 @@ export default class DataProperty extends React.Component { getDataFunctionTypes(fieldSpec) { if (fieldSpec.expression.interpolated) { - return ["categorical", "interval", "exponential"] + return ["categorical", "interval", "exponential", "identity"] } else { - return ["categorical", "interval"] + return ["categorical", "interval", "identity"] } } @@ -122,6 +123,29 @@ export default class DataProperty extends React.Component { return mappedWithRef.map((item) => item.data); } + onChange = (fieldName, value) => { + if (value.type === "identity") { + value = { + type: value.type, + property: value.property, + }; + } + else { + const stopValue = value.type === 'categorical' ? '' : 0; + value = { + property: "", + type: value.type, + // Default props if they don't already exist. + stops: [ + [{zoom: 6, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)], + [{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)] + ], + ...value, + } + } + this.props.onChange(fieldName, value); + } + changeStop(changeIdx, stopData, value) { const stops = this.props.value.stops.slice(0) const changedStop = stopData.zoom === undefined ? stopData.value : stopData @@ -133,7 +157,7 @@ export default class DataProperty extends React.Component { ...this.props.value, stops: orderedStops, } - this.props.onChange(this.props.fieldName, changedValue) + this.onChange(this.props.fieldName, changedValue) } changeDataProperty(propName, propVal) { @@ -143,7 +167,7 @@ export default class DataProperty extends React.Component { else { delete this.props.value[propName] } - this.props.onChange(this.props.fieldName, this.props.value) + this.onChange(this.props.fieldName, this.props.value) } render() { @@ -153,69 +177,72 @@ export default class DataProperty extends React.Component { this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec) } - const dataFields = this.props.value.stops.map((stop, idx) => { - const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined; - const key = this.state.refs[idx]; - const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0]; - const value = stop[1] - const deleteStopBtn = + let dataFields; + if (this.props.value.stops) { + dataFields = this.props.value.stops.map((stop, idx) => { + const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined; + const key = this.state.refs[idx]; + const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0]; + const value = stop[1] + const deleteStopBtn = - const dataProps = { - label: "Data value", - value: dataLevel, - onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value) - } + 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 = - } + let dataInput; + if(this.props.value.type === "categorical") { + dataInput = + } + else { + dataInput = + } - let zoomInput = null; - if(zoomLevel !== undefined) { - zoomInput =
- this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)} - min={0} - max={22} - /> -
- } + let zoomInput = null; + if(zoomLevel !== undefined) { + zoomInput =
+ this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)} + min={0} + max={22} + /> +
+ } - const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`; - const foundErrors = Object.entries(errors).filter(([key, error]) => { - return key.startsWith(errorKeyStart); - }); + const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`; + const foundErrors = Object.entries(errors).filter(([key, error]) => { + return key.startsWith(errorKeyStart); + }); - const message = foundErrors.map(([key, error]) => { - return error.message; - }).join(""); - const error = message ? {message} : undefined; + const message = foundErrors.map(([key, error]) => { + return error.message; + }).join(""); + const error = message ? {message} : undefined; - return - {zoomInput} -
- {dataInput} -
-
- this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)} - /> -
-
- }) + return + {zoomInput} +
+ {dataInput} +
+
+ this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)} + /> +
+
+ }) + } return
@@ -223,18 +250,6 @@ export default class DataProperty extends React.Component { fieldSpec={this.props.fieldSpec} label={labelFromFieldName(this.props.fieldName)} > -
- -
- this.changeDataProperty("property", propVal)} - /> -
-
- this.changeDataProperty("default", propVal)} + this.changeDataProperty("property", propVal)} />
+ {dataFields && +
+ +
+ this.changeDataProperty("default", propVal)} + /> +
+
+ }
- {dataFields} - + {dataFields && + <> + {dataFields} + + + } } @@ -307,7 +310,9 @@ export default class DataProperty extends React.Component { className="maputnik-add-stop" onClick={this.props.onExpressionClick.bind(this)} > - Convert to expression + + + Convert to expression
} diff --git a/src/components/fields/_ZoomProperty.jsx b/src/components/fields/_ZoomProperty.jsx index 829d356..98995f9 100644 --- a/src/components/fields/_ZoomProperty.jsx +++ b/src/components/fields/_ZoomProperty.jsx @@ -1,5 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' +import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js'; import Button from '../Button' import SpecField from './SpecField' @@ -176,13 +177,17 @@ export default class ZoomProperty extends React.Component { className="maputnik-add-stop" onClick={this.props.onAddStop.bind(this)} > - Add stop + + + Add stop
} diff --git a/src/components/filter/FilterEditor.jsx b/src/components/filter/FilterEditor.jsx index b995157..780d55e 100644 --- a/src/components/filter/FilterEditor.jsx +++ b/src/components/filter/FilterEditor.jsx @@ -1,6 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import { combiningFilterOps } from '../../libs/filterops.js' +import {mdiTableRowPlusAfter} from '@mdi/js'; import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec' import DocLabel from '../fields/DocLabel' @@ -261,8 +262,11 @@ export default class CombiningFilterEditor extends React.Component {