Skip to content

Commit

Permalink
[google-maps-input] Rewrite to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Oct 6, 2020
1 parent 07b2885 commit 4f1720a
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 157 deletions.
2 changes: 1 addition & 1 deletion packages/@sanity/google-maps-input/.babelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "../../../.babelrc",
"presets": ["@babel/react"]
"presets": ["@babel/react", "@babel/typescript"]
}
2 changes: 1 addition & 1 deletion packages/@sanity/google-maps-input/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
],
"devDependencies": {
"@sanity/check": "1.150.1",
"@types/googlemaps": "^3.39.11",
"lodash": "^4.17.15",
"prop-types": "^15.6.0",
"rimraf": "^2.7.1"
},
"peerDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions packages/@sanity/google-maps-input/src/@types/css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.css' {
const styles: Record<string, string>
export default styles
}
17 changes: 17 additions & 0 deletions packages/@sanity/google-maps-input/src/@types/parts.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
declare module 'part:*'
declare module 'all:part:*' {
const parts: unknown[]
export default parts
}
declare module 'config:@sanity/google-maps-input' {
interface Config {
apiKey: string
defaultZoom?: number
defaultLocation?: {
lat: number
lng: number
}
}
const config: Config
export default config
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PropTypes from 'prop-types'
import React from 'react'
import config from 'config:@sanity/google-maps-input'
import Button from 'part:@sanity/components/buttons/default'
Expand All @@ -7,14 +6,13 @@ import Fieldset from 'part:@sanity/components/fieldsets/default'
import {PatchEvent, set, setIfMissing, unset} from 'part:@sanity/form-builder/patch-event'
import ButtonGrid from 'part:@sanity/components/buttons/button-grid'
import EditIcon from 'part:@sanity/base/edit-icon'
import styles from '../styles/GeopointInput.css'
import GeopointSelect from './GeopointSelect'
import GoogleMapsLoadProxy from './GoogleMapsLoadProxy'
import TrashIcon from 'part:@sanity/base/trash-icon'
import styles from './styles/GeopointInput.css'
import {GeopointSelect} from './GeopointSelect'
import {GoogleMapsLoadProxy} from './GoogleMapsLoadProxy'
import {Geopoint, GeopointSchemaType} from './types'

const getLocale = context => {
const intl = context.intl || {}
return intl.locale || (typeof window !== 'undefined' && window.navigator.language) || 'en'
}
const locale = (typeof window !== 'undefined' && window.navigator.language) || 'en'

const getStaticImageUrl = value => {
const loc = `${value.lat},${value.lng}`
Expand All @@ -29,55 +27,44 @@ const getStaticImageUrl = value => {

const qs = Object.keys(params).reduce((res, param) => {
return res.concat(`${param}=${encodeURIComponent(params[param])}`)
}, [])
}, [] as string[])

return `https://maps.googleapis.com/maps/api/staticmap?${qs.join('&')}`
}

class GeopointInput extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
markers: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string
})
),
value: PropTypes.shape({
lat: PropTypes.number,
lng: PropTypes.number
}),
type: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string
})
}
interface InputProps {
markers: unknown[]
value?: Geopoint
type: GeopointSchemaType
onChange: (patchEvent: unknown) => void
}

interface InputState {
modalOpen: boolean
}

