mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 16:41:17 +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
|
* properties accept arrays as values, for example `text-font`. So we must try
|
||||||
* and create an expression.
|
* and create an expression.
|
||||||
*/
|
*/
|
||||||
function isExpression(value, fieldSpec={}) {
|
function checkIsExpression (value, fieldSpec={}) {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return false;
|
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) {
|
getFieldFunctionType(fieldSpec) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return "exponential"
|
return "exponential"
|
||||||
|
@ -103,6 +110,13 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.props.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteExpression = (newValue) => {
|
||||||
|
this.props.onChange(this.props.fieldName, newValue);
|
||||||
|
this.setState({
|
||||||
|
isExpression: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deleteStop = (stopIdx) => {
|
deleteStop = (stopIdx) => {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
stops.splice(stopIdx, 1)
|
stops.splice(stopIdx, 1)
|
||||||
|
@ -132,6 +146,10 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
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);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isExpression: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeDataFunction = () => {
|
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"
|
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
|
||||||
let specField;
|
let specField;
|
||||||
|
|
||||||
if (isExpression(this.props.value, this.props.fieldSpec)) {
|
if (this.state.isExpression) {
|
||||||
specField = (
|
specField = (
|
||||||
<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)}
|
||||||
|
onDelete={this.deleteExpression}
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import StringInput from '../inputs/StringInput'
|
||||||
|
|
||||||
import labelFromFieldName from './_labelFromFieldName'
|
import labelFromFieldName from './_labelFromFieldName'
|
||||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||||
|
import JSONEditor from '../layers/JSONEditor'
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression (value) {
|
function isLiteralExpression (value) {
|
||||||
|
@ -16,7 +17,7 @@ function isLiteralExpression (value) {
|
||||||
|
|
||||||
export default class ExpressionProperty extends React.Component {
|
export default class ExpressionProperty extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onDeleteStop: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
fieldName: PropTypes.string,
|
fieldName: PropTypes.string,
|
||||||
fieldSpec: PropTypes.object
|
fieldSpec: PropTypes.object
|
||||||
}
|
}
|
||||||
|
@ -28,10 +29,7 @@ export default class ExpressionProperty extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (value) => {
|
onChange = (jsonVal) => {
|
||||||
try {
|
|
||||||
const jsonVal = JSON.parse(value);
|
|
||||||
|
|
||||||
if (isLiteralExpression(jsonVal)) {
|
if (isLiteralExpression(jsonVal)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
lastValue: jsonVal
|
lastValue: jsonVal
|
||||||
|
@ -40,23 +38,19 @@ export default class ExpressionProperty extends React.Component {
|
||||||
|
|
||||||
this.props.onChange(jsonVal);
|
this.props.onChange(jsonVal);
|
||||||
}
|
}
|
||||||
catch (err) {
|
|
||||||
// TODO: Handle JSON parse error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDelete = () => {
|
onDelete = () => {
|
||||||
const {lastValue} = this.state;
|
const {lastValue} = this.state;
|
||||||
const {value, fieldName, fieldSpec} = this.props;
|
const {value, fieldName, fieldSpec} = this.props;
|
||||||
|
|
||||||
if (isLiteralExpression(value)) {
|
if (isLiteralExpression(value)) {
|
||||||
this.props.onChange(value[1]);
|
this.props.onDelete(value[1]);
|
||||||
}
|
}
|
||||||
else if (isLiteralExpression(lastValue)) {
|
else if (isLiteralExpression(lastValue)) {
|
||||||
this.props.onChange(lastValue[1]);
|
this.props.onDelete(lastValue[1]);
|
||||||
}
|
}
|
||||||
else {
|
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}
|
doc={this.props.fieldSpec.doc}
|
||||||
label={labelFromFieldName(this.props.fieldName)}
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
action={deleteStopBtn}
|
action={deleteStopBtn}
|
||||||
|
wideMode={true}
|
||||||
>
|
>
|
||||||
<StringInput
|
<JSONEditor
|
||||||
multi={true}
|
className="maputnik-expression-editor"
|
||||||
value={stringifyPretty(this.props.value, {indent: 2})}
|
layer={this.props.value}
|
||||||
spellCheck={false}
|
lineNumbers={false}
|
||||||
onInput={this.onChange}
|
maxHeight={200}
|
||||||
|
lineWrapping={true}
|
||||||
|
getValue={(data) => stringifyPretty(data, {indent: 2, maxLength: 50})}
|
||||||
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ class InputBlock extends React.Component {
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
className={classnames({
|
className={classnames({
|
||||||
"maputnik-input-block": true,
|
"maputnik-input-block": true,
|
||||||
|
"maputnik-input-block--wide": this.props.wideMode,
|
||||||
"maputnik-action-block": this.props.action
|
"maputnik-action-block": this.props.action
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
|
@ -22,6 +23,15 @@ class JSONEditor extends React.Component {
|
||||||
layer: PropTypes.object.isRequired,
|
layer: PropTypes.object.isRequired,
|
||||||
maxHeight: PropTypes.number,
|
maxHeight: PropTypes.number,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
lineNumbers: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
lineNumbers: true,
|
||||||
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
|
getValue: (data) => {
|
||||||
|
return JSON.stringify(data, null, 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -33,7 +43,7 @@ class JSONEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue () {
|
getValue () {
|
||||||
return JSON.stringify(this.props.layer, null, 2);
|
return this.props.getValue(this.props.layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -43,13 +53,14 @@ class JSONEditor extends React.Component {
|
||||||
name: "javascript",
|
name: "javascript",
|
||||||
json: true
|
json: true
|
||||||
},
|
},
|
||||||
|
lineWrapping: this.props.lineWrapping,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
theme: 'maputnik',
|
theme: 'maputnik',
|
||||||
viewportMargin: Infinity,
|
viewportMargin: Infinity,
|
||||||
lineNumbers: true,
|
lineNumbers: this.props.lineNumbers,
|
||||||
lint: true,
|
lint: true,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
gutters: this.props.gutters,
|
||||||
scrollbarStyle: "null",
|
scrollbarStyle: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,7 +124,7 @@ class JSONEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
className="codemirror-container"
|
className={classnames("codemirror-container", this.props.className)}
|
||||||
ref={(el) => this._el = el}
|
ref={(el) => this._el = el}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move these into correct *.scss files
|
||||||
.maputnik-inline-error {
|
.maputnik-inline-error {
|
||||||
color: #a4a4a4;
|
color: #a4a4a4;
|
||||||
padding: 0.4em 0.4em;
|
padding: 0.4em 0.4em;
|
||||||
|
@ -43,3 +44,22 @@
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin: $margin-2 0px;
|
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