UNPKG

@teikei/map

Version:

Teikei map SPA component. Teikei is the software that powers ernte-teilen.org, a website that maps out Community-supported Agriculture in Germany.

215 lines (187 loc) 5.85 kB
import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import Autocomplete from 'react-autocomplete' import { fieldInputPropTypes, fieldPropTypes } from 'redux-form' import _ from 'lodash' import classNames from 'classnames' import { autoCompleteSearch, geocodeAndShowOnPreviewTile } from './duck' import PreviewTile from '../../components/PreviewTile/index' import i18n from '../../i18n' import { addressOf, cityOf, labelOf } from './searchUtils' // TODO why are onDragStart and onDrop undefined? const fixedFieldPropTypes = { ...fieldPropTypes, input: PropTypes.shape(_.omit(fieldInputPropTypes, ['onDragStart', 'onDrop'])) } const ResultItem = (item, isHighlighted) => ( <div className={classNames({ 'geocoder-search-item': true, 'geocoder-search-item-active': isHighlighted })} key={item.key} > {labelOf(item)} </div> ) const ResultMenu = items => <div className="geocoder-search-menu">{items}</div> const Preview = (latitude, longitude, markerIcon) => ( <PreviewTile latitude={Number(latitude)} longitude={Number(longitude)} markerIcon={markerIcon} /> ) const initialState = { displayValue: '', geocodePosition: {} } class GeocoderSearch extends React.Component { constructor(props) { super(props) this.state = initialState } static getDerivedStateFromProps( { geocodePosition, city, address, latitude, longitude }, prevState ) { if ( geocodePosition.latitude && !_.isEqual(prevState.geocodePosition, geocodePosition) ) { return { geocodePosition, displayValue: labelOf(geocodePosition) } } return null } componentDidMount() { const valueOf = field => field.input.value const { address, city, latitude, longitude } = this.props const addressValue = valueOf(address) const cityValue = valueOf(city) this.setState({ displayValue: addressValue ? [addressValue, cityValue].join(', ') : cityValue, geocodePosition: { latitude: valueOf(latitude), longitude: valueOf(longitude), city: valueOf(city), street: valueOf(address) } }) } componentDidUpdate(prevProps, prevState, snapshot) { if ( this.state.geocodePosition.latitude && !_.isEqual(prevState.geocodePosition, this.state.geocodePosition) ) { this.props.address.input.onChange(addressOf(this.state.geocodePosition)) this.props.city.input.onChange(cityOf(this.state.geocodePosition)) this.props.latitude.input.onChange(this.state.geocodePosition.latitude) this.props.longitude.input.onChange(this.state.geocodePosition.longitude) } } handleSelect = (event, value) => { if (value) { this.props.onSelect(value.id) } } handleChange = (event, value) => { if (value) { this.setState({ displayValue: value }) this.props.onAutocomplete(value) } else { this.setState(initialState) } } render() { const { error, touched } = this.props.address.meta const { latitude, longitude } = this.state.geocodePosition const items = this.props.geocoderItems.filter( ({ type }) => type.toString() === 'location' ) const wrapperClassNames = classNames({ 'geocoder-search': true, 'form-input-error': error && touched }) return ( <div className={wrapperClassNames}> <label className={classNames({ required: this.props.required })} htmlFor={this.props.name} > {this.props.label} </label> <div className="geocoder-search-input-container"> <Autocomplete inputProps={{ name: this.props.name, className: 'geocoder-search-input', placeholder: i18n.t('geocoder.placeholder') }} renderItem={ResultItem} renderMenu={ResultMenu} onChange={this.handleChange} onSelect={this.handleSelect} items={items} getItemValue={item => labelOf(item)} value={this.state.displayValue} /> {latitude && longitude && Preview(latitude, longitude, this.props.markerIcon)} </div> {touched && error && <p className="form-error">{error}</p>} <div className="geocoder-search-info"> <p>{i18n.t('geocoder.help')}</p> <p>{i18n.t('geocoder.explanation')}</p> </div> </div> ) } } GeocoderSearch.propTypes = { label: PropTypes.string.isRequired, name: PropTypes.string.isRequired, markerIcon: PropTypes.oneOf(['Depot', 'Farm', 'Initiative']).isRequired, onAutocomplete: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired, geocoderItems: PropTypes.arrayOf(PropTypes.object).isRequired, geocodePosition: PropTypes.shape({ lat: PropTypes.number, lon: PropTypes.number, address: PropTypes.string, city: PropTypes.string }), required: PropTypes.bool, city: PropTypes.shape(fixedFieldPropTypes).isRequired, address: PropTypes.shape(fixedFieldPropTypes).isRequired, latitude: PropTypes.shape(fixedFieldPropTypes).isRequired, longitude: PropTypes.shape(fixedFieldPropTypes).isRequired } GeocoderSearch.defaultProps = { required: false, geocodePosition: {}, meta: {} } const mapStateToProps = ({ search }, props) => ({ geocoderItems: search.items, geocodePosition: search.geocodePosition, ...props }) const mapDispatchToProps = dispatch => ({ onAutocomplete: payload => dispatch(autoCompleteSearch(payload)), onSelect: id => dispatch(geocodeAndShowOnPreviewTile(id)) }) const GeocoderSearchContainer = connect( mapStateToProps, mapDispatchToProps )(GeocoderSearch) export default GeocoderSearchContainer