Merge remote-tracking branch 'upstream/master' into feature/ui-errors-and-expressions

This commit is contained in:
orangemug 2020-02-01 17:36:59 +00:00
commit f09cc25a3b
32 changed files with 381 additions and 86 deletions

View file

@ -20,7 +20,8 @@ class Button extends React.Component {
aria-label={this.props["aria-label"]} aria-label={this.props["aria-label"]}
className={classnames("maputnik-button", this.props.className)} className={classnames("maputnik-button", this.props.className)}
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
style={this.props.style}> style={this.props.style}
>
{this.props.children} {this.props.children}
</button> </button>
} }

View file

@ -13,7 +13,7 @@ function formatColor(color) {
class ColorField extends React.Component { class ColorField extends React.Component {
static propTypes = { static propTypes = {
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
doc: PropTypes.string, doc: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,

View file

@ -1,23 +1,56 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
export default class DocLabel extends React.Component { export default class DocLabel extends React.Component {
static propTypes = { static propTypes = {
label: PropTypes.oneOfType([ label: PropTypes.oneOfType([
PropTypes.object, PropTypes.object,
PropTypes.string PropTypes.string
]).isRequired, ]).isRequired,
doc: PropTypes.string.isRequired, fieldSpec: PropTypes.object.isRequired,
onToggleDoc: PropTypes.func.isRequired,
}
constructor (props) {
super(props);
this.state = {
open: false,
}
}
onToggleDoc = (open) => {
this.setState({
open,
}, () => {
if (this.props.onToggleDoc) {
this.props.onToggleDoc(this.state.open);
}
});
} }
render() { render() {
return <label className="maputnik-doc-wrapper"> const {label, fieldSpec} = this.props;
<div className="maputnik-doc-target"> const {doc} = fieldSpec || {};
<span>{this.props.label}</span>
<div className="maputnik-doc-popup"> if (doc) {
{this.props.doc} return <label className="maputnik-doc-wrapper">
<div className="maputnik-doc-target">
{label}
{'\xa0'}
<button
aria-label={this.state.open ? "close property documentation" : "open property documentation"}
className={`maputnik-doc-button maputnik-doc-button--${this.state.open ? 'open' : 'closed'}`}
onClick={() => this.onToggleDoc(!this.state.open)}
>
{this.state.open ? <MdHighlightOff /> : <MdInfoOutline />}
</button>
</div> </div>
</div> </label>
</label> }
else {
return <div />
}
} }
} }

View file

@ -201,17 +201,18 @@ export default class DataProperty extends React.Component {
<div className="maputnik-data-spec-property"> <div className="maputnik-data-spec-property">
<InputBlock <InputBlock
error={this.props.error} error={this.props.error}
doc={this.props.fieldSpec.doc} fieldSpec={this.props.fieldSpec}
fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
> >
<div className="maputnik-data-spec-property-group"> <div className="maputnik-data-spec-property-group">
<DocLabel <DocLabel
label="Property" label="Property"
doc={"Input a data property to base styles off of."}
/> />
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<StringInput <StringInput
value={this.props.value.property} value={this.props.value.property}
title={"Input a data property to base styles off of."}
onChange={propVal => this.changeDataProperty("property", propVal)} onChange={propVal => this.changeDataProperty("property", propVal)}
/> />
</div> </div>
@ -219,12 +220,12 @@ export default class DataProperty extends React.Component {
<div className="maputnik-data-spec-property-group"> <div className="maputnik-data-spec-property-group">
<DocLabel <DocLabel
label="Type" label="Type"
doc={"Select a type of data scale (default is 'categorical')."}
/> />
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<SelectInput <SelectInput
value={this.props.value.type} value={this.props.value.type}
onChange={propVal => this.changeDataProperty("type", propVal)} onChange={propVal => this.changeDataProperty("type", propVal)}
title={"Select a type of data scale (default is 'categorical')."}
options={this.getDataFunctionTypes(this.props.fieldSpec)} options={this.getDataFunctionTypes(this.props.fieldSpec)}
/> />
</div> </div>
@ -232,7 +233,6 @@ export default class DataProperty extends React.Component {
<div className="maputnik-data-spec-property-group"> <div className="maputnik-data-spec-property-group">
<DocLabel <DocLabel
label="Default" label="Default"
doc={"Input a default value for data if not covered by the scales."}
/> />
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<SpecField <SpecField
@ -243,15 +243,15 @@ export default class DataProperty extends React.Component {
/> />
</div> </div>
</div> </div>
{dataFields}
<Button
className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)}
>
Add stop
</Button>
</InputBlock> </InputBlock>
</div> </div>
{dataFields}
<Button
className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)}
>
Add stop
</Button>
</div> </div>
} }
} }

View file

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
import Button from '../Button' import Button from '../Button'
import {MdDelete} from 'react-icons/md' import {MdDelete} from 'react-icons/md'
@ -15,11 +14,9 @@ export default class DeleteStopButton extends React.Component {
return <Button return <Button
className="maputnik-delete-stop" className="maputnik-delete-stop"
onClick={this.props.onClick} onClick={this.props.onClick}
title={"Remove zoom level stop."}
> >
<DocLabel <MdDelete />
label={<MdDelete />}
doc={"Remove zoom level stop."}
/>
</Button> </Button>
} }
} }

