Merge pull request #563 from orangemug/feature/more-root-property-support

Added support for more root level properties
This commit is contained in:
Orange Mug 2019-10-21 10:18:37 +01:00 committed by GitHub
commit c45cf2f0c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 236 additions and 21 deletions

View file

@ -59,7 +59,7 @@ export default class PropertyGroup extends React.Component {
onChange={this.onPropertyChange} onChange={this.onPropertyChange}
key={fieldName} key={fieldName}
fieldName={fieldName} fieldName={fieldName}
value={fieldValue === undefined ? fieldSpec.default : fieldValue} value={fieldValue}
fieldSpec={fieldSpec} fieldSpec={fieldSpec}
/> />
}) })

View file

@ -12,29 +12,89 @@ class ArrayInput extends React.Component {
onChange: PropTypes.func, onChange: PropTypes.func,
} }
changeValue(idx, newValue) { static defaultProps = {
console.log(idx, newValue) value: [],
const values = this.values.slice(0) default: [],
values[idx] = newValue
this.props.onChange(values)
} }
get values() { constructor (props) {
return this.props.value || this.props.default || [] super(props);
this.state = {
value: this.props.value.slice(0),
// This is so we can compare changes in getDerivedStateFromProps
initialPropsValue: this.props.value.slice(0),
};
}
static getDerivedStateFromProps(props, state) {
const value = [];
const initialPropsValue = state.initialPropsValue.slice(0);
Array(props.length).fill(null).map((_, i) => {
if (props.value[i] === state.initialPropsValue[i]) {
value[i] = state.value[i];
}
else {
value[i] = state.value[i];
initialPropsValue[i] = state.value[i];
}
})
return {
value,
initialPropsValue,
};
}
isComplete (value) {
return Array(this.props.length).fill(null).every((_, i) => {
const val = value[i]
return !(val === undefined || val === "");
});
}
changeValue(idx, newValue) {
const value = this.state.value.slice(0);
value[idx] = newValue;
this.setState({
value,
}, () => {
if (this.isComplete(value)) {
this.props.onChange(value);
}
else {
// Unset until complete
this.props.onChange(undefined);
}
});
} }
render() { render() {
const inputs = this.values.map((v, i) => { const {value} = this.state;
const containsValues = (
value.length > 0 &&
!value.every(val => {
return (val === "" || val === undefined)
})
);
const inputs = Array(this.props.length).fill(null).map((_, i) => {
if(this.props.type === 'number') { if(this.props.type === 'number') {
return <NumberInput return <NumberInput
key={i} key={i}
value={v} default={containsValues ? undefined : this.props.default[i]}
value={value[i]}
required={containsValues ? true : false}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
/> />
} else { } else {
return <StringInput return <StringInput
key={i} key={i}
value={v} default={containsValues ? undefined : this.props.default[i]}
value={value[i]}
required={containsValues ? true : false}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
/> />
} }

View file

@ -29,13 +29,13 @@ class EnumInput extends React.Component {
if(options.length <= 3 && optionsLabelLength(options) <= 20) { if(options.length <= 3 && optionsLabelLength(options) <= 20) {
return <MultiButtonInput return <MultiButtonInput
options={options} options={options}
value={value} value={value || this.props.default}
onChange={onChange} onChange={onChange}
/> />
} else { } else {
return <SelectInput return <SelectInput
options={options} options={options}
value={value} value={value || this.props.default}
onChange={onChange} onChange={onChange}
/> />
} }

View file

@ -8,6 +8,7 @@ class NumberInput extends React.Component {
min: PropTypes.number, min: PropTypes.number,
max: PropTypes.number, max: PropTypes.number,
onChange: PropTypes.func, onChange: PropTypes.func,
required: PropTypes.bool,
} }
constructor(props) { constructor(props) {
@ -65,7 +66,7 @@ class NumberInput extends React.Component {
this.setState({editing: false}); this.setState({editing: false});
// Reset explicitly to default value if value has been cleared // Reset explicitly to default value if value has been cleared
if(this.state.value === "") { if(this.state.value === "") {
return this.changeValue(this.props.default) return;
} }
// If set value is invalid fall back to the last valid value from props or at last resort the default value // If set value is invalid fall back to the last valid value from props or at last resort the default value
@ -73,7 +74,7 @@ class NumberInput extends React.Component {
if(this.isValid(this.props.value)) { if(this.isValid(this.props.value)) {
this.changeValue(this.props.value) this.changeValue(this.props.value)
} else { } else {
this.changeValue(this.props.default) this.changeValue(undefined);
} }
} }
} }
@ -83,9 +84,10 @@ class NumberInput extends React.Component {
spellCheck="false" spellCheck="false"
className="maputnik-number" className="maputnik-number"
placeholder={this.props.default} placeholder={this.props.default}
value={this.state.value || ""} value={this.state.value === undefined ? "" : this.state.value}
onChange={e => this.changeValue(e.target.value)} onChange={e => this.changeValue(e.target.value)}
onBlur={this.resetValue} onBlur={this.resetValue}
required={this.props.required}
/> />
} }
} }

View file

@ -9,6 +9,7 @@ class StringInput extends React.Component {
default: PropTypes.string, default: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
multi: PropTypes.bool, multi: PropTypes.bool,
required: PropTypes.bool,
} }
constructor(props) { constructor(props) {
@ -50,7 +51,7 @@ class StringInput extends React.Component {
spellCheck: !(tag === "input"), spellCheck: !(tag === "input"),
className: classes.join(" "), className: classes.join(" "),
style: this.props.style, style: this.props.style,
value: this.state.value, value: this.state.value === undefined ? "" : this.state.value,
placeholder: this.props.default, placeholder: this.props.default,
onChange: e => { onChange: e => {
this.setState({ this.setState({
@ -63,7 +64,8 @@ class StringInput extends React.Component {
this.setState({editing: false}); this.setState({editing: false});
this.props.onChange(this.state.value); this.props.onChange(this.state.value);
} }
} },
required: this.props.required,
}); });
} }
} }