class GeopointInput extends React.Component<InputProps, InputState> {
static defaultProps = {
markers: []
}

static contextTypes = {
intl: PropTypes.shape({
locale: PropTypes.string
})
}

constructor() {
super()

this.handleToggleModal = this.handleToggleModal.bind(this)
this.handleCloseModal = this.handleCloseModal.bind(this)
constructor(props) {
super(props)

this.state = {
modalOpen: false
}
}

handleToggleModal() {
handleToggleModal = () => {
this.setState(prevState => ({modalOpen: !prevState.modalOpen}))
}

handleChange = latLng => {
handleCloseModal = () => {
this.setState({modalOpen: false})
}

handleChange = (latLng: google.maps.LatLng) => {
const {type, onChange} = this.props
onChange(
PatchEvent.from([
Expand All @@ -95,10 +82,6 @@ class GeopointInput extends React.Component {
onChange(PatchEvent.from(unset()))
}

handleCloseModal() {
this.setState({modalOpen: false})
}

render() {
const {value, type, markers} = this.props

Expand All @@ -116,7 +99,7 @@ class GeopointInput extends React.Component {
</ul>
<p>
Please enter the API key with access to these services in
<code style={{whitespace: 'nowrap'}}>
<code style={{whiteSpace: 'nowrap'}}>
`&lt;project-root&gt;/config/@sanity/google-maps-input.json`
</code>
</p>
Expand Down Expand Up @@ -149,7 +132,7 @@ class GeopointInput extends React.Component {
</Button>

{value && (
<Button color="danger" inverted type="button" onClick={this.handleClear}>
<Button color="danger" icon={TrashIcon} inverted onClick={this.handleClear}>
Remove
</Button>
)}
Expand All @@ -161,20 +144,22 @@ class GeopointInput extends React.Component {
title="Place on map"
onClose={this.handleCloseModal}
onCloseClick={this.handleCloseModal}
onOpen={this.handleOpenModal}
message="Select location by dragging the marker or search for a place"
isOpen={this.state.modalOpen}
>
<div className={styles.dialogInner}>
<GoogleMapsLoadProxy
value={value}
apiKey={config.apiKey}
onChange={this.handleChange}
defaultLocation={config.defaultLocation}
defaultZoom={config.defaultZoom}
locale={getLocale(this.context)}
component={GeopointSelect}
/>
<GoogleMapsLoadProxy apiKey={config.apiKey} locale={locale}>
{api => (
<GeopointSelect
api={api}
value={value}
onChange={this.handleChange}
defaultLocation={config.defaultLocation}
defaultZoom={config.defaultZoom}
locale={locale}
/>
)}
</GoogleMapsLoadProxy>
</div>
</Dialog>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,37 @@
import PropTypes from 'prop-types'
import React from 'react'
import styles from '../styles/GeopointSelect.css'

const fallbackLatLng = {lat: 40.7058254, lng: -74.1180863}

class GeopointSelect extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
value: PropTypes.shape({
lat: PropTypes.number,
lng: PropTypes.number
}),
api: PropTypes.shape({
Map: PropTypes.func.isRequired,
Circle: PropTypes.func.isRequired,
Marker: PropTypes.func.isRequired,
LatLng: PropTypes.func.isRequired,
places: PropTypes.shape({Autocomplete: PropTypes.func.isRequired}),
event: PropTypes.shape({addListener: PropTypes.func.isRequired})
}).isRequired,
defaultLocation: PropTypes.shape({
lat: PropTypes.number,
lng: PropTypes.number
}),
defaultZoom: PropTypes.number
}
import styles from './styles/GeopointSelect.css'
import {LatLng, Geopoint} from './types'

const fallbackLatLng: LatLng = {lat: 40.7058254, lng: -74.1180863}

interface SelectProps {
api: typeof window.google.maps
value?: Geopoint
locale: string
onChange: (latLng: google.maps.LatLng) => void
defaultLocation?: LatLng
defaultZoom?: number
}

export class GeopointSelect extends React.Component<SelectProps> {
static defaultProps = {
defaultZoom: 8,
defaultLocation: {lng: 10.74609, lat: 59.91273}
}

constructor(props) {
super(props)
map: google.maps.Map | undefined
marker: google.maps.Marker | undefined
autoComplete: google.maps.places.Autocomplete | undefined

this.elementRefs = {}
}
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()
Expand All @@ -46,47 +40,51 @@ class GeopointSelect extends React.Component {
center: geoPoint
}

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

const searchBounds = new Circle({center: geoPoint, radius: 100}).getBounds()
const input = this.elementRefs.searchInput
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.mapInstance, 'click', clickEvent => {
event.addListener(this.map, 'click', clickEvent => {
this.setValue(clickEvent.latLng)
})
}

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

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

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

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

return this.marker
return marker
}

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

const place = this.autoComplete.getPlace()
if (!place.geometry) {
return
Expand All @@ -95,7 +93,7 @@ class GeopointSelect extends React.Component {
this.setValue(place.geometry.location)
}

handleMarkerDragEnd(event) {
handleMarkerDragEnd = event => {
this.setValue(event.latLng)
}

Expand All @@ -104,25 +102,23 @@ class GeopointSelect extends React.Component {
}

componentDidUpdate() {
this.mapInstance.panTo(this.getValueLatLng())
this.marker.setPosition(this.getValueLatLng())
this.elementRefs.searchInput.value = ''
}

assignReference(type) {
return el => {
this.elementRefs[type] = el
if (!this.map || !this.marker || !this.searchInputRef.current) {
return
}

this.map.panTo(this.getValueLatLng())
this.marker.setPosition(this.getValueLatLng())
this.searchInputRef.current.value = ''
}

render() {
return (
<div className={styles.wrapper}>
<div ref={this.assignReference('map')} className={styles.map} />
<div ref={this.mapRef} className={styles.map} />
<div className={styles.searchInput}>
<input
name="place"
ref={this.assignReference('searchInput')}
ref={this.searchInputRef}
placeholder="Search for place or address"
className={styles.input}
/>
Expand All @@ -131,5 +127,3 @@ class GeopointSelect extends React.Component {
)
}
}

export default GeopointSelect

0 comments on commit 4f1720a

Please sign in to comment.