| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import React from 'react'; |
| import PropTypes from 'prop-types'; |
| import { Row, Col } from 'react-bootstrap'; |
| import { t } from '@superset-ui/core'; |
| |
| import Label from 'src/components/Label'; |
| import Popover from 'src/components/Popover'; |
| import PopoverSection from 'src/components/PopoverSection'; |
| import Checkbox from 'src/components/Checkbox'; |
| import ControlHeader from '../ControlHeader'; |
| import SelectControl from './SelectControl'; |
| |
| const spatialTypes = { |
| latlong: 'latlong', |
| delimited: 'delimited', |
| geohash: 'geohash', |
| }; |
| |
| const propTypes = { |
| onChange: PropTypes.func, |
| value: PropTypes.object, |
| animation: PropTypes.bool, |
| choices: PropTypes.array, |
| }; |
| |
| const defaultProps = { |
| onChange: () => {}, |
| animation: true, |
| choices: [], |
| }; |
| |
| export default class SpatialControl extends React.Component { |
| constructor(props) { |
| super(props); |
| const v = props.value || {}; |
| let defaultCol; |
| if (props.choices.length > 0) { |
| defaultCol = props.choices[0][0]; |
| } |
| this.state = { |
| type: v.type || spatialTypes.latlong, |
| delimiter: v.delimiter || ',', |
| latCol: v.latCol || defaultCol, |
| lonCol: v.lonCol || defaultCol, |
| lonlatCol: v.lonlatCol || defaultCol, |
| reverseCheckbox: v.reverseCheckbox || false, |
| geohashCol: v.geohashCol || defaultCol, |
| value: null, |
| errors: [], |
| }; |
| this.toggleCheckbox = this.toggleCheckbox.bind(this); |
| this.onChange = this.onChange.bind(this); |
| this.renderReverseCheckbox = this.renderReverseCheckbox.bind(this); |
| } |
| |
| componentDidMount() { |
| this.onChange(); |
| } |
| |
| onChange() { |
| const { type } = this.state; |
| const value = { type }; |
| const errors = []; |
| const errMsg = t('Invalid lat/long configuration.'); |
| if (type === spatialTypes.latlong) { |
| value.latCol = this.state.latCol; |
| value.lonCol = this.state.lonCol; |
| if (!value.lonCol || !value.latCol) { |
| errors.push(errMsg); |
| } |
| } else if (type === spatialTypes.delimited) { |
| value.lonlatCol = this.state.lonlatCol; |
| value.delimiter = this.state.delimiter; |
| value.reverseCheckbox = this.state.reverseCheckbox; |
| if (!value.lonlatCol || !value.delimiter) { |
| errors.push(errMsg); |
| } |
| } else if (type === spatialTypes.geohash) { |
| value.geohashCol = this.state.geohashCol; |
| value.reverseCheckbox = this.state.reverseCheckbox; |
| if (!value.geohashCol) { |
| errors.push(errMsg); |
| } |
| } |
| this.setState({ value, errors }); |
| this.props.onChange(value, errors); |
| } |
| |
| setType(type) { |
| this.setState({ type }, this.onChange); |
| } |
| |
| toggleCheckbox() { |
| this.setState( |
| prevState => ({ reverseCheckbox: !prevState.reverseCheckbox }), |
| this.onChange, |
| ); |
| } |
| |
| renderLabelContent() { |
| if (this.state.errors.length > 0) { |
| return 'N/A'; |
| } |
| if (this.state.type === spatialTypes.latlong) { |
| return `${this.state.lonCol} | ${this.state.latCol}`; |
| } |
| if (this.state.type === spatialTypes.delimited) { |
| return `${this.state.lonlatCol}`; |
| } |
| if (this.state.type === spatialTypes.geohash) { |
| return `${this.state.geohashCol}`; |
| } |
| return null; |
| } |
| |
| renderSelect(name, type) { |
| return ( |
| <SelectControl |
| name={name} |
| choices={this.props.choices} |
| value={this.state[name]} |
| clearable={false} |
| onFocus={() => { |
| this.setType(type); |
| }} |
| onChange={value => { |
| this.setState({ [name]: value }, this.onChange); |
| }} |
| /> |
| ); |
| } |
| |
| renderReverseCheckbox() { |
| return ( |
| <span> |
| {t('Reverse lat/long ')} |
| <Checkbox |
| checked={this.state.reverseCheckbox} |
| onChange={this.toggleCheckbox} |
| /> |
| </span> |
| ); |
| } |
| |
| renderPopoverContent() { |
| return ( |
| <div style={{ width: '300px' }}> |
| <PopoverSection |
| title={t('Longitude & Latitude columns')} |
| isSelected={this.state.type === spatialTypes.latlong} |
| onSelect={this.setType.bind(this, spatialTypes.latlong)} |
| > |
| <Row> |
| <Col md={6}> |
| Longitude |
| {this.renderSelect('lonCol', spatialTypes.latlong)} |
| </Col> |
| <Col md={6}> |
| Latitude |
| {this.renderSelect('latCol', spatialTypes.latlong)} |
| </Col> |
| </Row> |
| </PopoverSection> |
| <PopoverSection |
| title={t('Delimited long & lat single column')} |
| info={t( |
| 'Multiple formats accepted, look the geopy.points ' + |
| 'Python library for more details', |
| )} |
| isSelected={this.state.type === spatialTypes.delimited} |
| onSelect={this.setType.bind(this, spatialTypes.delimited)} |
| > |
| <Row> |
| <Col md={6}> |
| {t('Column')} |
| {this.renderSelect('lonlatCol', spatialTypes.delimited)} |
| </Col> |
| <Col md={6}>{this.renderReverseCheckbox()}</Col> |
| </Row> |
| </PopoverSection> |
| <PopoverSection |
| title={t('Geohash')} |
| isSelected={this.state.type === spatialTypes.geohash} |
| onSelect={this.setType.bind(this, spatialTypes.geohash)} |
| > |
| <Row> |
| <Col md={6}> |
| Column |
| {this.renderSelect('geohashCol', spatialTypes.geohash)} |
| </Col> |
| <Col md={6}>{this.renderReverseCheckbox()}</Col> |
| </Row> |
| </PopoverSection> |
| </div> |
| ); |
| } |
| |
| render() { |
| return ( |
| <div> |
| <ControlHeader {...this.props} /> |
| <Popover |
| content={this.renderPopoverContent()} |
| placement="topLeft" // so that popover doesn't move when label changes |
| trigger="click" |
| > |
| <Label className="pointer">{this.renderLabelContent()}</Label> |
| </Popover> |
| </div> |
| ); |
| } |
| } |
| |
| SpatialControl.propTypes = propTypes; |
| SpatialControl.defaultProps = defaultProps; |