Skip to content

Commit

Permalink
[google-maps-input] Componentize the different aspects of the map
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Oct 6, 2020
1 parent 8dfa784 commit d5b524a
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 84 deletions.
3 changes: 2 additions & 1 deletion packages/@sanity/google-maps-input/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"rules": {
"react/prop-types": "off"
"react/prop-types": "off",
"complexity": ["warn", 15]
}
}
112 changes: 29 additions & 83 deletions packages/@sanity/google-maps-input/src/GeopointSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React from 'react'
import {SearchInput} from './map/SearchInput'
import {GoogleMap} from './map/Map'
import {Marker} from './map/Marker'
import styles from './styles/GeopointSelect.css'
import {LatLng, Geopoint} from './types'

Expand All @@ -18,110 +21,53 @@ export class GeopointSelect extends React.Component<SelectProps> {
defaultLocation: {lng: 10.74609, lat: 59.91273}
}

map: google.maps.Map | undefined
marker: google.maps.Marker | undefined
autoComplete: google.maps.places.Autocomplete | undefined

mapRef = React.createRef<HTMLDivElement>()
searchInputRef = React.createRef<HTMLInputElement>()

componentDidMount() {
if (!this.mapRef.current || !this.searchInputRef.current) {
// Shouldn't ever happen, but for typescript
return
}

const {Circle, places, event} = this.props.api
const GMap = this.props.api.Map
const geoPoint = this.getValueLatLng()
const options = {
zoom: this.props.defaultZoom,
center: geoPoint
}

this.map = new GMap(this.mapRef.current, options)
this.marker = this.getMarker()

const searchBounds = new Circle({center: geoPoint, radius: 100}).getBounds()
const input = this.searchInputRef.current
this.autoComplete = new places.Autocomplete(input, {
bounds: searchBounds,
types: [] // return all kinds of places
})

event.addListener(this.autoComplete, 'place_changed', this.handlePlaceChanged.bind(this))

event.addListener(this.map, 'click', clickEvent => {
this.setValue(clickEvent.latLng)
})
}

getValueLatLng(): google.maps.LatLng {
const {api, value = {}, defaultLocation = {}} = this.props
const point = {...fallbackLatLng, ...defaultLocation, ...value}
return new api.LatLng(point.lat, point.lng)
}

