mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-27 09:45:29 +01:00
Added support for identity functions.
This commit is contained in:
parent
8f722c59de
commit
7cfe0563bc
3 changed files with 176 additions and 106 deletions
|
@ -6,12 +6,21 @@ import DataProperty from './_DataProperty'
|
||||||
import ZoomProperty from './_ZoomProperty'
|
import ZoomProperty from './_ZoomProperty'
|
||||||
import ExpressionProperty from './_ExpressionProperty'
|
import ExpressionProperty from './_ExpressionProperty'
|
||||||
import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec';
|
import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec';
|
||||||
|
import {findDefaultFromSpec} from '../util/spec-helper';
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression (value) {
|
function isLiteralExpression (value) {
|
||||||
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
|
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) {
|
function isZoomField(value) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
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 (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === 'object' &&
|
||||||
value.stops &&
|
value.stops &&
|
||||||
|
@ -45,6 +62,13 @@ function isDataField(value) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDataField(value) {
|
||||||
|
return (
|
||||||
|
isIdentityProperty(value) ||
|
||||||
|
isDataStopProperty(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isPrimative (value) {
|
function isPrimative (value) {
|
||||||
const valid = ["string", "boolean", "number"];
|
const valid = ["string", "boolean", "number"];
|
||||||
return valid.includes(typeof(value));
|
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
|
/** Supports displaying spec field for zoom function objects
|
||||||
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
||||||
|
@ -206,7 +212,16 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
undoExpression = () => {
|
undoExpression = () => {
|
||||||
const {value, fieldName} = this.props;
|
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.props.onChange(fieldName, value[1]);
|
||||||
this.setState({
|
this.setState({
|
||||||
dataType: "value",
|
dataType: "value",
|
||||||
|
@ -217,6 +232,7 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
canUndo = () => {
|
canUndo = () => {
|
||||||
const {value, fieldSpec} = this.props;
|
const {value, fieldSpec} = this.props;
|
||||||
return (
|
return (
|
||||||
|
isGetExpression(value) ||
|
||||||
isLiteralExpression(value) ||
|
isLiteralExpression(value) ||
|
||||||
isPrimative(value) ||
|
isPrimative(value) ||
|
||||||
(Array.isArray(value) && fieldSpec.type === "array")
|
(Array.isArray(value) && fieldSpec.type === "array")
|
||||||
|
@ -230,6 +246,9 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
if (typeof(value) === "object" && 'stops' in value) {
|
if (typeof(value) === "object" && 'stops' in value) {
|
||||||
expression = styleFunction.convertFunction(value, fieldSpec);
|
expression = styleFunction.convertFunction(value, fieldSpec);
|
||||||
}
|
}
|
||||||
|
else if (isIdentityProperty(value)) {
|
||||||
|
expression = ["get", value.property];
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
expression = ["literal", value || this.props.fieldSpec.default];
|
expression = ["literal", value || this.props.fieldSpec.default];
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import DocLabel from './DocLabel'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import docUid from '../../libs/document-uid'
|
import docUid from '../../libs/document-uid'
|
||||||
import sortNumerically from '../../libs/sort-numerically'
|
import sortNumerically from '../../libs/sort-numerically'
|
||||||
|
import {findDefaultFromSpec} from '../util/spec-helper';
|
||||||
|
|
||||||
import labelFromFieldName from './_labelFromFieldName'
|
import labelFromFieldName from './_labelFromFieldName'
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from './_DeleteStopButton'
|
||||||
|
@ -89,10 +90,10 @@ export default class DataProperty extends React.Component {
|
||||||
|
|
||||||
getDataFunctionTypes(fieldSpec) {
|
getDataFunctionTypes(fieldSpec) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return ["categorical", "interval", "exponential"]
|
return ["categorical", "interval", "exponential", "identity"]
|
||||||
}
|
}
|
||||||
else {
|
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);
|
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) {
|
changeStop(changeIdx, stopData, value) {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
||||||
|
@ -133,7 +157,7 @@ export default class DataProperty extends React.Component {
|
||||||
...this.props.value,
|
...this.props.value,
|
||||||
stops: orderedStops,
|
stops: orderedStops,
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeDataProperty(propName, propVal) {
|
changeDataProperty(propName, propVal) {
|
||||||
|
@ -143,7 +167,7 @@ export default class DataProperty extends React.Component {
|
||||||
else {
|
else {
|
||||||
delete this.props.value[propName]
|
delete this.props.value[propName]
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, this.props.value)
|
this.onChange(this.props.fieldName, this.props.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -153,69 +177,72 @@ export default class DataProperty extends React.Component {
|
||||||
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
|
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataFields = this.props.value.stops.map((stop, idx) => {
|
let dataFields;
|
||||||
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
if (this.props.value.stops) {
|
||||||
const key = this.state.refs[idx];
|
dataFields = this.props.value.stops.map((stop, idx) => {
|
||||||
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
||||||
const value = stop[1]
|
const key = this.state.refs[idx];
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
||||||
|
const value = stop[1]
|
||||||
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
||||||
|
|
||||||
const dataProps = {
|
const dataProps = {
|
||||||
label: "Data value",
|
label: "Data value",
|
||||||
value: dataLevel,
|
value: dataLevel,
|
||||||
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
|
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataInput;
|
let dataInput;
|
||||||
if(this.props.value.type === "categorical") {
|
if(this.props.value.type === "categorical") {
|
||||||
dataInput = <StringInput {...dataProps} />
|
dataInput = <StringInput {...dataProps} />
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dataInput = <NumberInput {...dataProps} />
|
dataInput = <NumberInput {...dataProps} />
|
||||||
}
|
}
|
||||||
|
|
||||||
let zoomInput = null;
|
let zoomInput = null;
|
||||||
if(zoomLevel !== undefined) {
|
if(zoomLevel !== undefined) {
|
||||||
zoomInput = <div className="maputnik-data-spec-property-stop-edit">
|
zoomInput = <div className="maputnik-data-spec-property-stop-edit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={zoomLevel}
|
value={zoomLevel}
|
||||||
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
||||||
min={0}
|
min={0}
|
||||||
max={22}
|
max={22}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
|
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
|
||||||
const foundErrors = Object.entries(errors).filter(([key, error]) => {
|
const foundErrors = Object.entries(errors).filter(([key, error]) => {
|
||||||
return key.startsWith(errorKeyStart);
|
return key.startsWith(errorKeyStart);
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = foundErrors.map(([key, error]) => {
|
const message = foundErrors.map(([key, error]) => {
|
||||||
return error.message;
|
return error.message;
|
||||||
}).join("");
|
}).join("");
|
||||||
const error = message ? {message} : undefined;
|
const error = message ? {message} : undefined;
|
||||||
|
|
||||||
return <InputBlock
|
return <InputBlock
|
||||||
error={error}
|
error={error}
|
||||||
key={key}
|
key={key}
|
||||||
action={deleteStopBtn}
|
action={deleteStopBtn}
|
||||||
label=""
|
label=""
|
||||||
>
|
>
|
||||||
{zoomInput}
|
{zoomInput}
|
||||||
<div className="maputnik-data-spec-property-stop-data">
|
<div className="maputnik-data-spec-property-stop-data">
|
||||||
{dataInput}
|
{dataInput}
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-data-spec-property-stop-value">
|
<div className="maputnik-data-spec-property-stop-value">
|
||||||
<SpecField
|
<SpecField
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
|
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="maputnik-data-spec-block">
|
return <div className="maputnik-data-spec-block">
|
||||||
<div className="maputnik-data-spec-property">
|
<div className="maputnik-data-spec-property">
|
||||||
|
@ -223,18 +250,6 @@ export default class DataProperty extends React.Component {
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
label={labelFromFieldName(this.props.fieldName)}
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-group">
|
|
||||||
<DocLabel
|
|
||||||
label="Property"
|
|
||||||
/>
|
|
||||||
<div className="maputnik-data-spec-property-input">
|
|
||||||
<StringInput
|
|
||||||
value={this.props.value.property}
|
|
||||||
title={"Input a data property to base styles off of."}
|
|
||||||
onChange={propVal => this.changeDataProperty("property", propVal)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="maputnik-data-spec-property-group">
|
<div className="maputnik-data-spec-property-group">
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label="Type"
|
label="Type"
|
||||||
|
@ -250,26 +265,44 @@ export default class DataProperty extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-data-spec-property-group">
|
<div className="maputnik-data-spec-property-group">
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label="Default"
|
label="Property"
|
||||||
/>
|
/>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<SpecField
|
<StringInput
|
||||||
fieldName={this.props.fieldName}
|
value={this.props.value.property}
|
||||||
fieldSpec={this.props.fieldSpec}
|
title={"Input a data property to base styles off of."}
|
||||||
value={this.props.value.default}
|
onChange={propVal => this.changeDataProperty("property", propVal)}
|
||||||
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{dataFields &&
|
||||||
|
<div className="maputnik-data-spec-property-group">
|
||||||
|
<DocLabel
|
||||||
|
label="Default"
|
||||||
|
/>
|
||||||
|
<div className="maputnik-data-spec-property-input">
|
||||||
|
<SpecField
|
||||||
|
fieldName={this.props.fieldName}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
value={this.props.value.default}
|
||||||
|
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
</div>
|
</div>
|
||||||
{dataFields}
|
{dataFields &&
|
||||||
<Button
|
<>
|
||||||
className="maputnik-add-stop"
|
{dataFields}
|
||||||
onClick={this.props.onAddStop.bind(this)}
|
<Button
|
||||||
>
|
className="maputnik-add-stop"
|
||||||
Add stop
|
onClick={this.props.onAddStop.bind(this)}
|
||||||
</Button>
|
>
|
||||||
|
Add stop
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-add-stop"
|
className="maputnik-add-stop"
|
||||||
onClick={this.props.onExpressionClick.bind(this)}
|
onClick={this.props.onExpressionClick.bind(this)}
|
||||||
|
|
18
src/components/util/spec-helper.js
Normal file
18
src/components/util/spec-helper.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* If we don't have a default value just make one up
|
||||||
|
*/
|
||||||
|
export function findDefaultFromSpec (spec) {
|
||||||
|
if (spec.hasOwnProperty('default')) {
|
||||||
|
return spec.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
'color': '#000000',
|
||||||
|
'string': '',
|
||||||
|
'boolean': false,
|
||||||
|
'number': 0,
|
||||||
|
'array': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaults[spec.type] || '';
|
||||||
|
}
|
Loading…
Reference in a new issue