mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-30 23:10:33 +01:00
Merge remote-tracking branch 'upstream/master' into feature/load-from-url
This commit is contained in:
commit
aa92e9da02
13 changed files with 467 additions and 197 deletions
|
@ -20,8 +20,8 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/maputnik/editor#readme",
|
"homepage": "https://github.com/maputnik/editor#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-style-spec": "^9.0.0",
|
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.1.0",
|
"@mapbox/mapbox-gl-rtl-text": "^0.1.0",
|
||||||
|
"@mapbox/mapbox-gl-style-spec": "^9.0.1",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"codemirror": "^5.18.2",
|
"codemirror": "^5.18.2",
|
||||||
"color": "^1.0.3",
|
"color": "^1.0.3",
|
||||||
|
@ -32,8 +32,9 @@
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.isequal": "^4.4.0",
|
"lodash.isequal": "^4.4.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl": "^0.34.0",
|
"mapbox-gl": "^0.40.1",
|
||||||
"mapbox-gl-inspect": "^1.2.3",
|
"mapbox-gl-inspect": "^1.2.3",
|
||||||
|
"maputnik-design": "github:maputnik/design",
|
||||||
"mousetrap": "^1.6.0",
|
"mousetrap": "^1.6.0",
|
||||||
"ol-mapbox-style": "1.0.1",
|
"ol-mapbox-style": "1.0.1",
|
||||||
"openlayers": "^3.19.1",
|
"openlayers": "^3.19.1",
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
"react-addons-pure-render-mixin": "^15.4.0",
|
"react-addons-pure-render-mixin": "^15.4.0",
|
||||||
"react-autocomplete": "^1.4.0",
|
"react-autocomplete": "^1.4.0",
|
||||||
"react-codemirror": "^0.3.0",
|
"react-codemirror": "^0.3.0",
|
||||||
"react-collapse": "^4.0.2",
|
"react-collapse": "^4.0.3",
|
||||||
"react-color": "^2.10.0",
|
"react-color": "^2.10.0",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
"react-file-reader-input": "^1.1.0",
|
"react-file-reader-input": "^1.1.0",
|
||||||
|
|
|
@ -16,7 +16,7 @@ import MdFontDownload from 'react-icons/lib/md/font-download'
|
||||||
import HelpIcon from 'react-icons/lib/md/help-outline'
|
import HelpIcon from 'react-icons/lib/md/help-outline'
|
||||||
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
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 SettingsModal from './modals/SettingsModal'
|
||||||
import ExportModal from './modals/ExportModal'
|
import ExportModal from './modals/ExportModal'
|
||||||
import SourcesModal from './modals/SourcesModal'
|
import SourcesModal from './modals/SourcesModal'
|
||||||
|
|
352
src/components/fields/FunctionSpecField.jsx
Normal file
352
src/components/fields/FunctionSpecField.jsx
Normal file
|
@ -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 = <DeleteStopButton onClick={this.deleteStop.bind(this, idx)} />
|
||||||
|
|
||||||
|
const dataProps = {
|
||||||
|
label: "Data value",
|
||||||
|
value: dataLevel,
|
||||||
|
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
|
||||||
|
}
|
||||||
|
const dataInput = this.props.value.type === "categorical" ? <StringInput {...dataProps} /> : <NumberInput {...dataProps} />
|
||||||
|
|
||||||
|
return <InputBlock key={idx} action={deleteStopBtn}>
|
||||||
|
<div className="maputnik-data-spec-property-stop-edit">
|
||||||
|
<NumberInput
|
||||||
|
value={zoomLevel}
|
||||||
|
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
||||||
|
min={0}
|
||||||
|
max={22}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-data-spec-property-stop-data">
|
||||||
|
{dataInput}
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-data-spec-property-stop-value">
|
||||||
|
<SpecField
|
||||||
|
fieldName={this.props.fieldName}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
value={value}
|
||||||
|
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</InputBlock>
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className="maputnik-data-spec-block">
|
||||||
|
<div className="maputnik-data-spec-property">
|
||||||
|
<InputBlock
|
||||||
|
doc={this.props.fieldSpec.doc}
|
||||||
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
|
>
|
||||||
|
<div className="maputnik-data-spec-property-group">
|
||||||
|
<DocLabel
|
||||||
|
label="Property"
|
||||||
|
doc={"Input a data property to base styles off of."}
|
||||||
|
/>
|
||||||
|
<div className="maputnik-data-spec-property-input">
|
||||||
|
<StringInput
|
||||||
|
value={this.props.value.property}
|
||||||
|
onChange={propVal => this.changeDataProperty("property", propVal)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-data-spec-property-group">
|
||||||
|
<DocLabel
|
||||||
|
label="Type"
|
||||||
|
doc={"Select a type of data scale (default is 'categorical')."}
|
||||||
|
/>
|
||||||
|
<div className="maputnik-data-spec-property-input">
|
||||||
|
<SelectInput
|
||||||
|
value={this.props.value.type}
|
||||||
|
onChange={propVal => this.changeDataProperty("type", propVal)}
|
||||||
|
options={this.getDataFunctionTypes(this.props.fieldSpec.function)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-data-spec-property-group">
|
||||||
|
<DocLabel
|
||||||
|
label="Default"
|
||||||
|
doc={"Input a default value for data if not covered by the scales."}
|
||||||
|
/>
|
||||||
|
<div className="maputnik-data-spec-property-input">
|
||||||
|
<SpecField
|
||||||
|
fieldName={this.props.fieldName}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
value={this.props.value.default}
|
||||||
|
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InputBlock>
|
||||||
|
</div>
|
||||||
|
{dataFields}
|
||||||
|
<Button
|
||||||
|
className="maputnik-add-stop"
|
||||||
|
onClick={this.addStop.bind(this)}
|
||||||
|
>
|
||||||
|
Add stop
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
renderZoomProperty() {
|
||||||
|
const zoomFields = this.props.value.stops.map((stop, idx) => {
|
||||||
|
const zoomLevel = stop[0]
|
||||||
|
const value = stop[1]
|
||||||
|
const deleteStopBtn= <DeleteStopButton onClick={this.deleteStop.bind(this, idx)} />
|
||||||
|
|
||||||
|
return <InputBlock
|
||||||
|
key={zoomLevel}
|
||||||
|
doc={this.props.fieldSpec.doc}
|
||||||
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
|
action={deleteStopBtn}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="maputnik-zoom-spec-property-stop-edit">
|
||||||
|
<NumberInput
|
||||||
|
value={zoomLevel}
|
||||||
|
onChange={changedStop => this.changeStop(idx, changedStop, value)}
|
||||||
|
min={0}
|
||||||
|
max={22}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-zoom-spec-property-stop-value">
|
||||||
|
<SpecField
|
||||||
|
fieldName={this.props.fieldName}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
value={value}
|
||||||
|
onChange={(_, newValue) => this.changeStop(idx, zoomLevel, newValue)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InputBlock>
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className="maputnik-zoom-spec-property">
|
||||||
|
{zoomFields}
|
||||||
|
<Button
|
||||||
|
className="maputnik-add-stop"
|
||||||
|
onClick={this.addStop.bind(this)}
|
||||||
|
>
|
||||||
|
Add stop
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProperty() {
|
||||||
|
const functionBtn = <MakeFunctionButtons
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
onZoomClick={this.makeZoomFunction.bind(this)}
|
||||||
|
onDataClick={this.makeDataFunction.bind(this)}
|
||||||
|
/>
|
||||||
|
return <InputBlock
|
||||||
|
doc={this.props.fieldSpec.doc}
|
||||||
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
|
action={functionBtn}
|
||||||
|
>
|
||||||
|
<SpecField {...this.props} />
|
||||||
|
</InputBlock>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <div className={propClass}>
|
||||||
|
{specField}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MakeFunctionButtons(props) {
|
||||||
|
let makeZoomButton, makeDataButton
|
||||||
|
if (props.fieldSpec['zoom-function']) {
|
||||||
|
makeZoomButton = <Button
|
||||||
|
className="maputnik-make-zoom-function"
|
||||||
|
onClick={props.onZoomClick}
|
||||||
|
>
|
||||||
|
<DocLabel
|
||||||
|
label={<FunctionIcon />}
|
||||||
|
cursorTargetStyle={{ cursor: 'pointer' }}
|
||||||
|
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
if (props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(props.fieldSpec['function']) !== -1) {
|
||||||
|
makeDataButton = <Button
|
||||||
|
className="maputnik-make-data-function"
|
||||||
|
onClick={props.onDataClick}
|
||||||
|
>
|
||||||
|
<DocLabel
|
||||||
|
label={<MdInsertChart />}
|
||||||
|
cursorTargetStyle={{ cursor: 'pointer' }}
|
||||||
|
doc={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
return <div>{makeDataButton}{makeZoomButton}</div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteStopButton(props) {
|
||||||
|
return <Button
|
||||||
|
className="maputnik-delete-stop"
|
||||||
|
onClick={props.onClick}
|
||||||
|
>
|
||||||
|
<DocLabel
|
||||||
|
label={<DeleteIcon />}
|
||||||
|
doc={"Remove zoom level stop."}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
function labelFromFieldName(fieldName) {
|
||||||
|
let label = fieldName.split('-').slice(1).join(' ')
|
||||||
|
return capitalize(label)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
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']
|
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||||
|
|
||||||
/** Extract field spec by {@fieldName} from the {@layerType} in the
|
/** 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 layout = this.props.layer.layout || {}
|
||||||
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
|
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
|
||||||
|
|
||||||
return <ZoomSpecField
|
return <FunctionSpecField
|
||||||
onChange={this.onPropertyChange.bind(this)}
|
onChange={this.onPropertyChange.bind(this)}
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
fieldName={fieldName}
|
fieldName={fieldName}
|
||||||
|
|
|
@ -1,181 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import Color from 'color'
|
|
||||||
|
|
||||||
import Button from '../Button'
|
|
||||||
import SpecField from './SpecField'
|
|
||||||
import NumberInput from '../inputs/NumberInput'
|
|
||||||
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 capitalize from 'lodash.capitalize'
|
|
||||||
|
|
||||||
function isZoomField(value) {
|
|
||||||
return typeof value === 'object' && value.stops
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Supports displaying spec field for zoom function objects
|
|
||||||
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
|
||||||
*/
|
|
||||||
export default class ZoomSpecProperty 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]
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
changeStop(changeIdx, zoomLevel, value) {
|
|
||||||
const stops = this.props.value.stops.slice(0)
|
|
||||||
stops[changeIdx] = [zoomLevel, value]
|
|
||||||
const changedValue = {
|
|
||||||
...this.props.value,
|
|
||||||
stops: stops,
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderZoomProperty() {
|
|
||||||
const zoomFields = this.props.value.stops.map((stop, idx) => {
|
|
||||||
const zoomLevel = stop[0]
|
|
||||||
const value = stop[1]
|
|
||||||
const deleteStopBtn= <DeleteStopButton onClick={this.deleteStop.bind(this, idx)} />
|
|
||||||
|
|
||||||
return <InputBlock
|
|
||||||
key={zoomLevel}
|
|
||||||
doc={this.props.fieldSpec.doc}
|
|
||||||
label={labelFromFieldName(this.props.fieldName)}
|
|
||||||
action={deleteStopBtn}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div className="maputnik-zoom-spec-property-stop-edit">
|
|
||||||
<NumberInput
|
|
||||||
value={zoomLevel}
|
|
||||||
onChange={changedStop => this.changeStop(idx, changedStop, value)}
|
|
||||||
min={0}
|
|
||||||
max={22}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="maputnik-zoom-spec-property-stop-value">
|
|
||||||
<SpecField
|
|
||||||
fieldName={this.props.fieldName}
|
|
||||||
fieldSpec={this.props.fieldSpec}
|
|
||||||
value={value}
|
|
||||||
onChange={(_, newValue) => this.changeStop(idx, zoomLevel, newValue)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</InputBlock>
|
|
||||||
})
|
|
||||||
|
|
||||||
return <div className="maputnik-zoom-spec-property">
|
|
||||||
{zoomFields}
|
|
||||||
<Button
|
|
||||||
className="maputnik-add-stop"
|
|
||||||
onClick={this.addStop.bind(this)}
|
|
||||||
>
|
|
||||||
Add stop
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProperty() {
|
|
||||||
let zoomBtn = null
|
|
||||||
if(this.props.fieldSpec['zoom-function']) {
|
|
||||||
zoomBtn = <MakeZoomFunctionButton onClick={this.makeZoomFunction.bind(this)} />
|
|
||||||
}
|
|
||||||
return <InputBlock
|
|
||||||
doc={this.props.fieldSpec.doc}
|
|
||||||
label={labelFromFieldName(this.props.fieldName)}
|
|
||||||
action={zoomBtn}
|
|
||||||
>
|
|
||||||
<SpecField {...this.props} />
|
|
||||||
</InputBlock>
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
|
|
||||||
return <div className={propClass}>
|
|
||||||
{isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function MakeZoomFunctionButton(props) {
|
|
||||||
return <Button
|
|
||||||
className="maputnik-make-zoom-function"
|
|
||||||
onClick={props.onClick}
|
|
||||||
>
|
|
||||||
<DocLabel
|
|
||||||
label={<FunctionIcon />}
|
|
||||||
cursorTargetStyle={{ cursor: 'pointer' }}
|
|
||||||
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
|
|
||||||
function DeleteStopButton(props) {
|
|
||||||
return <Button
|
|
||||||
className="maputnik-delete-stop"
|
|
||||||
onClick={props.onClick}
|
|
||||||
>
|
|
||||||
<DocLabel
|
|
||||||
label={<DeleteIcon />}
|
|
||||||
doc={"Remove zoom level stop."}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
|
|
||||||
function labelFromFieldName(fieldName) {
|
|
||||||
let label = fieldName.split('-').slice(1).join(' ')
|
|
||||||
return capitalize(label)
|
|
||||||
}
|
|
|
@ -11,6 +11,29 @@ function tryParseInt(v) {
|
||||||
return parseFloat(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 {
|
class SingleFilterEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
filter: React.PropTypes.array.isRequired,
|
filter: React.PropTypes.array.isRequired,
|
||||||
|
@ -23,7 +46,7 @@ class SingleFilterEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterPartChanged(filterOp, propertyName, filterArgs) {
|
onFilterPartChanged(filterOp, propertyName, filterArgs) {
|
||||||
let newFilter = [filterOp, propertyName, ...filterArgs.map(tryParseInt)]
|
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
|
||||||
if(filterOp === 'has' || filterOp === '!has') {
|
if(filterOp === 'has' || filterOp === '!has') {
|
||||||
newFilter = [filterOp, propertyName]
|
newFilter = [filterOp, propertyName]
|
||||||
} else if(filterArgs.length === 0) {
|
} else if(filterArgs.length === 0) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ class MetadataBlock extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Comments"}>
|
return <InputBlock label={"Comments"} doc={"Comments for the current layer. This is non-standard and not in the spec."}>
|
||||||
<StringInput
|
<StringInput
|
||||||
multi={true}
|
multi={true}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
|
|
@ -21,8 +21,10 @@ class Modal extends React.Component {
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
|
<div className="maputnik-modal-scroller">
|
||||||
<div className="maputnik-modal-content">{this.props.children}</div>
|
<div className="maputnik-modal-content">{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
|
|
||||||
.maputnik-doc-target:hover .maputnik-doc-popup {
|
.maputnik-doc-target:hover .maputnik-doc-popup {
|
||||||
display: block;
|
display: block;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BUTTON
|
// BUTTON
|
||||||
|
@ -104,13 +105,17 @@
|
||||||
.maputnik-action-block {
|
.maputnik-action-block {
|
||||||
.maputnik-input-block-label {
|
.maputnik-input-block-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 43%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-input-block-action {
|
.maputnik-input-block-action {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 7%;
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-input-block-action > div {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
.maputnik-modal {
|
.maputnik-modal {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
overflow: hidden;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
@ -39,9 +40,13 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-scroller {
|
||||||
|
max-height: calc(100vh - 35px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-modal-content {
|
.maputnik-modal-content {
|
||||||
padding: $margin-3;
|
padding: $margin-3;
|
||||||
max-height: 90vh;
|
|
||||||
|
|
||||||
@include flex-column;
|
@include flex-column;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +85,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-style-gallery-container {
|
.maputnik-style-gallery-container {
|
||||||
overflow-y: scroll;
|
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,8 @@
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
|
||||||
padding-right: $margin-2;
|
padding-right: $margin-2;
|
||||||
vertical-align: middle;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,3 +67,68 @@
|
||||||
.maputnik-zoom-spec-property .maputnik-input-block:not(:first-child) .maputnik-input-block-label {
|
.maputnik-zoom-spec-property .maputnik-input-block:not(:first-child) .maputnik-input-block-label {
|
||||||
visibility: hidden;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('maputnik', function() {
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.waitForExist(".maputnik-toolbar-link");
|
||||||
|
|
||||||
var src = browser.getAttribute(".maputnik-toolbar-link img", "src");
|
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue