Skip to content

Commit

Permalink
[google-maps-input] Provide diff component
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Oct 6, 2020
1 parent 757dcd7 commit 2b1143b
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 29 deletions.
1 change: 1 addition & 0 deletions packages/@sanity/google-maps-input/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"rules": {
"react/prop-types": "off",
"react/require-default-props": "off",
"complexity": ["warn", 15]
}
}
6 changes: 6 additions & 0 deletions packages/@sanity/google-maps-input/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@
"google-maps-input",
"sanity-plugin"
],
"dependencies": {
"@sanity/react-hooks": "1.150.4"
},
"devDependencies": {
"@sanity/check": "1.150.1",
"@sanity/components": "1.150.3",
"@sanity/diff": "1.150.1",
"@types/googlemaps": "^3.39.11",
"lodash": "^4.17.15",
"rimraf": "^2.7.1"
},
"peerDependencies": {
"@sanity/components": "^1.150.0",
"react": "^16.9",
"react-dom": "^16.9"
},
Expand Down
6 changes: 5 additions & 1 deletion packages/@sanity/google-maps-input/sanity.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
{
"name": "part:@sanity/google-maps-input/input/geopoint",
"implements": "part:@sanity/form-builder/input/geopoint",
"path": "GeopointInput.js"
"path": "input/GeopointInput"
},
{
"implements": "part:@sanity/base/diff-resolver",
"path": "diff/resolver"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class GoogleMapsLoadProxy extends React.Component<LoadProps, LoadState> {
}

if (api) {
return this.props.children(api)
return this.props.children(api) || null
}

return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.root {
height: 300px;
}
64 changes: 64 additions & 0 deletions packages/@sanity/google-maps-input/src/diff/GeopointFieldDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as React from 'react'
import {ObjectDiff} from '@sanity/diff'
import {GeopointSchemaType, Geopoint, Annotation} from '../types'
import {GoogleMapsLoadProxy} from '../loader/GoogleMapsLoadProxy'
import {GoogleMap} from '../map/Map'
import {GeopointMove} from './GeopointMove'
import styles from './GeopointFieldDiff.css'

export interface DiffProps {
diff: ObjectDiff<Annotation, Geopoint>
schemaType: GeopointSchemaType
}

export const GeopointFieldDiff: React.ComponentType<DiffProps> = ({diff, schemaType}) => {
return (
<div className={styles.root}>
<GoogleMapsLoadProxy>
{api => <GeopointDiff api={api} diff={diff} schemaType={schemaType} />}
</GoogleMapsLoadProxy>
</div>
)
}

function GeopointDiff({api, diff}: DiffProps & {api: typeof window.google.maps}) {
const {fromValue, toValue} = diff
const center = getCenter(diff, api)
const bounds = fromValue && toValue ? getBounds(fromValue, toValue, api) : undefined

return (
<GoogleMap api={api} location={center} mapTypeControl={false} controlSize={20} bounds={bounds}>
{map => <GeopointMove api={api} map={map} diff={diff} />}
</GoogleMap>
)
}

function getBounds(
fromValue: google.maps.LatLngLiteral,
toValue: google.maps.LatLngLiteral,
api: typeof window.google.maps
): google.maps.LatLngBounds {
return new api.LatLngBounds().extend(fromValue).extend(toValue)
}

function getCenter(
diff: DiffProps['diff'],
api: typeof window.google.maps
): google.maps.LatLngLiteral {
const {fromValue, toValue} = diff
if (fromValue && toValue) {
return getBounds(fromValue, toValue, api)
.getCenter()
.toJSON()
}

if (fromValue) {
return fromValue
}

if (toValue) {
return toValue
}

throw new Error('Neither a from or a to value present')
}
95 changes: 95 additions & 0 deletions packages/@sanity/google-maps-input/src/diff/GeopointMove.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import {ObjectDiff} from '@sanity/diff'
import {useUser} from '@sanity/react-hooks'
import {UserAvatar} from '@sanity/components/presence'
import {Marker} from '../map/Marker'
import {Arrow} from '../map/Arrow'
import {Geopoint, Annotation} from '../types'

interface Props {
api: typeof window.google.maps
map: google.maps.Map
diff: ObjectDiff<Annotation, Geopoint>
label?: string
}

export function GeopointMove({diff, api, map, label}: Props) {
const {fromValue: from, toValue: to} = diff
const annotation = diff.isChanged ? diff.annotation : undefined
const fromRef = React.useRef<google.maps.Marker>()
const toRef = React.useRef<google.maps.Marker>()
const infoEl = React.useRef(document.createElement('div'))
const infoWindow = React.useRef(new api.InfoWindow({content: infoEl.current}))
const openInfoOn = (ref: React.MutableRefObject<google.maps.Marker | undefined>) =>
React.useCallback(() => {
const current = ref.current
if (!current) {
return
}

infoWindow.current.open(map, current)

const position = current.getPosition()
if (position) {
map.panTo(position)
}
}, [infoWindow.current, map])

const openInfoOnFrom = openInfoOn(fromRef)
const openInfoOnTo = openInfoOn(toRef)

return (
<>
{from && (
<Marker
api={api}
map={map}
position={from}
zIndex={0}
opacity={0.75}
onClick={openInfoOnFrom}
markerRef={fromRef}
label={label}
/>
)}
{from && to && <Arrow api={api} map={map} from={from} to={to} zIndex={1} />}
{to && (
<Marker
api={api}
map={map}
position={to}
zIndex={2}
onClick={openInfoOnTo}
markerRef={toRef}
label={label}
/>
)}
{annotation &&
ReactDOM.createPortal(<AnnotationInfo annotation={annotation} />, infoEl.current)}
</>
)
}

function UserItem(props: {userId: string}) {
const {isLoading, error, value: user} = useUser(props.userId)

// @todo handle?
if (error) {
return null
}

if (isLoading) {
return <em>Loading…</em>
}

return user ? (
<>
<UserAvatar user={user} /> {user.displayName}
</>
) : null
}

function AnnotationInfo({annotation}: {annotation: Annotation}) {
return <UserItem userId={annotation.author} />
}
10 changes: 10 additions & 0 deletions packages/@sanity/google-maps-input/src/diff/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {GeopointSchemaType} from '../types'
import {GeopointFieldDiff} from './GeopointFieldDiff'

export default function diffResolver({schemaType}: {schemaType: GeopointSchemaType}) {
if (schemaType.name === 'geopoint') {
return GeopointFieldDiff
}

return undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {PatchEvent, set, setIfMissing, unset} from 'part:@sanity/form-builder/pa
import ButtonGrid from 'part:@sanity/components/buttons/button-grid'
import EditIcon from 'part:@sanity/base/edit-icon'
import TrashIcon from 'part:@sanity/base/trash-icon'
import styles from './styles/GeopointInput.css'
import {GoogleMapsLoadProxy} from '../GoogleMapsLoadProxy'
import {Geopoint, GeopointSchemaType} from '../types'
import styles from './GeopointInput.css'
import {GeopointSelect} from './GeopointSelect'
import {GoogleMapsLoadProxy} from './GoogleMapsLoadProxy'
import {Geopoint, GeopointSchemaType} from './types'

const getStaticImageUrl = value => {
const loc = `${value.lat},${value.lng}`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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'
import {SearchInput} from '../map/SearchInput'
import {GoogleMap} from '../map/Map'
import {Marker} from '../map/Marker'
import {LatLng, Geopoint} from '../types'
import styles from './GeopointSelect.css'

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

Expand Down
74 changes: 74 additions & 0 deletions packages/@sanity/google-maps-input/src/map/Arrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react'
import {LatLng} from '../types'

interface Props {
api: typeof window.google.maps
map: google.maps.Map
from: LatLng
to: LatLng
zIndex?: number
arrowRef?: React.MutableRefObject<google.maps.Polyline | undefined>
onClick?: (event: google.maps.MouseEvent) => void
}

export class Arrow extends React.Component<Props> {
line: google.maps.Polyline | undefined

eventHandlers: {
click?: google.maps.MapsEventListener
} = {}

componentDidMount() {
const {from, to, api, map, zIndex, onClick, arrowRef} = this.props
const lineSymbol = {
path: api.SymbolPath.FORWARD_OPEN_ARROW
}

this.line = new api.Polyline({
map,
zIndex,
path: [from, to],
icons: [{icon: lineSymbol, offset: '50%'}],
strokeOpacity: 0.8,
strokeColor: 'blue'
})

if (onClick) {
this.eventHandlers.click = api.event.addListener(this.line, 'click', onClick)
}

if (arrowRef) {
arrowRef.current = this.line
}
}

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

const {from, to, map} = this.props
if (prevProps.from !== from || prevProps.to !== to) {
this.line.setPath([from, to])
}

if (prevProps.map !== map) {
this.line.setMap(map)
}
}

componentWillUnmount() {
if (this.line) {
this.line.setMap(null)
}

if (this.eventHandlers.click) {
this.eventHandlers.click.remove()
}
}

// eslint-disable-next-line class-methods-use-this
render() {
return null
}
}
33 changes: 24 additions & 9 deletions packages/@sanity/google-maps-input/src/map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import styles from './Map.css'
interface MapProps {
api: typeof window.google.maps
location: LatLng
bounds?: google.maps.LatLngBounds
defaultZoom?: number
mapTypeControl?: boolean
controlSize?: number
onClick?: (event: google.maps.MouseEvent) => void
children?: (map: google.maps.Map) => React.ReactElement
}
Expand Down Expand Up @@ -52,13 +55,19 @@ export class GoogleMap extends React.Component<MapProps, MapState> {
return
}

if (prevProps.onClick !== this.props.onClick) {
const {onClick, location, bounds} = this.props

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

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

if (prevProps.bounds !== bounds && bounds) {
map.fitBounds(bounds)
}
}

componentWillUnmount() {
Expand All @@ -73,15 +82,21 @@ export class GoogleMap extends React.Component<MapProps, MapState> {
}

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

const map = new api.Map(el, {
zoom: defaultZoom,
center: geoPoint
center: this.getCenter(),
streetViewControl: false,
mapTypeControl,
controlSize
})

if (bounds) {
map.fitBounds(bounds)
}

return new GMap(el, options)
return map
}

setMapElement = (element: HTMLDivElement | null) => {
Expand All @@ -99,7 +114,7 @@ export class GoogleMap extends React.Component<MapProps, MapState> {
return (
<>
<div ref={this.setMapElement} className={styles.map} />
{children && map && children(map)}
{children && map ? children(map) : null}
</>
)
}
Expand Down

0 comments on commit 2b1143b

Please sign in to comment.