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 PropTypes from 'prop-types' import capitalize from 'lodash.capitalize' import docUid from '../../libs/document-uid' import sortNumerically from '../../libs/sort-numerically' 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: PropTypes.func.isRequired, fieldName: PropTypes.string.isRequired, fieldSpec: PropTypes.object.isRequired, value: PropTypes.oneOfType([ PropTypes.object, PropTypes.string, PropTypes.number, PropTypes.bool, PropTypes.array ]), } constructor() { super() this.state = { refs: {} } } /** * We cache a reference for each stop by its index. * * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus. */ setStopRefs(props) { // This is initialsed below only if required to improved performance. let newRefs; if(props.value && props.value.stops) { props.value.stops.forEach((val, idx) => { if(!this.state.refs.hasOwnProperty(idx)) { if(!newRefs) { newRefs = {...this.state.refs}; } newRefs[idx] = docUid("stop-"); } }) } if(newRefs) { this.setState({ refs: newRefs }) } } componentWillReceiveProps(nextProps) { this.setStopRefs(nextProps); } // Order the stops altering the refs to reflect their new position. orderStopsByZoom(stops) { const mappedWithRef = stops .map((stop, idx) => { return { ref: this.state.refs[idx], data: stop } }) // Sort by zoom .sort((a, b) => sortNumerically(a.data[0], b.data[0])); // Fetch the new position of the stops const newRefs = {}; mappedWithRef .forEach((stop, idx) =>{ newRefs[idx] = stop.ref; }) this.setState({ refs: newRefs }); return mappedWithRef.map((item) => item.data); } 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) } getFieldFunctionType(fieldSpec) { if (fieldSpec.function === "interpolated") { return "exponential" } if (fieldSpec.type === "number") { return "interval" } return "categorical" } 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 orderedStops = this.orderStopsByZoom(stops); const changedValue = { ...this.props.value, stops: orderedStops } 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() { if (typeof this.props.value.type === "undefined") { this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec) } 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 key = this.state.refs[idx]; 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}
} } class MakeFunctionButtons extends React.Component { static propTypes = { fieldSpec: PropTypes.object, onZoomClick: PropTypes.func, onDataClick: PropTypes.func, } render() { let makeZoomButton, makeDataButton if (this.props.fieldSpec['zoom-function']) { makeZoomButton = if (this.props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(this.props.fieldSpec['function']) !== -1) { makeDataButton = } return
{makeDataButton}{makeZoomButton}
} else { return null } } } class DeleteStopButton extends React.Component { static propTypes = { onClick: PropTypes.func, } render() { return } } function labelFromFieldName(fieldName) { let label = fieldName.split('-').slice(1).join(' ') return capitalize(label) }