View file

@ -3,8 +3,12 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import ArrayInput from '../inputs/ArrayInput'
import NumberInput from '../inputs/NumberInput'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
import EnumInput from '../inputs/EnumInput'
import ColorField from '../fields/ColorField'
import Modal from './Modal' import Modal from './Modal'
class SettingsModal extends React.Component { class SettingsModal extends React.Component {
@ -16,18 +20,64 @@ class SettingsModal extends React.Component {
onOpenToggle: PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
} }
changeTransitionProperty(property, value) {
const transition = {
...this.props.mapStyle.transition,
}
if (value === undefined) {
delete transition[property];
}
else {
transition[property] = value;
}
this.props.onStyleChanged({
...this.props.mapStyle,
transition,
});
}
changeLightProperty(property, value) {
const light = {
...this.props.mapStyle.light,
}
if (value === undefined) {
delete light[property];
}
else {
light[property] = value;
}
this.props.onStyleChanged({
...this.props.mapStyle,
light,
});
}
changeStyleProperty(property, value) { changeStyleProperty(property, value) {
const changedStyle = { const changedStyle = {
...this.props.mapStyle, ...this.props.mapStyle,
[property]: value };
if (value === undefined) {
delete changedStyle[property];
} }
this.props.onStyleChanged(changedStyle) else {
changedStyle[property] = value;
}
this.props.onStyleChanged(changedStyle);
} }
render() { render() {
const metadata = this.props.mapStyle.metadata || {} const metadata = this.props.mapStyle.metadata || {}
const {onChangeMetadataProperty} = this.props; const {onChangeMetadataProperty, mapStyle} = this.props;
const inputProps = { } const inputProps = { }
const light = this.props.mapStyle.light || {};
const transition = this.props.mapStyle.transition || {};
return <Modal return <Modal
data-wd-key="modal-settings" data-wd-key="modal-settings"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
@ -89,6 +139,100 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Center"} doc={latest.$root.center.doc}>
<ArrayInput
length={2}
type="number"
value={mapStyle.center}
default={latest.$root.center.default || [0, 0]}
onChange={this.changeStyleProperty.bind(this, "center")}
/>
</InputBlock>
<InputBlock label={"Zoom"} doc={latest.$root.zoom.doc}>
<NumberInput
{...inputProps}
value={mapStyle.zoom}
default={latest.$root.zoom.default || 0}
onChange={this.changeStyleProperty.bind(this, "zoom")}
/>
</InputBlock>
<InputBlock label={"Bearing"} doc={latest.$root.bearing.doc}>
<NumberInput
{...inputProps}
value={mapStyle.bearing}
default={latest.$root.bearing.default}
onChange={this.changeStyleProperty.bind(this, "bearing")}
/>
</InputBlock>
<InputBlock label={"Pitch"} doc={latest.$root.pitch.doc}>
<NumberInput
{...inputProps}
value={mapStyle.pitch}
default={latest.$root.pitch.default}
onChange={this.changeStyleProperty.bind(this, "pitch")}
/>
</InputBlock>
<InputBlock label={"Light anchor"} doc={latest.light.anchor.doc}>
<EnumInput
{...inputProps}
value={light.anchor}
options={Object.keys(latest.light.anchor.values)}
default={latest.light.anchor.default}
onChange={this.changeLightProperty.bind(this, "anchor")}
/>
</InputBlock>
<InputBlock label={"Light color"} doc={latest.light.color.doc}>
<ColorField
{...inputProps}
value={light.color}
default={latest.light.color.default}
onChange={this.changeLightProperty.bind(this, "color")}
/>
</InputBlock>
<InputBlock label={"Light intensity"} doc={latest.light.intensity.doc}>
<NumberInput
{...inputProps}
value={light.intensity}
default={latest.light.intensity.default}
onChange={this.changeLightProperty.bind(this, "intensity")}
/>
</InputBlock>
<InputBlock label={"Light position"} doc={latest.light.position.doc}>
<ArrayInput
{...inputProps}
type="number"
length={latest.light.position.length}
value={light.position}
default={latest.light.position.default}
onChange={this.changeLightProperty.bind(this, "position")}
/>
</InputBlock>
<InputBlock label={"Transition delay"} doc={latest.transition.delay.doc}>
<NumberInput
{...inputProps}
value={transition.delay}
default={latest.transition.delay.default}
onChange={this.changeTransitionProperty.bind(this, "delay")}
/>
</InputBlock>
<InputBlock label={"Transition duration"} doc={latest.transition.duration.doc}>
<NumberInput
{...inputProps}
value={transition.duration}
default={latest.transition.duration.default}
onChange={this.changeTransitionProperty.bind(this, "duration")}
/>
</InputBlock>
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}> <InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
<SelectInput {...inputProps} <SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer" data-wd-key="modal-settings.maputnik:renderer"
@ -101,6 +245,8 @@ class SettingsModal extends React.Component {
/> />
</InputBlock> </InputBlock>
</div> </div>
</Modal> </Modal>
} }

View file

@ -11,6 +11,11 @@
border: none; border: none;
background-color: $color-gray; background-color: $color-gray;
color: lighten($color-lowgray, 12); color: lighten($color-lowgray, 12);
&:invalid {
border: solid 1px #B71C1C;
border-radius: 2px;
}
} }
.maputnik-string { .maputnik-string {