getMarker(): google.maps.Marker {
if (this.marker) {
return this.marker
}

const {Marker, event} = this.props.api
const marker = new Marker({
position: this.getValueLatLng(),
map: this.map,
draggable: true
})

event.addListener(marker, 'dragend', this.handleMarkerDragEnd)

return marker
getCenter() {
const {value = {}, defaultLocation = {}} = this.props
const point: LatLng = {...fallbackLatLng, ...defaultLocation, ...value}
return point
}

handlePlaceChanged() {
if (!this.autoComplete) {
return
}

const place = this.autoComplete.getPlace()
handlePlaceChanged = (place: google.maps.places.PlaceResult) => {
if (!place.geometry) {
return
}

this.setValue(place.geometry.location)
}

handleMarkerDragEnd = event => {
handleMarkerDragEnd = (event: google.maps.MouseEvent) => {
this.setValue(event.latLng)
}

setValue(geoPoint) {
this.props.onChange(geoPoint)
handleMapClick = (event: google.maps.MouseEvent) => {
this.setValue(event.latLng)
}

componentDidUpdate() {
if (!this.map || !this.marker || !this.searchInputRef.current) {
return
}

this.map.panTo(this.getValueLatLng())
this.marker.setPosition(this.getValueLatLng())
this.searchInputRef.current.value = ''
setValue(geoPoint: google.maps.LatLng) {
this.props.onChange(geoPoint)
}

render() {
const {api, defaultZoom, value} = this.props
return (
<div className={styles.wrapper}>
<div ref={this.mapRef} className={styles.map} />
<div className={styles.searchInput}>
<input
name="place"
ref={this.searchInputRef}
placeholder="Search for place or address"
className={styles.input}
/>
</div>
<GoogleMap
api={api}
location={this.getCenter()}
onClick={this.handleMapClick}
defaultZoom={defaultZoom}
>
{map => (
<>
<SearchInput api={api} map={map} onChange={this.handlePlaceChanged} />
{value && (
<Marker api={api} map={map} position={value} onMove={this.handleMarkerDragEnd} />
)}
</>
)}
</GoogleMap>
</div>
)
}
Expand Down
7 changes: 7 additions & 0 deletions packages/@sanity/google-maps-input/src/map/Map.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import 'part:@sanity/base/theme/variables-style';

.map {
height: 100%;
width: 100%;
box-sizing: border-box;
}
106 changes: 106 additions & 0 deletions packages/@sanity/google-maps-input/src/map/Map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react'
import {LatLng} from '../types'
import styles from './Map.css'

interface MapProps {
api: typeof window.google.maps
location: LatLng
defaultZoom?: number
onClick?: (event: google.maps.MouseEvent) => void
children?: (map: google.maps.Map) => React.ReactElement
}

interface MapState {
map: google.maps.Map | undefined
}

export class GoogleMap extends React.Component<MapProps, MapState> {
static defaultProps = {
defaultZoom: 8
}

state: MapState = {map: undefined}
clickHandler: google.maps.MapsEventListener | undefined
mapRef = React.createRef<HTMLDivElement>()
mapEl: HTMLDivElement | null = null

componentDidMount() {
this.attachClickHandler()
}

attachClickHandler() {
const map = this.state.map
if (!map) {
return
}

const {api, onClick} = this.props
const {event} = api

if (this.clickHandler) {
this.clickHandler.remove()
}

if (onClick) {
this.clickHandler = event.addListener(map, 'click', onClick)
}
}

componentDidUpdate(prevProps: MapProps) {
const map = this.state.map
if (!map) {
return
}

if (prevProps.onClick !== this.props.onClick) {
this.attachClickHandler()
}

if (prevProps.location !== this.props.location) {
map.panTo(this.getCenter())
}
}

componentWillUnmount() {
if (this.clickHandler) {
this.clickHandler.remove()
}
}

getCenter(): google.maps.LatLng {
const {location, api} = this.props
return new api.LatLng(location.lat, location.lng)
}

constructMap(el: HTMLDivElement) {
const {defaultZoom, api} = this.props
const GMap = api.Map
const geoPoint = this.getCenter()
const options = {
zoom: defaultZoom,
center: geoPoint
}

return new GMap(el, options)
}

setMapElement = (element: HTMLDivElement | null) => {
if (element && element !== this.mapEl) {
const map = this.constructMap(element)
this.setState({map})
}

this.mapEl = element
}

render() {
const {children} = this.props
const {map} = this.state
return (
<>
<div ref={this.setMapElement} className={styles.map} />
{children && map && children(map)}
</>
)
}
}
84 changes: 84 additions & 0 deletions packages/@sanity/google-maps-input/src/map/Marker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as React from 'react'
import {LatLng} from '../types'

interface Props {
api: typeof window.google.maps
map: google.maps.Map
onMove?: (event: google.maps.MouseEvent) => void
position: LatLng | google.maps.LatLng
zIndex?: number
opacity?: number
label?: string
}

export class Marker extends React.Component<Props> {
marker: google.maps.Marker | undefined

moveHandler: google.maps.MapsEventListener | undefined

componentDidMount() {
const {position, api, map, onMove, zIndex, opacity, label} = this.props
const {Marker: GMarker} = api
this.marker = new GMarker({
position,
map,
draggable: Boolean(onMove),
zIndex,
opacity,
label
})

this.attachMoveHandler()
}

componentDidUpdate(prevProps: Props) {
if (!this.marker) {
return
}

const {position, onMove, label, zIndex, opacity} = this.props

if (prevProps.onMove !== onMove) {
this.attachMoveHandler()
}

if (prevProps.position !== position) {
this.marker.setPosition(position)
}

if (prevProps.label !== label) {
this.marker.setLabel(label || null)
}

if (prevProps.zIndex !== zIndex) {
this.marker.setZIndex(zIndex || null)
}

if (prevProps.opacity !== opacity) {
this.marker.setOpacity(opacity || null)
}
}

componentWillUnmount() {
if (this.moveHandler) {
this.moveHandler.remove()
}
}

attachMoveHandler() {
const {api, onMove} = this.props

if (this.moveHandler) {
this.moveHandler.remove()
}

if (this.marker && onMove) {
this.moveHandler = api.event.addListener(this.marker, 'dragend', onMove)
}
}

// eslint-disable-next-line class-methods-use-this
render() {
return null
}
}
16 changes: 16 additions & 0 deletions packages/@sanity/google-maps-input/src/map/SearchInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import 'part:@sanity/base/theme/variables-style';

.wrapper {
position: absolute;
right: 10px;
top: 10px;
width: 220px;
}

.input {
composes: textInput from 'part:@sanity/base/theme/forms/text-input-style';
}

:global(.pac-container) {
z-index: calc(var(--zindex-modal) + 1);
}

0 comments on commit d5b524a

Please sign in to comment.