UNPKG

@bigfishtv/cockpit

Version:

311 lines (280 loc) 8.17 kB
import React, { Component } from 'react' import PropTypes from 'prop-types' import { withFormValue, Field } from '@bigfishtv/react-forms' import GoogleMap from 'google-map-react' import _get from 'lodash/get' import _find from 'lodash/find' import cn from 'classnames' import $ from 'jquery' import Icon from '../Icon' import Cell from '../cell/Cell' const defaultMapOptions = maps => ({ panControl: true, mapTypeControl: false, scrollwheel: true, }) const DefaultMarker = props => ( <div style={{ position: 'absolute', left: -18, top: -36, cursor: 'pointer' }}> <Icon name="place" size={36} /> </div> ) class GoogleMapsSearchBox extends Component { static propTypes = { apiLoaded: PropTypes.bool, placeholder: PropTypes.string, onPlaceChanged: PropTypes.func, } componentDidMount() { if (this.props.apiLoaded) this.addListener() if (window.MutationObserver) { const observerHack = new MutationObserver(() => { observerHack.disconnect() $(this.input).attr('autocomplete', 'new-password') }) observerHack.observe(this.input, { attributes: true, attributeFilter: ['autocomplete'], }) } } componentWillUnmount() { if (this.props.apiLoaded && this.searchBoxListener) { google.maps.event.removeListener(this.searchBoxListener) } } componentDidUpdate(prevProps) { if (prevProps.apiLoaded != this.props.apiLoaded && this.props.apiLoaded) this.addListener() } addListener() { this.autoComplete = new google.maps.places.Autocomplete(this.input, this.props.autoCompleteOptions || {}) this.autoCompleteListener = this.autoComplete.addListener('place_changed', this.onPlaceChanged) } onPlaceChanged = () => { if (this.props.onPlaceChanged) { this.props.onPlaceChanged(this.autoComplete.getPlace(), this.input.value) } } handleKeyDown = event => { if (event.key === 'Enter') { event.preventDefault() } } render() { const { value, inputProps, error, onInputChange, onInputBlur } = this.props return ( <div className="form-field" style={{ margin: '0.5rem 0' }}> <input {...inputProps} type="text" value={value} className={cn(inputProps.className, error && 'error')} ref={elem => (this.input = elem)} onChange={e => onInputChange(e.target.value)} onKeyDown={this.handleKeyDown} onBlur={onInputBlur} /> </div> ) } } @withFormValue export default class GoogleMapsInput extends Component { static defaultProps = { width: '100%', height: 350, className: '', apiKey: null, selectPrepend: '', label: 'Location', instructions: null, placeholder: 'Search for place...', showMap: true, clearInputOnSelect: true, showTitleField: false, showAddressField: false, showDetailFields: false, isOwnEntity: false, disableMarkerDrag: true, inputProps: {}, autoCompleteOptions: {}, createMapOptions: defaultMapOptions, defaultCenter: [-27.471, 153.0142812], zoom: 12, minZoom: 4, center: null, Marker: DefaultMarker, SearchBox: GoogleMapsSearchBox, } constructor(props) { super(props) this.state = { inputValue: '', dragging: false, apiLoaded: false, } } handlePlaceChange = (place, typedAddress) => { const { selectPrepend, clearInputOnSelect } = this.props let address = place.formatted_address if (place.name && address.indexOf(place.name) < 0) address = place.name + ', ' + address const newValue = { [selectPrepend + 'lat']: place.geometry.location.lat(), [selectPrepend + 'lng']: place.geometry.location.lng(), [selectPrepend + 'google_maps_url']: place.url, [selectPrepend + 'address']: address, } if (place.name) newValue.title = place.name this.setState({ inputValue: clearInputOnSelect ? '' : typedAddress }, () => { this.props.formValue.update({ ...(this.props.formValue.value || {}), ...newValue, }) }) } handleChildMouseDown = (hoverKey, childProps, mouse) => { if (this.props.disableMarkerDrag) return this.setState({ dragging: true, lat: mouse.lat, lng: mouse.lng, latOffset: childProps.lat - mouse.lat, lngOffset: childProps.lng - mouse.lng, }) } handleChildMouseUp = (hoverKey, childProps, mouse) => { if (this.props.disableMarkerDrag) return this.setState({ dragging: false }) this.props.formValue.update({ ...this.props.formValue.value, lat: mouse.lat + this.state.latOffset, lng: mouse.lng + this.state.lngOffset, }) } handleChildMouseMove = (hoverKey, childProps, mouse) => { if (this.props.disableMarkerDrag) return this.setState({ lat: mouse.lat, lng: mouse.lng }) } handleChange = mapState => { if (mapState.zoom) { this.props.formValue.update({ ...this.props.formValue.value, zoom: mapState.zoom, }) } } handleClear() { const { selectPrepend, formValue, isOwnEntity } = this.props if (isOwnEntity) { formValue.update(null) } else { const oldFormValue = formValue.value const newFormValue = {} const excludeKeys = ['address', 'lat', 'lng', 'google_maps_url', 'zoom'].map(val => selectPrepend + val) Object.keys(oldFormValue).forEach(key => { if (excludeKeys.indexOf(key) < 0) newFormValue[key] = oldFormValue[key] }) formValue.update(newFormValue) } } render() { const { apiKey, width, height, className, inputProps, label, instructions, selectPrepend, zoom, minZoom, showMap, placeholder, createMapOptions, defaultCenter, autoCompleteOptions, formValue, showTitleField, showAddressField, showDetailFields, Marker, SearchBox, } = this.props const { apiLoaded, dragging, inputValue, latOffset, lngOffset } = this.state let lat = _get(formValue.value, selectPrepend + 'lat') let lng = _get(formValue.value, selectPrepend + 'lng') let address = _get(formValue.value, selectPrepend + 'address') const center = lat && lng ? [lat, lng] : defaultCenter const currentZoom = zoom if (dragging) { lat = this.state.lat + latOffset lng = this.state.lng + lngOffset } const keyPath = formValue.keyPath.length > 0 ? formValue.keyPath.join('.') + '.' : '' const addressError = _find(formValue.completeErrorList, { field: `data.${keyPath}${selectPrepend}address`, }) return ( <div className={className}> {showTitleField && <Field select="title" label="Place title" />} <label>{label}</label> {instructions && <p className="form-message">{instructions}</p>} {address ? ( <div> <Cell className="cell-input" icon="place" title={address} editable={false} onRemove={() => this.handleClear()} /> {addressError && <div className="form-message error">{addressError.message}</div>} </div> ) : ( <SearchBox apiLoaded={apiLoaded} placeholder={placeholder} value={inputValue} inputProps={inputProps} error={addressError} autoCompleteOptions={autoCompleteOptions} onInputChange={inputValue => this.setState({ inputValue })} onPlaceChanged={this.handlePlaceChange} /> )} <div style={{ width, height: showMap ? height : 0 }}> <GoogleMap bootstrapURLKeys={{ key: apiKey, libraries: 'places' }} onGoogleApiLoaded={() => console.log('API LOADEWD THO') || this.setState({ apiLoaded: true })} options={createMapOptions} hoverDistance={20} center={center} zoom={currentZoom} minZoom={minZoom} draggable={!this.state.dragging} onChange={this.handleChange} onChildMouseDown={this.handleChildMouseDown} onChildMouseUp={this.handleChildMouseUp} onChildMouseMove={this.handleChildMouseMove} yesIWantToUseGoogleMapApiInternals> <Marker lat={center[0]} lng={center[1]} /> </GoogleMap> </div> {showAddressField && <Field select="address" />} {showDetailFields && ( <div className="row gutter-top"> <div className="column-1-3"> <Field select="lat" /> </div> <div className="column-1-3"> <Field select="lng" /> </div> <div className="column-1-3"> <Field select="zoom" /> </div> </div> )} </div> ) } }