mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 16:41:17 +01:00
Added inline errors to the code-mirror editors based on field spec.
This commit is contained in:
parent
be7642976b
commit
ce976991d4
6 changed files with 185 additions and 8 deletions
|
@ -30,7 +30,8 @@
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"detect-browser": "^4.8.0",
|
"detect-browser": "^4.8.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
"json-to-ast": "^2.1.0",
|
||||||
|
"jsonlint": "^1.6.3",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash.capitalize": "^4.2.1",
|
"lodash.capitalize": "^4.2.1",
|
||||||
"lodash.clamp": "^4.0.3",
|
"lodash.clamp": "^4.0.3",
|
||||||
|
|
|
@ -358,7 +358,9 @@ export default class App extends React.Component {
|
||||||
if (message) {
|
if (message) {
|
||||||
try {
|
try {
|
||||||
const objPath = message.split(":")[0];
|
const objPath = message.split(":")[0];
|
||||||
unset(dirtyMapStyle, objPath);
|
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
||||||
|
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)[0];
|
||||||
|
unset(dirtyMapStyle, unsetPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
|
|
|
@ -84,6 +84,10 @@ export default class ExpressionProperty extends React.Component {
|
||||||
const errorKeyStart = `${fieldKey}[`;
|
const errorKeyStart = `${fieldKey}[`;
|
||||||
const foundErrors = [];
|
const foundErrors = [];
|
||||||
|
|
||||||
|
function getValue (data) {
|
||||||
|
return stringifyPretty(data, {indent: 2, maxLength: 38})
|
||||||
|
}
|
||||||
|
|
||||||
if (jsonError) {
|
if (jsonError) {
|
||||||
foundErrors.push({message: "Invalid JSON"});
|
foundErrors.push({message: "Invalid JSON"});
|
||||||
}
|
}
|
||||||
|
@ -109,6 +113,11 @@ export default class ExpressionProperty extends React.Component {
|
||||||
wideMode={true}
|
wideMode={true}
|
||||||
>
|
>
|
||||||
<JSONEditor
|
<JSONEditor
|
||||||
|
mode={{name: "mgl"}}
|
||||||
|
lint={{
|
||||||
|
context: "expression",
|
||||||
|
spec: this.props.fieldSpec,
|
||||||
|
}}
|
||||||
className="maputnik-expression-editor"
|
className="maputnik-expression-editor"
|
||||||
onFocus={this.props.onFocus}
|
onFocus={this.props.onFocus}
|
||||||
onBlur={this.props.onBlur}
|
onBlur={this.props.onBlur}
|
||||||
|
@ -118,7 +127,7 @@ export default class ExpressionProperty extends React.Component {
|
||||||
lineNumbers={false}
|
lineNumbers={false}
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
lineWrapping={true}
|
lineWrapping={true}
|
||||||
getValue={(data) => stringifyPretty(data, {indent: 2, maxLength: 50})}
|
getValue={getValue}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
|
@ -16,6 +16,10 @@ import ExpressionProperty from '../fields/_ExpressionProperty';
|
||||||
function combiningFilter (props) {
|
function combiningFilter (props) {
|
||||||
let filter = props.filter || ['all'];
|
let filter = props.filter || ['all'];
|
||||||
|
|
||||||
|
if (!Array.isArray(filter)) {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
let combiningOp = filter[0];
|
let combiningOp = filter[0];
|
||||||
let filters = filter.slice(1);
|
let filters = filter.slice(1);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'codemirror/lib/codemirror.css'
|
||||||
import 'codemirror/addon/lint/lint.css'
|
import 'codemirror/addon/lint/lint.css'
|
||||||
import jsonlint from 'jsonlint'
|
import jsonlint from 'jsonlint'
|
||||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||||
|
import '../util/codemirror-mgl';
|
||||||
|
|
||||||
// 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'
|
||||||
|
@ -32,6 +33,11 @@ class JSONEditor extends React.Component {
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onJSONValid: PropTypes.func,
|
onJSONValid: PropTypes.func,
|
||||||
onJSONInvalid: PropTypes.func,
|
onJSONInvalid: PropTypes.func,
|
||||||
|
mode: PropTypes.object,
|
||||||
|
lint: PropTypes.oneOfType([
|
||||||
|
PropTypes.bool,
|
||||||
|
PropTypes.object,
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -39,7 +45,7 @@ class JSONEditor extends React.Component {
|
||||||
lineWrapping: false,
|
lineWrapping: false,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
getValue: (data) => {
|
getValue: (data) => {
|
||||||
return stringifyPretty(data, {indent: 2, maxLength: 50});
|
return stringifyPretty(data, {indent: 2, maxLength: 40});
|
||||||
},
|
},
|
||||||
onFocus: () => {},
|
onFocus: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
|
@ -58,16 +64,17 @@ class JSONEditor extends React.Component {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._doc = CodeMirror(this._el, {
|
this._doc = CodeMirror(this._el, {
|
||||||
value: this.props.getValue(this.props.layer),
|
value: this.props.getValue(this.props.layer),
|
||||||
mode: {
|
mode: this.props.mode || {
|
||||||
name: "javascript",
|
name: "mgl",
|
||||||
json: true
|
|
||||||
},
|
},
|
||||||
lineWrapping: this.props.lineWrapping,
|
lineWrapping: this.props.lineWrapping,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
theme: 'maputnik',
|
theme: 'maputnik',
|
||||||
viewportMargin: Infinity,
|
viewportMargin: Infinity,
|
||||||
lineNumbers: this.props.lineNumbers,
|
lineNumbers: this.props.lineNumbers,
|
||||||
lint: true,
|
lint: this.props.lint || {
|
||||||
|
context: "layer"
|
||||||
|
},
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
gutters: this.props.gutters,
|
gutters: this.props.gutters,
|
||||||
scrollbarStyle: "null",
|
scrollbarStyle: "null",
|
||||||
|
|
154
src/components/util/codemirror-mgl.js
Normal file
154
src/components/util/codemirror-mgl.js
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import jsonlint from 'jsonlint';
|
||||||
|
import CodeMirror from 'codemirror';
|
||||||
|
import jsonToAst from 'json-to-ast';
|
||||||
|
import {expression, validate, latest} from '@mapbox/mapbox-gl-style-spec';
|
||||||
|
|
||||||
|
|
||||||
|
CodeMirror.defineMode("mgl", function(config, parserConfig) {
|
||||||
|
// Just using the javascript mode with json enabled. Our logic is in the linter below.
|
||||||
|
return CodeMirror.modes.javascript(
|
||||||
|
{...config, json: true},
|
||||||
|
parserConfig
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
||||||
|
const found = [];
|
||||||
|
const {parser} = jsonlint;
|
||||||
|
const {context} = opts;
|
||||||
|
|
||||||
|
parser.parseError = function(str, hash) {
|
||||||
|
const loc = hash.loc;
|
||||||
|
found.push({
|
||||||
|
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||||
|
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
||||||
|
message: str
|
||||||
|
});
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
parser.parse(text);
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
|
||||||
|
if (found.length > 0) {
|
||||||
|
// JSON invalid so don't go any further
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = jsonToAst(text);
|
||||||
|
const input = JSON.parse(text);
|
||||||
|
|
||||||
|
function getArrayPositionalFromAst (node, path) {
|
||||||
|
if (!node) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
else if (path.length < 1) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
else if (!node.children) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const key = path[0];
|
||||||
|
let newNode;
|
||||||
|
if (key.match(/^[0-9]+$/)) {
|
||||||
|
newNode = node.children[path[0]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newNode = node.children.find(childNode => {
|
||||||
|
return (
|
||||||
|
childNode.key &&
|
||||||
|
childNode.key.type === "Identifier" &&
|
||||||
|
childNode.key.value === key
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (newNode) {
|
||||||
|
newNode = newNode.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getArrayPositionalFromAst(newNode, path.slice(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let out;
|
||||||
|
if (context === "layer") {
|
||||||
|
// Just an empty style so we can validate a layer.
|
||||||
|
const errors = validate({
|
||||||
|
"version": 8,
|
||||||
|
"name": "Empty Style",
|
||||||
|
"metadata": {},
|
||||||
|
"sources": {},
|
||||||
|
"sprite": "",
|
||||||
|
"glyphs": "https://example.com/glyphs/{fontstack}/{range}.pbf",
|
||||||
|
"layers": [
|
||||||
|
input
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
out = {
|
||||||
|
result: "error",
|
||||||
|
value: errors
|
||||||
|
.filter(err => {
|
||||||
|
// Remove missing 'layer source' errors, because we don't include them
|
||||||
|
if (err.message.match(/^layers\[0\]: source ".*" not found$/)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(err => {
|
||||||
|
// Remove the 'layers[0].' as we're validating the layer only here
|
||||||
|
const errMessageParts = err.message.replace(/^layers\[0\]./, "").split(":");
|
||||||
|
return {
|
||||||
|
key: errMessageParts[0],
|
||||||
|
message: errMessageParts[1],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (context === "expression") {
|
||||||
|
out = expression.createExpression(input, opts.spec);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Invalid context ${context}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.result === "error") {
|
||||||
|
const errors = out.value;
|
||||||
|
errors.forEach(error => {
|
||||||
|
const {key, message} = error;
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
const lastLineHandle = doc.getLineHandle(doc.lastLine());
|
||||||
|
const err = {
|
||||||
|
from: CodeMirror.Pos(doc.firstLine(), 0),
|
||||||
|
to: CodeMirror.Pos(doc.lastLine(), lastLineHandle.text.length),
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
found.push(err);
|
||||||
|
}
|
||||||
|
else if (key) {
|
||||||
|
const path = key.replace(/^\[|\]$/g, "").split(/\.|[\[\]]+/).filter(Boolean)
|
||||||
|
const parsedError = getArrayPositionalFromAst(ast, path);
|
||||||
|
if (!parsedError) {
|
||||||
|
console.warn("Something went wrong parsing error:", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {loc} = parsedError;
|
||||||
|
const {start, end} = loc;
|
||||||
|
|
||||||
|
found.push({
|
||||||
|
from: CodeMirror.Pos(start.line - 1, start.column),
|
||||||
|
to: CodeMirror.Pos(end.line - 1, end.column),
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
});
|
Loading…
Reference in a new issue