mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-11-10 09:07:47 +01:00
Better support for expressions
- Expression editing state - CodeMirror JSON editor - Improved styling
This commit is contained in:
parent
725b752e35
commit
c5c3e93aff
5 changed files with 78 additions and 29 deletions
|
@ -22,7 +22,7 @@ function isDataField(value) {
|
|||
* properties accept arrays as values, for example `text-font`. So we must try
|
||||
* and create an expression.
|
||||
*/
|
||||
function isExpression(value, fieldSpec={}) {
|
||||
function checkIsExpression (value, fieldSpec={}) {
|
||||
if (!Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -72,6 +72,13 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
]),
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super();
|
||||
this.state = {
|
||||
isExpression: checkIsExpression(props.value, props.fieldSpec),
|
||||
}
|
||||
}
|
||||
|
||||
getFieldFunctionType(fieldSpec) {
|
||||
if (fieldSpec.expression.interpolated) {
|
||||
return "exponential"
|
||||
|
@ -103,6 +110,13 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
this.props.onChange(this.props.fieldName, changedValue)
|
||||
}
|
||||
|
||||
deleteExpression = (newValue) => {
|
||||
this.props.onChange(this.props.fieldName, newValue);
|
||||
this.setState({
|
||||
isExpression: false,
|
||||
});
|
||||
}
|
||||
|
||||
deleteStop = (stopIdx) => {
|
||||
const stops = this.props.value.stops.slice(0)
|
||||
stops.splice(stopIdx, 1)
|
||||
|
@ -132,6 +146,10 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
makeExpression = () => {
|
||||
const expression = ["literal", this.props.value || this.props.fieldSpec.default];
|
||||
this.props.onChange(this.props.fieldName, expression);
|
||||
|
||||
this.setState({
|
||||
isExpression: true,
|
||||
});
|
||||
}
|
||||
|
||||
makeDataFunction = () => {
|
||||
|
@ -152,11 +170,12 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
|
||||
let specField;
|
||||
|
||||
if (isExpression(this.props.value, this.props.fieldSpec)) {
|
||||
if (this.state.isExpression) {
|
||||
specField = (
|
||||
<ExpressionProperty
|
||||
error={this.props.error}
|
||||
onChange={this.props.onChange.bind(this, this.props.fieldName)}
|
||||
onDelete={this.deleteExpression}
|
||||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={this.props.value}
|
||||
|
|
|
@ -8,6 +8,7 @@ import StringInput from '../inputs/StringInput'
|
|||
|
||||
import labelFromFieldName from './_labelFromFieldName'
|
||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||
import JSONEditor from '../layers/JSONEditor'
|
||||
|
||||
|
||||
function isLiteralExpression (value) {
|
||||
|
@ -16,7 +17,7 @@ function isLiteralExpression (value) {
|
|||
|
||||
export default class ExpressionProperty extends React.Component {
|
||||
static propTypes = {
|
||||
onDeleteStop: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
fieldName: PropTypes.string,
|
||||
fieldSpec: PropTypes.object
|
||||
}
|
||||
|
@ -28,21 +29,14 @@ export default class ExpressionProperty extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
try {
|
||||
const jsonVal = JSON.parse(value);
|
||||
|
||||
if (isLiteralExpression(jsonVal)) {
|
||||
this.setState({
|
||||
lastValue: jsonVal
|
||||
});
|
||||
}
|
||||
|
||||
this.props.onChange(jsonVal);
|
||||
}
|
||||
catch (err) {
|
||||
// TODO: Handle JSON parse error
|
||||
onChange = (jsonVal) => {
|
||||
if (isLiteralExpression(jsonVal)) {
|
||||
this.setState({
|
||||
lastValue: jsonVal
|
||||
});
|
||||
}
|
||||
|
||||
this.props.onChange(jsonVal);
|
||||
}
|
||||
|
||||
onDelete = () => {
|
||||
|
@ -50,13 +44,13 @@ export default class ExpressionProperty extends React.Component {
|
|||
const {value, fieldName, fieldSpec} = this.props;
|
||||
|
||||
if (isLiteralExpression(value)) {
|
||||
this.props.onChange(value[1]);
|
||||
this.props.onDelete(value[1]);
|
||||
}
|
||||
else if (isLiteralExpression(lastValue)) {
|
||||
this.props.onChange(lastValue[1]);
|
||||
this.props.onDelete(lastValue[1]);
|
||||
}
|
||||
else {
|
||||
this.props.onChange(fieldSpec.default);
|
||||
this.props.onDelete(fieldSpec.default);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +69,16 @@ export default class ExpressionProperty extends React.Component {
|
|||
doc={this.props.fieldSpec.doc}
|
||||
label={labelFromFieldName(this.props.fieldName)}
|
||||
action={deleteStopBtn}
|
||||
wideMode={true}
|
||||
>
|
||||
<StringInput
|
||||
multi={true}
|
||||
value={stringifyPretty(this.props.value, {indent: 2})}
|
||||
spellCheck={false}
|
||||
onInput={this.onChange}
|
||||
<JSONEditor
|
||||
className="maputnik-expression-editor"
|
||||
layer={this.props.value}
|
||||
lineNumbers={false}
|
||||
maxHeight={200}
|
||||
lineWrapping={true}
|
||||
getValue={(data) => stringifyPretty(data, {indent: 2, maxLength: 50})}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</InputBlock>
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ class InputBlock extends React.Component {
|
|||
data-wd-key={this.props["data-wd-key"]}
|
||||
className={classnames({
|
||||
"maputnik-input-block": true,
|
||||
"maputnik-input-block--wide": this.props.wideMode,
|
||||
"maputnik-action-block": this.props.action
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames';
|
||||
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import StringInput from '../inputs/StringInput'
|
||||
|
@ -22,6 +23,15 @@ class JSONEditor extends React.Component {
|
|||
layer: PropTypes.object.isRequired,
|
||||
maxHeight: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
lineNumbers: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
lineNumbers: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
getValue: (data) => {
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -33,7 +43,7 @@ class JSONEditor extends React.Component {
|
|||
}
|
||||
|
||||
getValue () {
|
||||
return JSON.stringify(this.props.layer, null, 2);
|
||||
return this.props.getValue(this.props.layer);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
|
@ -43,13 +53,14 @@ class JSONEditor extends React.Component {
|
|||
name: "javascript",
|
||||
json: true
|
||||
},
|
||||
lineWrapping: this.props.lineWrapping,
|
||||
tabSize: 2,
|
||||
theme: 'maputnik',
|
||||
viewportMargin: Infinity,
|
||||
lineNumbers: true,
|
||||
lineNumbers: this.props.lineNumbers,
|
||||
lint: true,
|
||||
matchBrackets: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
gutters: this.props.gutters,
|
||||
scrollbarStyle: "null",
|
||||
});
|
||||
|
||||
|
@ -113,7 +124,7 @@ class JSONEditor extends React.Component {
|
|||
}
|
||||
|
||||
return <div
|
||||
className="codemirror-container"
|
||||
className={classnames("codemirror-container", this.props.className)}
|
||||
ref={(el) => this._el = el}
|
||||
style={style}
|
||||
/>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
height: 14px;
|
||||
}
|
||||
|
||||
// TODO: Move these into correct *.scss files
|
||||
.maputnik-inline-error {
|
||||
color: #a4a4a4;
|
||||
padding: 0.4em 0.4em;
|
||||
|
@ -43,3 +44,22 @@
|
|||
border-radius: 2px;
|
||||
margin: $margin-2 0px;
|
||||
}
|
||||
|
||||
.maputnik-expression-editor {
|
||||
border: solid 1px $color-gray;
|
||||
}
|
||||
|
||||
.maputnik-input-block--wide {
|
||||
.maputnik-input-block-content {
|
||||
display: block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.maputnik-input-block-label {
|
||||
width: 82%;
|
||||
}
|
||||
|
||||
.maputnik-input-block-action {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue