diff --git a/package.json b/package.json
index 5471a5dd..aed9b823 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
"license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
- "@mapbox/mapbox-gl-style-spec": "^9.0.0",
"@mapbox/mapbox-gl-rtl-text": "^0.1.0",
+ "@mapbox/mapbox-gl-style-spec": "^9.0.1",
"classnames": "^2.2.5",
"codemirror": "^5.18.2",
"color": "^1.0.3",
@@ -32,8 +32,9 @@
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.4.0",
"lodash.throttle": "^4.1.1",
- "mapbox-gl": "^0.34.0",
+ "mapbox-gl": "^0.40.1",
"mapbox-gl-inspect": "^1.2.3",
+ "maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.0",
"ol-mapbox-style": "1.0.1",
"openlayers": "^3.19.1",
@@ -41,7 +42,7 @@
"react-addons-pure-render-mixin": "^15.4.0",
"react-autocomplete": "^1.4.0",
"react-codemirror": "^0.3.0",
- "react-collapse": "^4.0.2",
+ "react-collapse": "^4.0.3",
"react-color": "^2.10.0",
"react-dom": "^15.4.0",
"react-file-reader-input": "^1.1.0",
diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx
index bcb60f8d..9bf7ad68 100644
--- a/src/components/Toolbar.jsx
+++ b/src/components/Toolbar.jsx
@@ -16,7 +16,7 @@ import MdFontDownload from 'react-icons/lib/md/font-download'
import HelpIcon from 'react-icons/lib/md/help-outline'
import InspectionIcon from 'react-icons/lib/md/find-in-page'
-import logoImage from '../img/maputnik.png'
+import logoImage from 'maputnik-design/logos/logo-color.svg'
import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
import SourcesModal from './modals/SourcesModal'
diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx
new file mode 100644
index 00000000..2b7383b9
--- /dev/null
+++ b/src/components/fields/FunctionSpecField.jsx
@@ -0,0 +1,352 @@
+import React from 'react'
+import Color from 'color'
+
+import Button from '../Button'
+import SpecField from './SpecField'
+import NumberInput from '../inputs/NumberInput'
+import StringInput from '../inputs/StringInput'
+import SelectInput from '../inputs/SelectInput'
+import DocLabel from './DocLabel'
+import InputBlock from '../inputs/InputBlock'
+
+import AddIcon from 'react-icons/lib/md/add-circle-outline'
+import DeleteIcon from 'react-icons/lib/md/delete'
+import FunctionIcon from 'react-icons/lib/md/functions'
+import MdInsertChart from 'react-icons/lib/md/insert-chart'
+
+import capitalize from 'lodash.capitalize'
+
+function isZoomField(value) {
+ return typeof value === 'object' && value.stops && typeof value.property === 'undefined'
+}
+
+function isDataField(value) {
+ return typeof value === 'object' && value.stops && typeof value.property !== 'undefined'
+}
+
+/** Supports displaying spec field for zoom function objects
+ * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
+ */
+export default class FunctionSpecProperty extends React.Component {
+ static propTypes = {
+ onChange: React.PropTypes.func.isRequired,
+ fieldName: React.PropTypes.string.isRequired,
+ fieldSpec: React.PropTypes.object.isRequired,
+
+ value: React.PropTypes.oneOfType([
+ React.PropTypes.object,
+ React.PropTypes.string,
+ React.PropTypes.number,
+ React.PropTypes.bool,
+ React.PropTypes.array
+ ]),
+ }
+
+ addStop() {
+ const stops = this.props.value.stops.slice(0)
+ const lastStop = stops[stops.length - 1]
+ if (typeof lastStop[0] === "object") {
+ stops.push([
+ {zoom: lastStop[0].zoom + 1, value: lastStop[0].value},
+ lastStop[1]
+ ])
+ }
+ else {
+ stops.push([lastStop[0] + 1, lastStop[1]])
+ }
+
+ const changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ deleteStop(stopIdx) {
+ const stops = this.props.value.stops.slice(0)
+ stops.splice(stopIdx, 1)
+
+ let changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+
+ if(stops.length === 1) {
+ changedValue = stops[0][1]
+ }
+
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ makeZoomFunction() {
+ const zoomFunc = {
+ stops: [
+ [6, this.props.value],
+ [10, this.props.value]
+ ]
+ }
+ this.props.onChange(this.props.fieldName, zoomFunc)
+ }
+
+ getDataFunctionTypes(functionType) {
+ if (functionType === "interpolated") {
+ return ["categorical", "interval", "exponential"]
+ }
+ else {
+ return ["categorical", "interval"]
+ }
+ }
+
+ makeDataFunction() {
+ const dataFunc = {
+ property: "",
+ type: "categorical",
+ stops: [
+ [{zoom: 6, value: 0}, this.props.value],
+ [{zoom: 10, value: 0}, this.props.value]
+ ]
+ }
+ this.props.onChange(this.props.fieldName, dataFunc)
+ }
+
+ changeStop(changeIdx, stopData, value) {
+ const stops = this.props.value.stops.slice(0)
+ stops[changeIdx] = [stopData, value]
+ const changedValue = {
+ ...this.props.value,
+ stops: stops,
+ }
+ this.props.onChange(this.props.fieldName, changedValue)
+ }
+
+ changeDataProperty(propName, propVal) {
+ if (propVal) {
+ this.props.value[propName] = propVal
+ }
+ else {
+ delete this.props.value[propName]
+ }
+ this.props.onChange(this.props.fieldName, this.props.value)
+ }
+
+ renderDataProperty() {
+ const dataFields = this.props.value.stops.map((stop, idx) => {
+ const zoomLevel = stop[0].zoom
+ const dataLevel = stop[0].value
+ const value = stop[1]
+ const deleteStopBtn =
+
+ const dataProps = {
+ label: "Data value",
+ value: dataLevel,
+ onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
+ }
+ const dataInput = this.props.value.type === "categorical" ? :
+
+ return
+
+ this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
+ min={0}
+ max={22}
+ />
+
+
+ {dataInput}
+
+
+ this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
+ />
+
+
+ })
+
+ return
+
+
+
+
+
+ this.changeDataProperty("property", propVal)}
+ />
+
+
+
+
+
+ this.changeDataProperty("type", propVal)}
+ options={this.getDataFunctionTypes(this.props.fieldSpec.function)}
+ />
+
+
+
+
+
+ this.changeDataProperty("default", propVal)}
+ />
+
+
+
+
+ {dataFields}
+
+
+ }
+
+ renderZoomProperty() {
+ const zoomFields = this.props.value.stops.map((stop, idx) => {
+ const zoomLevel = stop[0]
+ const value = stop[1]
+ const deleteStopBtn=
+
+ return
+
+
+ this.changeStop(idx, changedStop, value)}
+ min={0}
+ max={22}
+ />
+
+
+ this.changeStop(idx, zoomLevel, newValue)}
+ />
+
+
+
+ })
+
+ return
+ {zoomFields}
+
+
+ }
+
+ renderProperty() {
+ const functionBtn =
+ return
+
+
+ }
+
+ render() {
+ const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
+ let specField
+ if (isZoomField(this.props.value)) {
+ specField = this.renderZoomProperty()
+ }
+ else if (isDataField(this.props.value)) {
+ specField = this.renderDataProperty()
+ }
+ else {
+ specField = this.renderProperty()
+ }
+ return
+ {specField}
+
+ }
+}
+
+function MakeFunctionButtons(props) {
+ let makeZoomButton, makeDataButton
+ if (props.fieldSpec['zoom-function']) {
+ makeZoomButton =
+
+ if (props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(props.fieldSpec['function']) !== -1) {
+ makeDataButton =
+ }
+ return {makeDataButton}{makeZoomButton}
+ }
+ else {
+ return null
+ }
+}
+
+function DeleteStopButton(props) {
+ return
+}
+
+function labelFromFieldName(fieldName) {
+ let label = fieldName.split('-').slice(1).join(' ')
+ return capitalize(label)
+}
diff --git a/src/components/fields/PropertyGroup.jsx b/src/components/fields/PropertyGroup.jsx
index 8de8b342..fc178929 100644
--- a/src/components/fields/PropertyGroup.jsx
+++ b/src/components/fields/PropertyGroup.jsx
@@ -1,6 +1,6 @@
import React from 'react'
-import ZoomSpecField from './ZoomSpecField'
+import FunctionSpecField from './FunctionSpecField'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
/** Extract field spec by {@fieldName} from the {@layerType} in the
@@ -54,7 +54,7 @@ export default class PropertyGroup extends React.Component {
const layout = this.props.layer.layout || {}
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
- return {
- const zoomLevel = stop[0]
- const value = stop[1]
- const deleteStopBtn=
-
- return
-
-
- this.changeStop(idx, changedStop, value)}
- min={0}
- max={22}
- />
-
-
- this.changeStop(idx, zoomLevel, newValue)}
- />
-
-
-
- })
-
- return
- {zoomFields}
-
-
- }
-
- renderProperty() {
- let zoomBtn = null
- if(this.props.fieldSpec['zoom-function']) {
- zoomBtn =
- }
- return
-
-
- }
-
- render() {
- const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
- return
- {isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()}
-
- }
-}
-
-function MakeZoomFunctionButton(props) {
- return
-}
-
-function DeleteStopButton(props) {
- return
-}
-
-function labelFromFieldName(fieldName) {
- let label = fieldName.split('-').slice(1).join(' ')
- return capitalize(label)
-}
diff --git a/src/components/filter/SingleFilterEditor.jsx b/src/components/filter/SingleFilterEditor.jsx
index 8902625b..f7af98d4 100644
--- a/src/components/filter/SingleFilterEditor.jsx
+++ b/src/components/filter/SingleFilterEditor.jsx
@@ -11,6 +11,29 @@ function tryParseInt(v) {
return parseFloat(v)
}
+function tryParseBool(v) {
+ const isString = (typeof(v) === "string");
+ if(!isString) {
+ return v;
+ }
+
+ if(v.match(/^\s*true\s*$/)) {
+ return true;
+ }
+ else if(v.match(/^\s*false\s*$/)) {
+ return false;
+ }
+ else {
+ return v;
+ }
+}
+
+function parseFilter(v) {
+ v = tryParseInt(v);
+ v = tryParseBool(v);
+ return v;
+}
+
class SingleFilterEditor extends React.Component {
static propTypes = {
filter: React.PropTypes.array.isRequired,
@@ -23,7 +46,7 @@ class SingleFilterEditor extends React.Component {
}
onFilterPartChanged(filterOp, propertyName, filterArgs) {
- let newFilter = [filterOp, propertyName, ...filterArgs.map(tryParseInt)]
+ let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
if(filterOp === 'has' || filterOp === '!has') {
newFilter = [filterOp, propertyName]
} else if(filterArgs.length === 0) {
diff --git a/src/components/layers/CommentBlock.jsx b/src/components/layers/CommentBlock.jsx
index 996ca1ca..ac8bee61 100644
--- a/src/components/layers/CommentBlock.jsx
+++ b/src/components/layers/CommentBlock.jsx
@@ -10,7 +10,7 @@ class MetadataBlock extends React.Component {
}
render() {
- return
+ return
- {this.props.children}
+
+
{this.props.children}
+
}
diff --git a/src/styles/_components.scss b/src/styles/_components.scss
index b4032a5d..12415faa 100644
--- a/src/styles/_components.scss
+++ b/src/styles/_components.scss
@@ -40,6 +40,7 @@
.maputnik-doc-target:hover .maputnik-doc-popup {
display: block;
+ text-align: left;
}
// BUTTON
@@ -104,13 +105,17 @@
.maputnik-action-block {
.maputnik-input-block-label {
display: inline-block;
- width: 43%;
+ width: 35%;
}
.maputnik-input-block-action {
vertical-align: top;
display: inline-block;
- width: 7%;
+ width: 15%;
+ }
+
+ .maputnik-input-block-action > div {
+ text-align: right;
}
}
diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss
index 69ee2e49..56740608 100644
--- a/src/styles/_modal.scss
+++ b/src/styles/_modal.scss
@@ -2,6 +2,7 @@
.maputnik-modal {
min-width: 350px;
max-width: 600px;
+ overflow: hidden;
background-color: $color-black;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
z-index: 3;
@@ -39,9 +40,13 @@
cursor: pointer;
}
+.maputnik-modal-scroller {
+ max-height: calc(100vh - 35px);
+ overflow-y: auto;
+}
+
.maputnik-modal-content {
padding: $margin-3;
- max-height: 90vh;
@include flex-column;
}
@@ -80,7 +85,6 @@
}
.maputnik-style-gallery-container {
- overflow-y: scroll;
flex-shrink: 1;
}
diff --git a/src/styles/_toolbar.scss b/src/styles/_toolbar.scss
index e9459645..77dbea87 100644
--- a/src/styles/_toolbar.scss
+++ b/src/styles/_toolbar.scss
@@ -22,9 +22,8 @@
img {
width: 30px;
- height: 30px;
padding-right: $margin-2;
- vertical-align: middle;
+ vertical-align: top;
}
}
diff --git a/src/styles/_zoomproperty.scss b/src/styles/_zoomproperty.scss
index 22984961..d6510c9b 100644
--- a/src/styles/_zoomproperty.scss
+++ b/src/styles/_zoomproperty.scss
@@ -67,3 +67,68 @@
.maputnik-zoom-spec-property .maputnik-input-block:not(:first-child) .maputnik-input-block-label {
visibility: hidden;
}
+
+// DATA FUNC
+.maputnik-make-data-function {
+ background-color: transparent;
+ display: inline-block;
+ padding-bottom: 0;
+ padding-top: 0;
+ vertical-align: middle;
+
+ @extend .maputnik-icon-button;
+}
+
+// DATA PROPERTY
+.maputnik-data-spec-block {
+ overflow: auto;
+}
+
+.maputnik-data-spec-property {
+ .maputnik-input-block-label {
+ width: 30%;
+ }
+
+ .maputnik-input-block-content {
+ width: 70%;
+ }
+
+ .maputnik-data-spec-property-group {
+ margin-bottom: 3%;
+
+ .maputnik-doc-wrapper {
+ width: 25%;
+ color: $color-lowgray;
+ }
+
+ .maputnik-doc-wrapper:hover {
+ color: inherit;
+ }
+
+ .maputnik-data-spec-property-input {
+ width: 75%;
+ display: inline-block;
+
+ .maputnik-string {
+ margin-bottom: 3%;
+ }
+ }
+ }
+}
+
+.maputnik-data-spec-block {
+ .maputnik-data-spec-property-stop-edit,
+ .maputnik-data-spec-property-stop-data {
+ display: inline-block;
+ margin-bottom: 3%;
+ }
+
+ .maputnik-data-spec-property-stop-edit {
+ width: 18%;
+ margin-right: 3%;
+ }
+
+ .maputnik-data-spec-property-stop-data {
+ width: 78%;
+ }
+}
diff --git a/test/specs/simple.js b/test/specs/simple.js
index 56b27695..fb6ca499 100644
--- a/test/specs/simple.js
+++ b/test/specs/simple.js
@@ -9,7 +9,7 @@ describe('maputnik', function() {
browser.waitForExist(".maputnik-toolbar-link");
var src = browser.getAttribute(".maputnik-toolbar-link img", "src");
- assert.equal(src, config.baseUrl+'/img/maputnik.png');
+ assert.equal(src, config.baseUrl+'/img/logo-color.svg');
});
});