mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 16:31:16 +01:00
Added expression support for filters.
This commit is contained in:
parent
be36eec93d
commit
b539644b2b
7 changed files with 248 additions and 109 deletions
|
@ -318,7 +318,7 @@ export default class App extends React.Component {
|
||||||
|
|
||||||
onStyleChanged = (newStyle, save=true) => {
|
onStyleChanged = (newStyle, save=true) => {
|
||||||
|
|
||||||
const errors = validate(newStyle, latest)
|
const errors = validate(newStyle, latest) || [];
|
||||||
const mappedErrors = errors.map(error => {
|
const mappedErrors = errors.map(error => {
|
||||||
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
||||||
if (layerMatch) {
|
if (layerMatch) {
|
||||||
|
|
|
@ -8,6 +8,9 @@ import ExpressionProperty from './_ExpressionProperty'
|
||||||
import {expression} from '@mapbox/mapbox-gl-style-spec'
|
import {expression} from '@mapbox/mapbox-gl-style-spec'
|
||||||
|
|
||||||
|
|
||||||
|
function isLiteralExpression (value) {
|
||||||
|
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
|
||||||
|
}
|
||||||
|
|
||||||
function isZoomField(value) {
|
function isZoomField(value) {
|
||||||
return typeof value === 'object' && value.stops && typeof value.property === 'undefined'
|
return typeof value === 'object' && value.stops && typeof value.property === 'undefined'
|
||||||
|
@ -22,6 +25,7 @@ function isDataField(value) {
|
||||||
* properties accept arrays as values, for example `text-font`. So we must try
|
* properties accept arrays as values, for example `text-font`. So we must try
|
||||||
* and create an expression.
|
* and create an expression.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Use function from filter checks.
|
||||||
function checkIsExpression (value, fieldSpec={}) {
|
function checkIsExpression (value, fieldSpec={}) {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -111,8 +115,9 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.props.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteExpression = (newValue) => {
|
deleteExpression = () => {
|
||||||
this.props.onChange(this.props.fieldName, newValue);
|
const {fieldSpec, fieldName} = this.props;
|
||||||
|
this.props.onChange(fieldName, fieldSpec.default);
|
||||||
this.setState({
|
this.setState({
|
||||||
isExpression: false,
|
isExpression: false,
|
||||||
});
|
});
|
||||||
|
@ -144,6 +149,22 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
this.props.onChange(this.props.fieldName, zoomFunc)
|
this.props.onChange(this.props.fieldName, zoomFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
undoExpression = () => {
|
||||||
|
const {value, fieldName} = this.props;
|
||||||
|
|
||||||
|
if (isLiteralExpression(value)) {
|
||||||
|
this.props.onChange(fieldName, value[1]);
|
||||||
|
this.setState({
|
||||||
|
isExpression: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canUndo = () => {
|
||||||
|
const {value} = this.props;
|
||||||
|
return isLiteralExpression(value);
|
||||||
|
}
|
||||||
|
|
||||||
makeExpression = () => {
|
makeExpression = () => {
|
||||||
const expression = ["literal", this.props.value || this.props.fieldSpec.default];
|
const expression = ["literal", this.props.value || this.props.fieldSpec.default];
|
||||||
this.props.onChange(this.props.fieldName, expression);
|
this.props.onChange(this.props.fieldName, expression);
|
||||||
|
@ -176,6 +197,8 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
<ExpressionProperty
|
<ExpressionProperty
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
onChange={this.props.onChange.bind(this, this.props.fieldName)}
|
onChange={this.props.onChange.bind(this, this.props.fieldName)}
|
||||||
|
canUndo={this.canUndo}
|
||||||
|
onUndo={this.undoExpression}
|
||||||
onDelete={this.deleteExpression}
|
onDelete={this.deleteExpression}
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class PropertyGroup extends React.Component {
|
||||||
groupFields: PropTypes.array.isRequired,
|
groupFields: PropTypes.array.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
spec: PropTypes.object.isRequired,
|
spec: PropTypes.object.isRequired,
|
||||||
errors: PropTypes.array,
|
errors: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
onPropertyChange = (property, newValue) => {
|
onPropertyChange = (property, newValue) => {
|
||||||
|
|
|
@ -11,70 +11,38 @@ import stringifyPretty from 'json-stringify-pretty-compact'
|
||||||
import JSONEditor from '../layers/JSONEditor'
|
import JSONEditor from '../layers/JSONEditor'
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression (value) {
|
|
||||||
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ExpressionProperty extends React.Component {
|
export default class ExpressionProperty extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
fieldName: PropTypes.string,
|
fieldName: PropTypes.string,
|
||||||
fieldSpec: PropTypes.object,
|
fieldSpec: PropTypes.object,
|
||||||
value: PropTypes.object,
|
value: PropTypes.any,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
|
||||||
lastValue: props.value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange = (jsonVal) => {
|
|
||||||
if (isLiteralExpression(jsonVal)) {
|
|
||||||
this.setState({
|
|
||||||
lastValue: jsonVal
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(jsonVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
onReset = () => {
|
|
||||||
const {lastValue} = this.state;
|
|
||||||
const {value, fieldSpec} = this.props;
|
|
||||||
|
|
||||||
if (isLiteralExpression(value)) {
|
|
||||||
this.props.onDelete(value[1]);
|
|
||||||
}
|
|
||||||
else if (isLiteralExpression(lastValue)) {
|
|
||||||
this.props.onDelete(lastValue[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDelete = () => {
|
|
||||||
const {fieldSpec} = this.props;
|
|
||||||
this.props.onDelete(fieldSpec.default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {lastValue} = this.state;
|
const {value, canUndo} = this.props;
|
||||||
const {value} = this.props;
|
|
||||||
|
|
||||||
const canUndo = isLiteralExpression(value) || isLiteralExpression(lastValue);
|
|
||||||
const deleteStopBtn = (
|
const deleteStopBtn = (
|
||||||
<>
|
<>
|
||||||
|
{this.props.onUndo &&
|
||||||
|
<Button
|
||||||
|
key="undo_action"
|
||||||
|
onClick={this.props.onUndo}
|
||||||
|
disabled={canUndo ? canUndo() : false}
|
||||||
|
className="maputnik-delete-stop"
|
||||||
|
>
|
||||||
|
<MdUndo />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
<Button
|
<Button
|
||||||
onClick={this.onReset}
|
key="delete_action"
|
||||||
disabled={!canUndo}
|
onClick={this.props.onDelete}
|
||||||
className="maputnik-delete-stop"
|
|
||||||
>
|
|
||||||
<MdUndo />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={this.onDelete}
|
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
|
@ -96,7 +64,7 @@ export default class ExpressionProperty extends React.Component {
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
lineWrapping={true}
|
lineWrapping={true}
|
||||||
getValue={(data) => stringifyPretty(data, {indent: 2, maxLength: 50})}
|
getValue={(data) => stringifyPretty(data, {indent: 2, maxLength: 50})}
|
||||||
onChange={this.onChange}
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,66 @@ import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { combiningFilterOps } from '../../libs/filterops.js'
|
import { combiningFilterOps } from '../../libs/filterops.js'
|
||||||
|
|
||||||
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import DocLabel from '../fields/DocLabel'
|
import DocLabel from '../fields/DocLabel'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import SingleFilterEditor from './SingleFilterEditor'
|
import SingleFilterEditor from './SingleFilterEditor'
|
||||||
import FilterEditorBlock from './FilterEditorBlock'
|
import FilterEditorBlock from './FilterEditorBlock'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import SpecDoc from '../inputs/SpecDoc'
|
import SpecDoc from '../inputs/SpecDoc'
|
||||||
|
import ExpressionProperty from '../fields/_ExpressionProperty';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function migrateFilter (filter) {
|
||||||
|
return migrate(createStyleFromFilter(filter)).layers[0].filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStyleFromFilter (filter) {
|
||||||
|
return {
|
||||||
|
"id": "tmp",
|
||||||
|
"version": 8,
|
||||||
|
"name": "Empty Style",
|
||||||
|
"metadata": {"maputnik:renderer": "mbgljs"},
|
||||||
|
"sources": {
|
||||||
|
"tmp": {
|
||||||
|
"type": "geojson",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sprite": "",
|
||||||
|
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
id: "tmp",
|
||||||
|
type: "fill",
|
||||||
|
source: "tmp",
|
||||||
|
filter: filter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is doing way more work than we need it to, however validating a whole
|
||||||
|
* style if the only thing that's exported from mapbox-gl-style-spec at the
|
||||||
|
* moment. Not really an issue though as it take ~0.1ms to calculate.
|
||||||
|
*/
|
||||||
|
function checkIfSimpleFilter (filter) {
|
||||||
|
if (!filter || !combiningFilterOps.includes(filter[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because "none" isn't supported by the next expression syntax we can test
|
||||||
|
// with ["none", ...] because it'll return false if it's a new style
|
||||||
|
// expression.
|
||||||
|
const moddedFilter = ["none", ...filter.slice(1)];
|
||||||
|
const tmpStyle = createStyleFromFilter(moddedFilter)
|
||||||
|
|
||||||
|
const errors = validate(tmpStyle);
|
||||||
|
return (errors.length < 1);
|
||||||
|
}
|
||||||
|
|
||||||
function hasCombiningFilter(filter) {
|
function hasCombiningFilter(filter) {
|
||||||
return combiningFilterOps.indexOf(filter[0]) >= 0
|
return combiningFilterOps.indexOf(filter[0]) >= 0
|
||||||
|
@ -27,20 +80,21 @@ export default class CombiningFilterEditor extends React.Component {
|
||||||
/** Properties of the vector layer and the available fields */
|
/** Properties of the vector layer and the available fields */
|
||||||
properties: PropTypes.object,
|
properties: PropTypes.object,
|
||||||
filter: PropTypes.array,
|
filter: PropTypes.array,
|
||||||
errors: PropTypes.array,
|
errors: PropTypes.object,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor () {
|
constructor (props) {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
showDoc: false,
|
showDoc: false,
|
||||||
|
isSimpleFilter: checkIfSimpleFilter(this.combiningFilter(props)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert filter to combining filter
|
// Convert filter to combining filter
|
||||||
combiningFilter() {
|
combiningFilter(props=this.props) {
|
||||||
let filter = this.props.filter || ['all']
|
let filter = props.filter || ['all']
|
||||||
|
|
||||||
let combiningOp = filter[0]
|
let combiningOp = filter[0]
|
||||||
let filters = filter.slice(1)
|
let filters = filter.slice(1)
|
||||||
|
@ -61,7 +115,6 @@ export default class CombiningFilterEditor extends React.Component {
|
||||||
|
|
||||||
deleteFilterItem(filterIdx) {
|
deleteFilterItem(filterIdx) {
|
||||||
const newFilter = this.combiningFilter().slice(0)
|
const newFilter = this.combiningFilter().slice(0)
|
||||||
console.log('Delete', filterIdx, newFilter)
|
|
||||||
newFilter.splice(filterIdx + 1, 1)
|
newFilter.splice(filterIdx + 1, 1)
|
||||||
this.props.onChange(newFilter)
|
this.props.onChange(newFilter)
|
||||||
}
|
}
|
||||||
|
@ -78,70 +131,159 @@ export default class CombiningFilterEditor extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
makeExpression = () => {
|
||||||
const filter = this.combiningFilter()
|
let filter = this.combiningFilter();
|
||||||
let combiningOp = filter[0]
|
this.props.onChange(migrateFilter(filter));
|
||||||
let filters = filter.slice(1)
|
this.setState({
|
||||||
const {errors} = this.props;
|
isSimpleFilter: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps (props, currentState) {
|
||||||
|
const {filter} = props;
|
||||||
|
const isSimpleFilter = checkIfSimpleFilter(props.filter);
|
||||||
|
|
||||||
|
// Upgrade but never downgrade
|
||||||
|
if (!isSimpleFilter && currentState.isSimpleFilter === true) {
|
||||||
|
return {
|
||||||
|
isSimpleFilter: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {errors} = this.props;
|
||||||
|
const {isSimpleFilter} = this.state;
|
||||||
const fieldSpec={
|
const fieldSpec={
|
||||||
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
|
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
|
||||||
};
|
};
|
||||||
|
const defaultFilter = ["all"];
|
||||||
|
|
||||||
|
const isNestedCombiningFilter = isSimpleFilter && hasNestedCombiningFilter(this.combiningFilter());
|
||||||
|
|
||||||
|
if (isNestedCombiningFilter) {
|
||||||
|
return <div className="maputnik-filter-editor-unsupported">
|
||||||
|
<p>
|
||||||
|
Nested filters are not supported.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={this.makeExpression}
|
||||||
|
>
|
||||||
|
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12.42,5.29C11.32,5.19 10.35,6 10.25,7.11L10,10H12.82V12H9.82L9.38,17.07C9.18,19.27 7.24,20.9 5.04,20.7C3.79,20.59 2.66,19.9 2,18.83L3.5,17.33C3.83,18.38 4.96,18.97 6,18.63C6.78,18.39 7.33,17.7 7.4,16.89L7.82,12H4.82V10H8L8.27,6.93C8.46,4.73 10.39,3.1 12.6,3.28C13.86,3.39 15,4.09 15.66,5.17L14.16,6.67C13.91,5.9 13.23,5.36 12.42,5.29M22,13.65L20.59,12.24L17.76,15.07L14.93,12.24L13.5,13.65L16.35,16.5L13.5,19.31L14.93,20.72L17.76,17.89L20.59,20.72L22,19.31L19.17,16.5L22,13.65Z" />
|
||||||
|
</svg>
|
||||||
|
Upgrade to expression
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (isSimpleFilter) {
|
||||||
|
const filter = this.combiningFilter();
|
||||||
|
let combiningOp = filter[0];
|
||||||
|
let filters = filter.slice(1)
|
||||||
|
|
||||||
|
const actions = (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={this.makeExpression}
|
||||||
|
className="maputnik-make-zoom-function"
|
||||||
|
>
|
||||||
|
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12.42,5.29C11.32,5.19 10.35,6 10.25,7.11L10,10H12.82V12H9.82L9.38,17.07C9.18,19.27 7.24,20.9 5.04,20.7C3.79,20.59 2.66,19.9 2,18.83L3.5,17.33C3.83,18.38 4.96,18.97 6,18.63C6.78,18.39 7.33,17.7 7.4,16.89L7.82,12H4.82V10H8L8.27,6.93C8.46,4.73 10.39,3.1 12.6,3.28C13.86,3.39 15,4.09 15.66,5.17L14.16,6.67C13.91,5.9 13.23,5.36 12.42,5.29M22,13.65L20.59,12.24L17.76,15.07L14.93,12.24L13.5,13.65L16.35,16.5L13.5,19.31L14.93,20.72L17.76,17.89L20.59,20.72L22,19.31L19.17,16.5L22,13.65Z" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const editorBlocks = filters.map((f, idx) => {
|
||||||
|
const error = errors[`filter[${idx+1}]`];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterEditorBlock key={idx} onDelete={this.deleteFilterItem.bind(this, idx)}>
|
||||||
|
<SingleFilterEditor
|
||||||
|
properties={this.props.properties}
|
||||||
|
filter={f}
|
||||||
|
onChange={this.onFilterPartChanged.bind(this, idx + 1)}
|
||||||
|
/>
|
||||||
|
</FilterEditorBlock>
|
||||||
|
{error &&
|
||||||
|
<div className="maputnik-inline-error">{error.message}</div>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
const editorBlocks = filters.map((f, idx) => {
|
|
||||||
const error = errors[`filter[${idx+1}]`];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FilterEditorBlock key={idx} onDelete={this.deleteFilterItem.bind(this, idx)}>
|
<InputBlock
|
||||||
<SingleFilterEditor
|
key="top"
|
||||||
properties={this.props.properties}
|
fieldSpec={fieldSpec}
|
||||||
filter={f}
|
label={"Compound filter"}
|
||||||
onChange={this.onFilterPartChanged.bind(this, idx + 1)}
|
action={actions}
|
||||||
|
>
|
||||||
|
<SelectInput
|
||||||
|
value={combiningOp}
|
||||||
|
onChange={this.onFilterPartChanged.bind(this, 0)}
|
||||||
|
options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]}
|
||||||
/>
|
/>
|
||||||
</FilterEditorBlock>
|
</InputBlock>
|
||||||
{error &&
|
{editorBlocks}
|
||||||
<div className="maputnik-inline-error">{error.message}</div>
|
<div
|
||||||
}
|
key="buttons"
|
||||||
|
className="maputnik-filter-editor-add-wrapper"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
data-wd-key="layer-filter-button"
|
||||||
|
className="maputnik-add-filter"
|
||||||
|
onClick={this.addFilterItem}>
|
||||||
|
Add filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
key="doc"
|
||||||
|
className="maputnik-doc-inline"
|
||||||
|
style={{display: this.state.showDoc ? '' : 'none'}}
|
||||||
|
>
|
||||||
|
<SpecDoc fieldSpec={fieldSpec} />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})
|
|
||||||
|
|
||||||
//TODO: Implement support for nested filter
|
|
||||||
if(hasNestedCombiningFilter(filter)) {
|
|
||||||
return <div className="maputnik-filter-editor-unsupported">
|
|
||||||
Nested filters are not supported.
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
let {filter} = this.props;
|
||||||
|
|
||||||
return <div className="maputnik-filter-editor">
|
if (!filter) {
|
||||||
<div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
|
filter = defaultFilter;
|
||||||
<DocLabel
|
}
|
||||||
label={"Compound Filter"}
|
else if (isNestedCombiningFilter) {
|
||||||
onToggleDoc={this.onToggleDoc}
|
filter = migrateFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = Object.entries(errors)
|
||||||
|
.filter(([k, v]) => k.match(/filter(\[\d+\])?/))
|
||||||
|
.map(([k, v]) => {
|
||||||
|
return v.message;
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
const error = errorMessage ? {message: errorMessage} : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExpressionProperty
|
||||||
|
onDelete={() => {
|
||||||
|
this.setState({isSimpleFilter: true});
|
||||||
|
this.props.onChange(defaultFilter);
|
||||||
|
}}
|
||||||
|
fieldName="filter-compound-filter"
|
||||||
fieldSpec={fieldSpec}
|
fieldSpec={fieldSpec}
|
||||||
|
value={filter}
|
||||||
|
error={error}
|
||||||
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
<SelectInput
|
);
|
||||||
value={combiningOp}
|
}
|
||||||
onChange={this.onFilterPartChanged.bind(this, 0)}
|
|
||||||
options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{editorBlocks}
|
|
||||||
<div className="maputnik-filter-editor-add-wrapper">
|
|
||||||
<Button
|
|
||||||
data-wd-key="layer-filter-button"
|
|
||||||
className="maputnik-add-filter"
|
|
||||||
onClick={this.addFilterItem}>
|
|
||||||
Add filter
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="maputnik-doc-inline"
|
|
||||||
style={{display: this.state.showDoc ? '' : 'none'}}
|
|
||||||
>
|
|
||||||
<SpecDoc fieldSpec={fieldSpec} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'codemirror/lib/codemirror.css'
|
||||||
import 'codemirror/addon/lint/lint.css'
|
import 'codemirror/addon/lint/lint.css'
|
||||||
import '../../codemirror-maputnik.css'
|
import '../../codemirror-maputnik.css'
|
||||||
import jsonlint from 'jsonlint'
|
import jsonlint from 'jsonlint'
|
||||||
|
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||||
|
|
||||||
// This is mainly because of this issue <https://github.com/zaach/jsonlint/issues/57> also the API has changed, see comment in file
|
// This is mainly because of this issue <https://github.com/zaach/jsonlint/issues/57> also the API has changed, see comment in file
|
||||||
import '../../vendor/codemirror/addon/lint/json-lint'
|
import '../../vendor/codemirror/addon/lint/json-lint'
|
||||||
|
@ -20,7 +21,7 @@ import '../../vendor/codemirror/addon/lint/json-lint'
|
||||||
|
|
||||||
class JSONEditor extends React.Component {
|
class JSONEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
layer: PropTypes.object.isRequired,
|
layer: PropTypes.any.isRequired,
|
||||||
maxHeight: PropTypes.number,
|
maxHeight: PropTypes.number,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
lineNumbers: PropTypes.bool,
|
lineNumbers: PropTypes.bool,
|
||||||
|
@ -35,7 +36,7 @@ class JSONEditor extends React.Component {
|
||||||
lineWrapping: false,
|
lineWrapping: false,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
getValue: (data) => {
|
getValue: (data) => {
|
||||||
return JSON.stringify(data, null, 2)
|
return stringifyPretty(data, {indent: 2, maxLength: 50} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
.maputnik-filter-editor-wrapper {
|
.maputnik-filter-editor-wrapper {
|
||||||
padding: $margin-3;
|
padding: $margin-3;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.maputnik-input-block {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-filter-editor {
|
.maputnik-filter-editor {
|
||||||
|
|
Loading…
Reference in a new issue