View file

@ -66,7 +66,7 @@ export default class ExpressionProperty extends React.Component {
return <InputBlock return <InputBlock
error={this.props.error} error={this.props.error}
doc={this.props.fieldSpec.doc} fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn} action={deleteStopBtn}
wideMode={true} wideMode={true}

View file

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
import Button from '../Button' import Button from '../Button'
import {MdFunctions, MdInsertChart} from 'react-icons/md' import {MdFunctions, MdInsertChart} from 'react-icons/md'
@ -50,24 +49,18 @@ export default class FunctionButtons extends React.Component {
makeZoomButton = <Button makeZoomButton = <Button
className="maputnik-make-zoom-function" className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick} onClick={this.props.onZoomClick}
title={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
> >
<DocLabel <MdFunctions />
label={<MdFunctions />}
cursorTargetStyle={{ cursor: 'pointer' }}
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
/>
</Button> </Button>
if (this.props.fieldSpec['property-type'] === 'data-driven') { if (this.props.fieldSpec['property-type'] === 'data-driven') {
makeDataButton = <Button makeDataButton = <Button
className="maputnik-make-data-function" className="maputnik-make-data-function"
onClick={this.props.onDataClick} onClick={this.props.onDataClick}
title={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."}
> >
<DocLabel <MdInsertChart />
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> </Button>
} }
return <div> return <div>

View file

@ -27,7 +27,8 @@ export default class SpecProperty extends React.Component {
return <InputBlock return <InputBlock
error={this.props.error} error={this.props.error}
doc={this.props.fieldSpec.doc} fieldSpec={this.props.fieldSpec}
fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
action={functionBtn} action={functionBtn}
> >

View file

@ -126,7 +126,7 @@ export default class ZoomProperty extends React.Component {
return <InputBlock return <InputBlock
error={this.props.error} error={this.props.error}
key={key} key={key}
doc={this.props.fieldSpec.doc} fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn} action={deleteStopBtn}
> >

View file

@ -8,6 +8,7 @@ import SelectInput from '../inputs/SelectInput'
import SingleFilterEditor from './SingleFilterEditor' import SingleFilterEditor from './SingleFilterEditor'
import FilterEditorBlock from './FilterEditorBlock' import FilterEditorBlock from './FilterEditorBlock'
import Button from '../Button' import Button from '../Button'
import SpecDoc from '../inputs/SpecDoc'
function hasCombiningFilter(filter) { function hasCombiningFilter(filter) {
return combiningFilterOps.indexOf(filter[0]) >= 0 return combiningFilterOps.indexOf(filter[0]) >= 0
@ -29,6 +30,13 @@ export default class CombiningFilterEditor extends React.Component {
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
constructor () {
super();
this.state = {
showDoc: false,
};
}
// Convert filter to combining filter // Convert filter to combining filter
combiningFilter() { combiningFilter() {
let filter = this.props.filter || ['all'] let filter = this.props.filter || ['all']
@ -63,12 +71,22 @@ export default class CombiningFilterEditor extends React.Component {
this.props.onChange(newFilterItem) this.props.onChange(newFilterItem)
} }
onToggleDoc = (val) => {
this.setState({
showDoc: val
});
}
render() { render() {
const filter = this.combiningFilter() const filter = this.combiningFilter()
let combiningOp = filter[0] let combiningOp = filter[0]
let filters = filter.slice(1) let filters = filter.slice(1)
const {errors} = this.props; const {errors} = this.props;
const fieldSpec={
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
};
const editorBlocks = filters.map((f, idx) => { const editorBlocks = filters.map((f, idx) => {
const error = errors[`filter[${idx+1}]`]; const error = errors[`filter[${idx+1}]`];
@ -99,7 +117,8 @@ export default class CombiningFilterEditor extends React.Component {
<div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter"> <div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
<DocLabel <DocLabel
label={"Compound Filter"} label={"Compound Filter"}
doc={latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."} onToggleDoc={this.onToggleDoc}
fieldSpec={fieldSpec}
/> />
<SelectInput <SelectInput
value={combiningOp} value={combiningOp}
@ -116,6 +135,12 @@ export default class CombiningFilterEditor extends React.Component {
Add filter Add filter
</Button> </Button>
</div> </div>
<div
className="maputnik-doc-inline"
style={{display: this.state.showDoc ? '' : 'none'}}
>
<SpecDoc fieldSpec={fieldSpec} />
</div>
</div> </div>
} }
} }

View file

@ -2,6 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import DocLabel from '../fields/DocLabel' import DocLabel from '../fields/DocLabel'
import SpecDoc from './SpecDoc'
/** Wrap a component with a label */ /** Wrap a component with a label */
class InputBlock extends React.Component { class InputBlock extends React.Component {
@ -11,11 +13,18 @@ class InputBlock extends React.Component {
PropTypes.string, PropTypes.string,
PropTypes.element, PropTypes.element,
]).isRequired, ]).isRequired,
doc: PropTypes.string,
action: PropTypes.element, action: PropTypes.element,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
style: PropTypes.object, style: PropTypes.object,
onChange: PropTypes.func, onChange: PropTypes.func,
fieldSpec: PropTypes.object,
}
constructor (props) {
super(props);
this.state = {
showDoc: false,
}
} }
onChange(e) { onChange(e) {
@ -23,6 +32,12 @@ class InputBlock extends React.Component {
return this.props.onChange(value === "" ? undefined : value) return this.props.onChange(value === "" ? undefined : value)
} }
onToggleDoc = (val) => {
this.setState({
showDoc: val
});
}
render() { render() {
return <div style={this.props.style} return <div style={this.props.style}
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
@ -32,15 +47,16 @@ class InputBlock extends React.Component {
"maputnik-action-block": this.props.action "maputnik-action-block": this.props.action
})} })}
> >
{this.props.doc && {this.props.fieldSpec &&
<div className="maputnik-input-block-label"> <div className="maputnik-input-block-label">
<DocLabel <DocLabel
label={this.props.label} label={this.props.label}
doc={this.props.doc} onToggleDoc={this.onToggleDoc}
fieldSpec={this.props.fieldSpec}
/> />
</div> </div>
} }
{!this.props.doc && {!this.props.fieldSpec &&
<label className="maputnik-input-block-label"> <label className="maputnik-input-block-label">
{this.props.label} {this.props.label}
</label> </label>
@ -58,6 +74,14 @@ class InputBlock extends React.Component {
{this.props.error.message} {this.props.error.message}
</div> </div>
} }
{this.props.fieldSpec &&
<div
className="maputnik-doc-inline"
style={{display: this.state.showDoc ? '' : 'none'}}
>
<SpecDoc fieldSpec={this.props.fieldSpec} />
</div>
}
</div> </div>
} }
} }

View file

@ -8,6 +8,7 @@ class SelectInput extends React.Component {
options: PropTypes.array.isRequired, options: PropTypes.array.isRequired,
style: PropTypes.object, style: PropTypes.object,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
title: PropTypes.string,
} }
@ -21,6 +22,7 @@ class SelectInput extends React.Component {
className="maputnik-select" className="maputnik-select"
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
style={this.props.style} style={this.props.style}
title={this.props.title}
value={this.props.value} value={this.props.value}
onChange={e => this.props.onChange(e.target.value)} onChange={e => this.props.onChange(e.target.value)}
> >

View file

@ -0,0 +1,83 @@
import React from 'react'
import PropTypes from 'prop-types'
export default class SpecDoc extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object.isRequired,
}
render () {
const {fieldSpec} = this.props;
const {doc, values} = fieldSpec;
const sdkSupport = fieldSpec['sdk-support'];
const headers = {
js: "JS",
android: "Android",
ios: "iOS",
macos: "macOS",
};
const renderValues = (
!!values &&
// HACK: Currently we merge additional values into the stylespec, so this is required
// See <https://github.com/maputnik/editor/blob/master/src/components/fields/PropertyGroup.jsx#L16>
!Array.isArray(values)
);
return (
<>
{doc &&
<div className="SpecDoc">
<div className="SpecDoc__doc">{doc}</div>
{renderValues &&
<ul className="SpecDoc__values">
{Object.entries(values).map(([key, value]) => {
return (
<li key={key}>
<code>{JSON.stringify(key)}</code>
<div>{value.doc}</div>
</li>
);
})}
</ul>
}
</div>
}
{sdkSupport &&
<div className="SpecDoc__sdk-support">
<table className="SpecDoc__sdk-support__table">
<thead>
<tr>
<th></th>
{Object.values(headers).map(header => {
return <th key={header}>{header}</th>;
})}
</tr>
</thead>
<tbody>
{Object.entries(sdkSupport).map(([key, supportObj]) => {
return (
<tr key={key}>
<td>{key}</td>
{Object.keys(headers).map(k => {
const value = supportObj[k];
if (supportObj.hasOwnProperty(k)) {
return <td key={k}>{supportObj[k]}</td>;
}
else {
return <td key={k}>no</td>;
}
})}
</tr>
);
})}
</tbody>
</table>
</div>
}
</>
);
}
}

View file

@ -11,9 +11,13 @@ class MetadataBlock extends React.Component {
} }
render() { render() {
const fieldSpec = {
doc: "Comments for the current layer. This is non-standard and not in the spec."
};
return <InputBlock return <InputBlock
label={"Comments"} label={"Comments"}
doc={"Comments for the current layer. This is non-standard and not in the spec."} fieldSpec={fieldSpec}
data-wd-key="layer-comment" data-wd-key="layer-comment"
> >
<StringInput <StringInput

View file

@ -13,7 +13,7 @@ class LayerIdBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"ID"} doc={latest.layer.id.doc} return <InputBlock label={"ID"} fieldSpec={latest.layer.id}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
> >
<StringInput <StringInput

View file

@ -19,7 +19,7 @@ class LayerSourceBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Source"} doc={latest.layer.source.doc} return <InputBlock label={"Source"} fieldSpec={latest.layer.source}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
> >
<AutocompleteInput <AutocompleteInput

View file

@ -20,7 +20,7 @@ class LayerSourceLayer extends React.Component {
} }
render() { render() {
return <InputBlock label={"Source Layer"} doc={latest.layer['source-layer'].doc} return <InputBlock label={"Source Layer"} fieldSpec={latest.layer['source-layer']}
data-wd-key="layer-source-layer" data-wd-key="layer-source-layer"
> >
<AutocompleteInput <AutocompleteInput

View file

@ -14,7 +14,7 @@ class LayerTypeBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Type"} doc={latest.layer.type.doc} return <InputBlock label={"Type"} fieldSpec={latest.layer.type}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
error={this.props.error} error={this.props.error}
> >

View file

@ -12,7 +12,7 @@ class MaxZoomBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Max Zoom"} doc={latest.layer.maxzoom.doc} return <InputBlock label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
error={this.props.error} error={this.props.error}
data-wd-key="max-zoom" data-wd-key="max-zoom"
> >

View file

@ -12,7 +12,7 @@ class MinZoomBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Min Zoom"} doc={latest.layer.minzoom.doc} return <InputBlock label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
error={this.props.error} error={this.props.error}
data-wd-key="min-zoom" data-wd-key="min-zoom"
> >

View file

@ -21,8 +21,8 @@ function renderProperties(feature) {
}) })
} }
function renderFeature(feature) { function renderFeature(feature, idx) {
return <div key={`${feature.sourceLayer}-${feature.id}`}> return <div key={`${feature.sourceLayer}-${idx}`}>
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div> <div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
<InputBlock key={"property-type"} label={"$type"}> <InputBlock key={"property-type"} label={"$type"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} /> <StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />

View file

@ -126,6 +126,7 @@ class AddModal extends React.Component {
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Add Layer'} title={'Add Layer'}
data-wd-key="modal:add-layer" data-wd-key="modal:add-layer"
className="maputnik-add-modal"
> >
<div className="maputnik-add-layer"> <div className="maputnik-add-layer">
<LayerIdBlock <LayerIdBlock

View file

@ -11,6 +11,7 @@ import Button from '../Button'
import Modal from './Modal' import Modal from './Modal'
import {MdFileDownload} from 'react-icons/md' import {MdFileDownload} from 'react-icons/md'
import style from '../../libs/style' import style from '../../libs/style'
import fieldSpecAdditional from '../../libs/field-spec-additional'
@ -70,6 +71,7 @@ class ExportModal extends React.Component {
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Export Style'} title={'Export Style'}
className="maputnik-export-modal"
> >
<div className="maputnik-modal-section"> <div className="maputnik-modal-section">
@ -79,19 +81,28 @@ class ExportModal extends React.Component {
</p> </p>
<p> <p>
<InputBlock label={"MapTiler Access Token: "}> <InputBlock
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
>
<StringInput <StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']} value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Mapbox Access Token: "}> <InputBlock
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
>
<StringInput <StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']} value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Thunderforest Access Token: "}> <InputBlock
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
>
<StringInput <StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']} value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}

View file

@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {MdClose} from 'react-icons/md' import {MdClose} from 'react-icons/md'
import AriaModal from 'react-aria-modal' import AriaModal from 'react-aria-modal'
import classnames from 'classnames';
class Modal extends React.Component { class Modal extends React.Component {
@ -13,6 +14,7 @@ class Modal extends React.Component {
children: PropTypes.node, children: PropTypes.node,
underlayClickExits: PropTypes.bool, underlayClickExits: PropTypes.bool,
underlayProps: PropTypes.object, underlayProps: PropTypes.object,
className: PropTypes.string,
} }
static defaultProps = { static defaultProps = {
@ -45,7 +47,7 @@ class Modal extends React.Component {
verticallyCenter={true} verticallyCenter={true}
onExit={this.onClose} onExit={this.onClose}
> >
<div className="maputnik-modal" <div className={classnames("maputnik-modal", this.props.className)}
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
> >
<header className="maputnik-modal-header"> <header className="maputnik-modal-header">

View file

@ -11,6 +11,7 @@ import SelectInput from '../inputs/SelectInput'
import EnumInput from '../inputs/EnumInput' import EnumInput from '../inputs/EnumInput'
import ColorField from '../fields/ColorField' import ColorField from '../fields/ColorField'
import Modal from './Modal' import Modal from './Modal'
import fieldSpecAdditional from '../../libs/field-spec-additional'
class SettingsModal extends React.Component { class SettingsModal extends React.Component {
static propTypes = { static propTypes = {
@ -86,21 +87,21 @@ class SettingsModal extends React.Component {
title={'Style Settings'} title={'Style Settings'}
> >
<div className="modal-settings"> <div className="modal-settings">
<InputBlock label={"Name"} doc={latest.$root.name.doc}> <InputBlock label={"Name"} fieldSpec={latest.$root.name}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.name" data-wd-key="modal-settings.name"
value={this.props.mapStyle.name} value={this.props.mapStyle.name}
onChange={this.changeStyleProperty.bind(this, "name")} onChange={this.changeStyleProperty.bind(this, "name")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}> <InputBlock label={"Owner"} fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.owner" data-wd-key="modal-settings.owner"
value={this.props.mapStyle.owner} value={this.props.mapStyle.owner}
onChange={this.changeStyleProperty.bind(this, "owner")} onChange={this.changeStyleProperty.bind(this, "owner")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}> <InputBlock label={"Sprite URL"} fieldSpec={latest.$root.sprite}>
<UrlInput {...inputProps} <UrlInput {...inputProps}
data-wd-key="modal-settings.sprite" data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite} value={this.props.mapStyle.sprite}
@ -108,7 +109,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}> <InputBlock label={"Glyphs URL"} fieldSpec={latest.$root.glyphs}>
<UrlInput {...inputProps} <UrlInput {...inputProps}
data-wd-key="modal-settings.glyphs" data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs} value={this.props.mapStyle.glyphs}
@ -116,7 +117,10 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}> <InputBlock
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token" data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']} value={metadata['maputnik:mapbox_access_token']}
@ -124,7 +128,10 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"MapTiler Access Token"} doc={"Public access token for MapTiler Cloud."}> <InputBlock
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token" data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']} value={metadata['maputnik:openmaptiles_access_token']}
@ -132,7 +139,10 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Thunderforest Access Token"} doc={"Public access token for Thunderforest services."}> <InputBlock
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:thunderforest_access_token" data-wd-key="modal-settings.maputnik:thunderforest_access_token"
value={metadata['maputnik:thunderforest_access_token']} value={metadata['maputnik:thunderforest_access_token']}
@ -140,7 +150,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Center"} doc={latest.$root.center.doc}> <InputBlock label={"Center"} fieldSpec={latest.$root.center}>
<ArrayInput <ArrayInput
length={2} length={2}
type="number" type="number"
@ -150,7 +160,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Zoom"} doc={latest.$root.zoom.doc}> <InputBlock label={"Zoom"} fieldSpec={latest.$root.zoom}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={mapStyle.zoom} value={mapStyle.zoom}
@ -159,7 +169,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Bearing"} doc={latest.$root.bearing.doc}> <InputBlock label={"Bearing"} fieldSpec={latest.$root.bearing}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={mapStyle.bearing} value={mapStyle.bearing}
@ -168,7 +178,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Pitch"} doc={latest.$root.pitch.doc}> <InputBlock label={"Pitch"} fieldSpec={latest.$root.pitch}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={mapStyle.pitch} value={mapStyle.pitch}
@ -177,7 +187,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Light anchor"} doc={latest.light.anchor.doc}> <InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}>
<EnumInput <EnumInput
{...inputProps} {...inputProps}
value={light.anchor} value={light.anchor}
@ -187,7 +197,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Light color"} doc={latest.light.color.doc}> <InputBlock label={"Light color"} fieldSpec={latest.light.color}>
<ColorField <ColorField
{...inputProps} {...inputProps}
value={light.color} value={light.color}
@ -196,7 +206,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Light intensity"} doc={latest.light.intensity.doc}> <InputBlock label={"Light intensity"} fieldSpec={latest.light.intensity}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={light.intensity} value={light.intensity}
@ -205,7 +215,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Light position"} doc={latest.light.position.doc}> <InputBlock label={"Light position"} fieldSpec={latest.light.position}>
<ArrayInput <ArrayInput
{...inputProps} {...inputProps}
type="number" type="number"
@ -216,7 +226,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Transition delay"} doc={latest.transition.delay.doc}> <InputBlock label={"Transition delay"} fieldSpec={latest.transition.delay}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={transition.delay} value={transition.delay}
@ -225,7 +235,7 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Transition duration"} doc={latest.transition.duration.doc}> <InputBlock label={"Transition duration"} fieldSpec={latest.transition.duration}>
<NumberInput <NumberInput
{...inputProps} {...inputProps}
value={transition.duration} value={transition.duration}
@ -234,7 +244,10 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}> <InputBlock
label={fieldSpecAdditional.maputnik.style_renderer.label}
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
>
<SelectInput {...inputProps} <SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer" data-wd-key="modal-settings.maputnik:renderer"
options={[ options={[

View file

@ -195,14 +195,25 @@ class AddSource extends React.Component {
} }
render() { render() {
// Kind of a hack because the type changes, however maputnik has 1..n
// options per type, for example
//
// - 'geojson' - 'GeoJSON (URL)' and 'GeoJSON (JSON)'
// - 'raster' - 'Raster (TileJSON URL)' and 'Raster (XYZ URL)'
//
// So we just ignore the values entirely as they are self explanatory
const sourceTypeFieldSpec = {
doc: latest.source_vector.type.doc
};
return <div className="maputnik-add-source"> return <div className="maputnik-add-source">
<InputBlock label={"Source ID"} doc={"Unique ID that identifies the source and is used in the layer to reference the source."}> <InputBlock label={"Source ID"} fieldSpec={{doc: "Unique ID that identifies the source and is used in the layer to reference the source."}}>
<StringInput <StringInput
value={this.state.sourceId} value={this.state.sourceId}
onChange={v => this.setState({ sourceId: v})} onChange={v => this.setState({ sourceId: v})}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}> <InputBlock label={"Source Type"} fieldSpec={sourceTypeFieldSpec}>
<SelectInput <SelectInput
options={[ options={[
['geojson_json', 'GeoJSON (JSON)'], ['geojson_json', 'GeoJSON (JSON)'],

View file

@ -20,7 +20,7 @@ class TileJSONSourceEditor extends React.Component {
render() { render() {
return <div> return <div>
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}> <InputBlock label={"TileJSON URL"} fieldSpec={latest.source_vector.url}>
<UrlInput <UrlInput
value={this.props.source.url} value={this.props.source.url}
onChange={url => this.props.onChange({ onChange={url => this.props.onChange({
@ -54,7 +54,7 @@ class TileURLSourceEditor extends React.Component {
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th'] const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
const tiles = this.props.source.tiles || [] const tiles = this.props.source.tiles || []
return tiles.map((tileUrl, tileIndex) => { return tiles.map((tileUrl, tileIndex) => {
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={latest.source_vector.tiles.doc}> return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} fieldSpec={latest.source_vector.tiles}>
<UrlInput <UrlInput
value={tileUrl} value={tileUrl}
onChange={this.changeTileUrl.bind(this, tileIndex)} onChange={this.changeTileUrl.bind(this, tileIndex)}
@ -66,7 +66,7 @@ class TileURLSourceEditor extends React.Component {
render() { render() {
return <div> return <div>
{this.renderTileUrls()} {this.renderTileUrls()}
<InputBlock label={"Min Zoom"} doc={latest.source_vector.minzoom.doc}> <InputBlock label={"Min Zoom"} fieldSpec={latest.source_vector.minzoom}>
<NumberInput <NumberInput
value={this.props.source.minzoom || 0} value={this.props.source.minzoom || 0}
onChange={minzoom => this.props.onChange({ onChange={minzoom => this.props.onChange({
@ -75,7 +75,7 @@ class TileURLSourceEditor extends React.Component {
})} })}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Max Zoom"} doc={latest.source_vector.maxzoom.doc}> <InputBlock label={"Max Zoom"} fieldSpec={latest.source_vector.maxzoom}>
<NumberInput <NumberInput
value={this.props.source.maxzoom || 22} value={this.props.source.maxzoom || 22}
onChange={maxzoom => this.props.onChange({ onChange={maxzoom => this.props.onChange({
@ -191,7 +191,7 @@ class GeoJSONSourceUrlEditor extends React.Component {
} }
render() { render() {
return <InputBlock label={"GeoJSON URL"} doc={latest.source_geojson.data.doc}> return <InputBlock label={"GeoJSON URL"} fieldSpec={latest.source_geojson.data}>
<UrlInput <UrlInput
value={this.props.source.data} value={this.props.source.data}
onChange={data => this.props.onChange({ onChange={data => this.props.onChange({
@ -210,7 +210,7 @@ class GeoJSONSourceJSONEditor extends React.Component {
} }
render() { render() {
return <InputBlock label={"GeoJSON"} doc={latest.source_geojson.data.doc}> return <InputBlock label={"GeoJSON"} fieldSpec={latest.source_geojson.data}>
<JSONEditor <JSONEditor
layer={this.props.source.data} layer={this.props.source.data}
maxHeight={200} maxHeight={200}
@ -246,7 +246,7 @@ class SourceTypeEditor extends React.Component {
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} /> case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} /> case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}> case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
<InputBlock label={"Encoding"} doc={latest.source_raster_dem.encoding.doc}> <InputBlock label={"Encoding"} fieldSpec={latest.source_raster_dem.encoding}>
<SelectInput <SelectInput
options={Object.keys(latest.source_raster_dem.encoding.values)} options={Object.keys(latest.source_raster_dem.encoding.values)}
onChange={encoding => this.props.onChange({ onChange={encoding => this.props.onChange({

View file

@ -0,0 +1,22 @@
const spec = {
maputnik: {
mapbox_access_token: {
label: "Mapbox Access Token",
doc: "Public access token for Mapbox services."
},
maptiler_access_token: {
label: "MapTiler Access Token",
doc: "Public access token for MapTiler Cloud."
},
thunderforest_access_token: {
label: "Thunderforest Access Token",
doc: "Public access token for Thunderforest services."
},
style_renderer: {
label: "Style Renderer",
doc: "Choose the default Maputnik renderer for this style.",
},
}
}
export default spec;

View file

@ -28,6 +28,14 @@
height: 100%; height: 100%;
} }
.maputnik-input-block:hover,
.maputnik-filter-editor-compound-select:hover {
.maputnik-doc-button {
opacity: 1;
pointer-events: all;
}
}
// DOC LABEL // DOC LABEL
.maputnik-doc { .maputnik-doc {
&-target { &-target {
@ -57,6 +65,33 @@
z-index: 10; z-index: 10;
pointer-events: none; pointer-events: none;
} }
&-button {
opacity: 0;
pointer-events: none;
background: $color-black;
color: white;
border: none;
padding: 0;
svg {
pointer-events: none;
}
&--open {
opacity: 1;
pointer-events: all;
}
}
}
.maputnik-doc-inline {
color: $color-lowgray;
background-color: $color-gray;
padding: $margin-2;
font-size: 12px;
margin-top: $margin-3;
line-height: 1.5;
} }
.maputnik-doc-target:hover .maputnik-doc-popup { .maputnik-doc-target:hover .maputnik-doc-popup {

View file

@ -198,3 +198,30 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.SpecDoc__sdk-support {
position: relative;
max-width: 100%;
overflow-x: auto;
}
.SpecDoc__sdk-support__table {
width: 100%;
margin-top: $margin-3;
td, th {
border: solid 1px $color-midgray;
padding: 4px 6px;
white-space: nowrap;
}
}
.SpecDoc__values li {
margin-top: $margin-3;
}
.SpecDoc__values code {
background: $color-midgray;
padding: 0.1em 0.3em;
border-radius: 2px;
}

View file

@ -32,7 +32,7 @@
top: $toolbar-height + $toolbar-offset; top: $toolbar-height + $toolbar-offset;
left: 200px; left: 200px;
z-index: 1; z-index: 1;
width: 350px; width: 370px;
background-color: $color-black; background-color: $color-black;
} }

View file

@ -108,6 +108,16 @@
background-color: $color-midgray; background-color: $color-midgray;
} }
.maputnik-add-modal {
width: 400px;
max-width: 100%;
}
.maputnik-export-modal {
width: 400px;
max-width: 100%;
}
.maputnik-add-layer { .maputnik-add-layer {
@extend .clearfix; @extend .clearfix;
} }