@sanity/google-maps-input 2.26.0 → 2.26.1-purple-unicorn.560

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/diff/resolver.js +7 -0
  2. package/input/GeopointInput.js +7 -0
  3. package/lib/_Marker-20984c4d.cjs +331 -0
  4. package/lib/_Marker-20984c4d.cjs.map +1 -0
  5. package/lib/_Marker-96f5387c.js +302 -0
  6. package/lib/_Marker-96f5387c.js.map +1 -0
  7. package/lib/_reExport.js +19 -0
  8. package/lib/diff/resolver.cjs +204 -0
  9. package/lib/diff/resolver.cjs.map +1 -0
  10. package/lib/diff/resolver.js +169 -17
  11. package/lib/diff/resolver.js.map +1 -0
  12. package/{dist/dts → lib/dts/src}/diff/GeopointArrayDiff.d.ts +4 -4
  13. package/lib/dts/src/diff/GeopointArrayDiff.d.ts.map +1 -0
  14. package/{dist/dts → lib/dts/src}/diff/GeopointFieldDiff.d.ts +4 -4
  15. package/lib/dts/src/diff/GeopointFieldDiff.d.ts.map +1 -0
  16. package/{dist/dts → lib/dts/src}/diff/GeopointFieldDiff.styles.d.ts +1 -1
  17. package/lib/dts/src/diff/GeopointFieldDiff.styles.d.ts.map +1 -0
  18. package/{dist/dts → lib/dts/src}/diff/GeopointMove.d.ts +12 -12
  19. package/lib/dts/src/diff/GeopointMove.d.ts.map +1 -0
  20. package/lib/dts/src/diff/resolver.d.ts +4 -0
  21. package/lib/dts/src/diff/resolver.d.ts.map +1 -0
  22. package/{dist/dts → lib/dts/src}/input/GeopointInput.d.ts +27 -39
  23. package/lib/dts/src/input/GeopointInput.d.ts.map +1 -0
  24. package/{dist/dts → lib/dts/src}/input/GeopointInput.styles.d.ts +2 -2
  25. package/lib/dts/src/input/GeopointInput.styles.d.ts.map +1 -0
  26. package/{dist/dts → lib/dts/src}/input/GeopointSelect.d.ts +27 -27
  27. package/lib/dts/src/input/GeopointSelect.d.ts.map +1 -0
  28. package/{dist/dts → lib/dts/src}/loader/GoogleMapsLoadProxy.d.ts +13 -13
  29. package/lib/dts/src/loader/GoogleMapsLoadProxy.d.ts.map +1 -0
  30. package/{dist/dts → lib/dts/src}/loader/LoadError.d.ts +9 -9
  31. package/lib/dts/src/loader/LoadError.d.ts.map +1 -0
  32. package/{dist/dts → lib/dts/src}/loader/loadGoogleMapsApi.d.ts +17 -17
  33. package/lib/dts/src/loader/loadGoogleMapsApi.d.ts.map +1 -0
  34. package/{dist/dts → lib/dts/src}/map/Arrow.d.ts +28 -28
  35. package/lib/dts/src/map/Arrow.d.ts.map +1 -0
  36. package/{dist/dts → lib/dts/src}/map/Map.d.ts +36 -36
  37. package/lib/dts/src/map/Map.d.ts.map +1 -0
  38. package/{dist/dts → lib/dts/src}/map/Map.styles.d.ts +1 -1
  39. package/lib/dts/src/map/Map.styles.d.ts.map +1 -0
  40. package/{dist/dts → lib/dts/src}/map/Marker.d.ts +33 -35
  41. package/lib/dts/src/map/Marker.d.ts.map +1 -0
  42. package/{dist/dts → lib/dts/src}/map/SearchInput.d.ts +15 -15
  43. package/lib/dts/src/map/SearchInput.d.ts.map +1 -0
  44. package/{dist/dts → lib/dts/src}/map/SearchInput.styles.d.ts +1 -1
  45. package/lib/dts/src/map/SearchInput.styles.d.ts.map +1 -0
  46. package/{dist/dts → lib/dts/src}/map/util.d.ts +3 -3
  47. package/lib/dts/src/map/util.d.ts.map +1 -0
  48. package/lib/dts/src/types.d.ts +17 -0
  49. package/lib/dts/src/types.d.ts.map +1 -0
  50. package/lib/input/GeopointInput.cjs +228 -0
  51. package/lib/input/GeopointInput.cjs.map +1 -0
  52. package/lib/input/GeopointInput.js +198 -190
  53. package/lib/input/GeopointInput.js.map +1 -0
  54. package/package.json +34 -11
  55. package/src/@types/css.d.ts +4 -0
  56. package/src/diff/GeopointArrayDiff.tsx +83 -0
  57. package/src/diff/GeopointFieldDiff.styles.tsx +20 -0
  58. package/src/diff/GeopointFieldDiff.tsx +94 -0
  59. package/src/diff/GeopointMove.tsx +49 -0
  60. package/src/diff/resolver.ts +21 -0
  61. package/src/input/GeopointInput.styles.tsx +12 -0
  62. package/src/input/GeopointInput.tsx +221 -0
  63. package/src/input/GeopointSelect.tsx +78 -0
  64. package/src/loader/GoogleMapsLoadProxy.tsx +49 -0
  65. package/src/loader/LoadError.tsx +44 -0
  66. package/src/loader/loadGoogleMapsApi.ts +93 -0
  67. package/src/map/Arrow.tsx +76 -0
  68. package/src/map/Map.styles.tsx +10 -0
  69. package/src/map/Map.tsx +125 -0
  70. package/src/map/Marker.tsx +130 -0
  71. package/src/map/SearchInput.styles.tsx +8 -0
  72. package/src/map/SearchInput.tsx +56 -0
  73. package/src/map/util.ts +14 -0
  74. package/src/types.ts +19 -0
  75. package/.depcheckignore.json +0 -3
  76. package/dist/dts/diff/GeopointArrayDiff.d.ts.map +0 -1
  77. package/dist/dts/diff/GeopointFieldDiff.d.ts.map +0 -1
  78. package/dist/dts/diff/GeopointFieldDiff.styles.d.ts.map +0 -1
  79. package/dist/dts/diff/GeopointMove.d.ts.map +0 -1
  80. package/dist/dts/diff/resolver.d.ts +0 -4
  81. package/dist/dts/diff/resolver.d.ts.map +0 -1
  82. package/dist/dts/input/GeopointInput.d.ts.map +0 -1
  83. package/dist/dts/input/GeopointInput.styles.d.ts.map +0 -1
  84. package/dist/dts/input/GeopointSelect.d.ts.map +0 -1
  85. package/dist/dts/loader/GoogleMapsLoadProxy.d.ts.map +0 -1
  86. package/dist/dts/loader/LoadError.d.ts.map +0 -1
  87. package/dist/dts/loader/loadGoogleMapsApi.d.ts.map +0 -1
  88. package/dist/dts/map/Arrow.d.ts.map +0 -1
  89. package/dist/dts/map/Map.d.ts.map +0 -1
  90. package/dist/dts/map/Map.styles.d.ts.map +0 -1
  91. package/dist/dts/map/Marker.d.ts.map +0 -1
  92. package/dist/dts/map/SearchInput.d.ts.map +0 -1
  93. package/dist/dts/map/SearchInput.styles.d.ts.map +0 -1
  94. package/dist/dts/map/util.d.ts.map +0 -1
  95. package/dist/dts/types.d.ts +0 -14
  96. package/dist/dts/types.d.ts.map +0 -1
  97. package/lib/@types/css.d.js +0 -1
  98. package/lib/diff/GeopointArrayDiff.js +0 -82
  99. package/lib/diff/GeopointFieldDiff.js +0 -97
  100. package/lib/diff/GeopointFieldDiff.styles.js +0 -18
  101. package/lib/diff/GeopointMove.js +0 -55
  102. package/lib/input/GeopointInput.styles.js +0 -22
  103. package/lib/input/GeopointSelect.js +0 -103
  104. package/lib/loader/GoogleMapsLoadProxy.js +0 -70
  105. package/lib/loader/LoadError.js +0 -43
  106. package/lib/loader/loadGoogleMapsApi.js +0 -81
  107. package/lib/map/Arrow.js +0 -97
  108. package/lib/map/Map.js +0 -147
  109. package/lib/map/Map.styles.js +0 -18
  110. package/lib/map/Marker.js +0 -156
  111. package/lib/map/SearchInput.js +0 -77
  112. package/lib/map/SearchInput.styles.js +0 -18
  113. package/lib/map/util.js +0 -14
  114. package/lib/types.js +0 -5
  115. package/tsconfig.json +0 -20
@@ -0,0 +1,94 @@
1
+ import * as React from 'react'
2
+ import {
3
+ DiffComponent,
4
+ ObjectDiff,
5
+ DiffProps as GenericDiffProps,
6
+ DiffTooltip,
7
+ getAnnotationAtPath,
8
+ } from '@sanity/base/field'
9
+ import {GoogleMapsLoadProxy} from '../loader/GoogleMapsLoadProxy'
10
+ import {GoogleMap} from '../map/Map'
11
+ import {Geopoint} from '../types'
12
+ import {GeopointMove} from './GeopointMove'
13
+ import {RootContainer} from './GeopointFieldDiff.styles'
14
+
15
+ export type DiffProps = GenericDiffProps<ObjectDiff<Geopoint>>
16
+
17
+ export const GeopointFieldDiff: DiffComponent<ObjectDiff<Geopoint>> = ({diff, schemaType}) => {
18
+ return (
19
+ <RootContainer>
20
+ <GoogleMapsLoadProxy>
21
+ {(api) => <GeopointDiff api={api} diff={diff} schemaType={schemaType} />}
22
+ </GoogleMapsLoadProxy>
23
+ </RootContainer>
24
+ )
25
+ }
26
+
27
+ function GeopointDiff({api, diff}: DiffProps & {api: typeof window.google.maps}) {
28
+ const {fromValue, toValue} = diff
29
+ const annotation =
30
+ getAnnotationAtPath(diff, ['lat']) ||
31
+ getAnnotationAtPath(diff, ['lng']) ||
32
+ getAnnotationAtPath(diff, [])
33
+
34
+ const center = getCenter(diff, api)
35
+ const bounds = fromValue && toValue ? getBounds(fromValue, toValue, api) : undefined
36
+
37
+ return (
38
+ <DiffTooltip annotations={annotation ? [annotation] : []} description={getAction(diff)}>
39
+ <div>
40
+ <GoogleMap
41
+ api={api}
42
+ location={center}
43
+ mapTypeControl={false}
44
+ controlSize={20}
45
+ bounds={bounds}
46
+ scrollWheel={false}
47
+ >
48
+ {(map) => <GeopointMove api={api} map={map} diff={diff} />}
49
+ </GoogleMap>
50
+ </div>
51
+ </DiffTooltip>
52
+ )
53
+ }
54
+
55
+ function getBounds(
56
+ fromValue: google.maps.LatLngLiteral,
57
+ toValue: google.maps.LatLngLiteral,
58
+ api: typeof window.google.maps
59
+ ): google.maps.LatLngBounds {
60
+ return new api.LatLngBounds().extend(fromValue).extend(toValue)
61
+ }
62
+
63
+ function getCenter(
64
+ diff: DiffProps['diff'],
65
+ api: typeof window.google.maps
66
+ ): google.maps.LatLngLiteral {
67
+ const {fromValue, toValue} = diff
68
+ if (fromValue && toValue) {
69
+ return getBounds(fromValue, toValue, api).getCenter().toJSON()
70
+ }
71
+
72
+ if (fromValue) {
73
+ return fromValue
74
+ }
75
+
76
+ if (toValue) {
77
+ return toValue
78
+ }
79
+
80
+ throw new Error('Neither a from or a to value present')
81
+ }
82
+
83
+ function getAction(diff: ObjectDiff<Geopoint>) {
84
+ const {fromValue, toValue} = diff
85
+ if (fromValue && toValue) {
86
+ return 'Moved'
87
+ } else if (fromValue) {
88
+ return 'Removed'
89
+ } else if (toValue) {
90
+ return 'Added'
91
+ }
92
+
93
+ return 'Unchanged'
94
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react'
2
+ import {useUserColor} from '@sanity/base/user-color'
3
+ import {ObjectDiff} from '@sanity/base/field'
4
+ import {Marker} from '../map/Marker'
5
+ import {Arrow} from '../map/Arrow'
6
+ import {Geopoint} from '../types'
7
+
8
+ interface Props {
9
+ api: typeof window.google.maps
10
+ map: google.maps.Map
11
+ diff: ObjectDiff<Geopoint>
12
+ label?: string
13
+ }
14
+
15
+ export function GeopointMove({diff, api, map, label}: Props) {
16
+ const {fromValue: from, toValue: to} = diff
17
+ const annotation = diff.isChanged ? diff.annotation : undefined
18
+ const userColor = useUserColor(annotation ? annotation.author : null) || undefined
19
+ const fromRef = React.useRef<google.maps.Marker>()
20
+ const toRef = React.useRef<google.maps.Marker>()
21
+
22
+ return (
23
+ <>
24
+ {from && (
25
+ <Marker
26
+ api={api}
27
+ map={map}
28
+ position={from}
29
+ zIndex={0}
30
+ opacity={0.55}
31
+ markerRef={fromRef}
32
+ color={userColor}
33
+ />
34
+ )}
35
+ {from && to && <Arrow api={api} map={map} from={from} to={to} zIndex={1} color={userColor} />}
36
+ {to && (
37
+ <Marker
38
+ api={api}
39
+ map={map}
40
+ position={to}
41
+ zIndex={2}
42
+ markerRef={toRef}
43
+ label={label}
44
+ color={userColor}
45
+ />
46
+ )}
47
+ </>
48
+ )
49
+ }
@@ -0,0 +1,21 @@
1
+ import {DiffComponentResolver} from '@sanity/base/field'
2
+ import {GeopointFieldDiff} from './GeopointFieldDiff'
3
+ import {GeopointArrayDiff} from './GeopointArrayDiff'
4
+
5
+ const diffResolver: DiffComponentResolver = function diffResolver({schemaType}) {
6
+ if (schemaType.name === 'geopoint') {
7
+ return GeopointFieldDiff
8
+ }
9
+
10
+ if (
11
+ schemaType.jsonType === 'array' &&
12
+ schemaType.of.length === 1 &&
13
+ schemaType.of[0].name === 'geopoint'
14
+ ) {
15
+ return GeopointArrayDiff
16
+ }
17
+
18
+ return undefined
19
+ }
20
+
21
+ export default diffResolver
@@ -0,0 +1,12 @@
1
+ import styled from 'styled-components'
2
+
3
+ export const PreviewImage = styled.img`
4
+ width: 100%;
5
+ height: auto;
6
+ vertical-align: top;
7
+ `
8
+
9
+ export const DialogInnerContainer = styled.div`
10
+ height: 40rem;
11
+ width: 50rem;
12
+ `
@@ -0,0 +1,221 @@
1
+ // @todo: remove the following line when part imports has been removed from this file
2
+ ///<reference types="@sanity/types/parts" />
3
+
4
+ import React from 'react'
5
+ import {uniqueId} from 'lodash'
6
+ import {Box, Grid, Button, Dialog} from '@sanity/ui'
7
+ import {TrashIcon, EditIcon} from '@sanity/icons'
8
+ import config from 'config:@sanity/google-maps-input'
9
+ import {
10
+ FormFieldSet,
11
+ ChangeIndicatorCompareValueProvider,
12
+ ChangeIndicator,
13
+ } from '@sanity/base/components'
14
+ import {FormInputProps, PatchEvent, set, setIfMissing, unset} from '@sanity/base/form'
15
+ import {GoogleMapsLoadProxy} from '../loader/GoogleMapsLoadProxy'
16
+ import {Geopoint, GeopointSchemaType} from '../types'
17
+ import {GeopointSelect} from './GeopointSelect'
18
+ import {PreviewImage, DialogInnerContainer} from './GeopointInput.styles'
19
+
20
+ const getStaticImageUrl = (value) => {
21
+ const loc = `${value.lat},${value.lng}`
22
+ const params = {
23
+ key: config.apiKey,
24
+ center: loc,
25
+ markers: loc,
26
+ zoom: 13,
27
+ scale: 2,
28
+ size: '640x300',
29
+ }
30
+
31
+ const qs = Object.keys(params).reduce((res, param) => {
32
+ return res.concat(`${param}=${encodeURIComponent(params[param])}`)
33
+ }, [] as string[])
34
+
35
+ return `https://maps.googleapis.com/maps/api/staticmap?${qs.join('&')}`
36
+ }
37
+
38
+ type GeopointInputProps = FormInputProps<Geopoint, GeopointSchemaType>
39
+
40
+ // @todo
41
+ // interface Focusable {
42
+ // focus: () => void
43
+ // }
44
+ type Focusable = any
45
+
46
+ interface InputState {
47
+ modalOpen: boolean
48
+ }
49
+
50
+ class GeopointInput extends React.PureComponent<GeopointInputProps, InputState> {
51
+ _geopointInputId = uniqueId('GeopointInput')
52
+
53
+ static defaultProps = {
54
+ validation: [],
55
+ }
56
+
57
+ editButton: Focusable | undefined
58
+
59
+ constructor(props) {
60
+ super(props)
61
+
62
+ this.state = {
63
+ modalOpen: false,
64
+ }
65
+ }
66
+
67
+ setEditButton = (el: Focusable) => {
68
+ this.editButton = el
69
+ }
70
+
71
+ focus() {
72
+ if (this.editButton) {
73
+ this.editButton.focus()
74
+ }
75
+ }
76
+
77
+ handleFocus = (event) => {
78
+ this.props.onFocus(event)
79
+ }
80
+
81
+ handleBlur = () => {
82
+ this.props.onBlur?.()
83
+ }
84
+
85
+ handleToggleModal = () => {
86
+ const {onFocus, onBlur} = this.props
87
+ this.setState(
88
+ (prevState) => ({modalOpen: !prevState.modalOpen}),
89
+ () => {
90
+ if (this.state.modalOpen) {
91
+ onFocus(['$'])
92
+ } else {
93
+ onBlur?.()
94
+ }
95
+ }
96
+ )
97
+ }
98
+
99
+ handleCloseModal = () => {
100
+ this.setState({modalOpen: false})
101
+ }
102
+
103
+ handleChange = (latLng: google.maps.LatLng) => {
104
+ const {type, onChange} = this.props
105
+ onChange(
106
+ PatchEvent.from([
107
+ setIfMissing({
108
+ _type: type.name,
109
+ }),
110
+ set(latLng.lat(), ['lat']),
111
+ set(latLng.lng(), ['lng']),
112
+ ])
113
+ )
114
+ }
115
+
116
+ handleClear = () => {
117
+ const {onChange} = this.props
118
+ onChange(PatchEvent.from(unset()))
119
+ }
120
+
121
+ render() {
122
+ const {value, compareValue, readOnly, type, validation, level, presence} = this.props
123
+ const {modalOpen} = this.state
124
+
125
+ if (!config || !config.apiKey) {
126
+ return (
127
+ <div>
128
+ <p>
129
+ The <a href="https://sanity.io/docs/schema-types/geopoint-type">Geopoint type</a> needs
130
+ a Google Maps API key with access to:
131
+ </p>
132
+ <ul>
133
+ <li>Google Maps JavaScript API</li>
134
+ <li>Google Places API Web Service</li>
135
+ <li>Google Static Maps API</li>
136
+ </ul>
137
+ <p>
138
+ Please enter the API key with access to these services in
139
+ <code style={{whiteSpace: 'nowrap'}}>
140
+ `&lt;project-root&gt;/config/@sanity/google-maps-input.json`
141
+ </code>
142
+ </p>
143
+ </div>
144
+ )
145
+ }
146
+
147
+ return (
148
+ <FormFieldSet
149
+ level={level}
150
+ title={type.title}
151
+ description={type.description}
152
+ onFocus={this.handleFocus}
153
+ onBlur={this.handleBlur}
154
+ __unstable_presence={presence}
155
+ __unstable_changeIndicator={false}
156
+ validation={validation}
157
+ >
158
+ <div>
159
+ {value && (
160
+ <ChangeIndicatorCompareValueProvider value={value} compareValue={compareValue}>
161
+ <ChangeIndicator compareDeep>
162
+ <PreviewImage src={getStaticImageUrl(value)} alt="Map location" />
163
+ </ChangeIndicator>
164
+ </ChangeIndicatorCompareValueProvider>
165
+ )}
166
+
167
+ {!readOnly && (
168
+ <Box marginTop={4}>
169
+ <Grid columns={2} gap={2}>
170
+ <Button
171
+ mode="ghost"
172
+ icon={value && EditIcon}
173
+ padding={3}
174
+ ref={this.setEditButton}
175
+ text={value ? 'Edit' : 'Set location'}
176
+ onClick={this.handleToggleModal}
177
+ />
178
+
179
+ {value && (
180
+ <Button
181
+ tone="critical"
182
+ icon={TrashIcon}
183
+ padding={3}
184
+ mode="ghost"
185
+ text={'Remove'}
186
+ onClick={this.handleClear}
187
+ />
188
+ )}
189
+ </Grid>
190
+ </Box>
191
+ )}
192
+
193
+ {modalOpen && (
194
+ <Dialog
195
+ id={`${this._geopointInputId}_dialog`}
196
+ onClose={this.handleCloseModal}
197
+ header="Place the marker on the map"
198
+ width={1}
199
+ >
200
+ <DialogInnerContainer>
201
+ <GoogleMapsLoadProxy>
202
+ {(api) => (
203
+ <GeopointSelect
204
+ api={api}
205
+ value={value || undefined}
206
+ onChange={readOnly ? undefined : this.handleChange}
207
+ defaultLocation={config.defaultLocation}
208
+ defaultZoom={config.defaultZoom}
209
+ />
210
+ )}
211
+ </GoogleMapsLoadProxy>
212
+ </DialogInnerContainer>
213
+ </Dialog>
214
+ )}
215
+ </div>
216
+ </FormFieldSet>
217
+ )
218
+ }
219
+ }
220
+
221
+ export default GeopointInput
@@ -0,0 +1,78 @@
1
+ import React from 'react'
2
+ import {SearchInput} from '../map/SearchInput'
3
+ import {GoogleMap} from '../map/Map'
4
+ import {Marker} from '../map/Marker'
5
+ import {LatLng, Geopoint} from '../types'
6
+
7
+ const fallbackLatLng: LatLng = {lat: 40.7058254, lng: -74.1180863}
8
+
9
+ interface SelectProps {
10
+ api: typeof window.google.maps
11
+ value?: Geopoint
12
+ onChange?: (latLng: google.maps.LatLng) => void
13
+ defaultLocation?: LatLng
14
+ defaultZoom?: number
15
+ }
16
+
17
+ export class GeopointSelect extends React.PureComponent<SelectProps> {
18
+ static defaultProps = {
19
+ defaultZoom: 8,
20
+ defaultLocation: {lng: 10.74609, lat: 59.91273},
21
+ }
22
+
23
+ mapRef = React.createRef<HTMLDivElement>()
24
+
25
+ getCenter() {
26
+ const {value = {}, defaultLocation = {}} = this.props
27
+ const point: LatLng = {...fallbackLatLng, ...defaultLocation, ...value}
28
+ return point
29
+ }
30
+
31
+ handlePlaceChanged = (place: google.maps.places.PlaceResult) => {
32
+ if (!place.geometry) {
33
+ return
34
+ }
35
+
36
+ this.setValue(place.geometry.location)
37
+ }
38
+
39
+ handleMarkerDragEnd = (event: google.maps.MapMouseEvent) => {
40
+ this.setValue(event.latLng)
41
+ }
42
+
43
+ handleMapClick = (event: google.maps.MapMouseEvent) => {
44
+ this.setValue(event.latLng)
45
+ }
46
+
47
+ setValue(geoPoint: google.maps.LatLng) {
48
+ if (this.props.onChange) {
49
+ this.props.onChange(geoPoint)
50
+ }
51
+ }
52
+
53
+ render() {
54
+ const {api, defaultZoom, value, onChange} = this.props
55
+ return (
56
+ <GoogleMap
57
+ api={api}
58
+ location={this.getCenter()}
59
+ onClick={this.handleMapClick}
60
+ defaultZoom={defaultZoom}
61
+ >
62
+ {(map) => (
63
+ <>
64
+ <SearchInput api={api} map={map} onChange={this.handlePlaceChanged} />
65
+ {value && (
66
+ <Marker
67
+ api={api}
68
+ map={map}
69
+ position={value}
70
+ onMove={onChange ? this.handleMarkerDragEnd : undefined}
71
+ />
72
+ )}
73
+ </>
74
+ )}
75
+ </GoogleMap>
76
+ )
77
+ }
78
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import {Subscription} from 'rxjs'
3
+ import {loadGoogleMapsApi, GoogleLoadState} from './loadGoogleMapsApi'
4
+ import {LoadError} from './LoadError'
5
+
6
+ interface LoadProps {
7
+ children: (api: typeof window.google.maps) => React.ReactElement
8
+ }
9
+
10
+ export class GoogleMapsLoadProxy extends React.Component<LoadProps, GoogleLoadState> {
11
+ loadSubscription: Subscription | undefined
12
+
13
+ constructor(props: LoadProps) {
14
+ super(props)
15
+
16
+ this.state = {loadState: 'loading'}
17
+
18
+ let sync = true
19
+ this.loadSubscription = loadGoogleMapsApi().subscribe((loadState) => {
20
+ if (sync) {
21
+ this.state = loadState
22
+ } else {
23
+ this.setState(loadState)
24
+ }
25
+ })
26
+ sync = false
27
+ }
28
+
29
+ componentWillUnmount() {
30
+ if (this.loadSubscription) {
31
+ this.loadSubscription.unsubscribe()
32
+ }
33
+ }
34
+
35
+ render() {
36
+ switch (this.state.loadState) {
37
+ case 'loadError':
38
+ return <LoadError error={this.state.error} isAuthError={false} />
39
+ case 'authError':
40
+ return <LoadError isAuthError />
41
+ case 'loading':
42
+ return <div>Loading Google Maps API</div>
43
+ case 'loaded':
44
+ return this.props.children(this.state.api) || null
45
+ default:
46
+ return null
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from 'react'
2
+ import {Card, Box, Text, Code} from '@sanity/ui'
3
+
4
+ type Props = {error: Error; isAuthError: false} | {isAuthError: true}
5
+
6
+ export function LoadError(props: Props) {
7
+ return (
8
+ <Card tone="critical" radius={1}>
9
+ <Box as="header" paddingX={4} paddingTop={4} paddingBottom={1}>
10
+ <Text as="h2" weight="bold">
11
+ Google Maps failed to load
12
+ </Text>
13
+ </Box>
14
+
15
+ <Box paddingX={4} paddingTop={4} paddingBottom={1}>
16
+ {props.isAuthError ? (
17
+ <AuthError />
18
+ ) : (
19
+ <>
20
+ <Text as="h3">Error details:</Text>
21
+ <pre>
22
+ <Code size={1}>{props.error?.message}</Code>
23
+ </pre>
24
+ </>
25
+ )}
26
+ </Box>
27
+ </Card>
28
+ )
29
+ }
30
+
31
+ function AuthError() {
32
+ return (
33
+ <Text>
34
+ <p>The error appears to be related to authentication</p>
35
+ <p>Common causes include:</p>
36
+ <ul>
37
+ <li>Incorrect API key</li>
38
+ <li>Referer not allowed</li>
39
+ <li>Missing authentication scope</li>
40
+ </ul>
41
+ <p>Check the browser developer tools for more information.</p>
42
+ </Text>
43
+ )
44
+ }
@@ -0,0 +1,93 @@
1
+ // @todo: remove the following line when part imports has been removed from this file
2
+ ///<reference types="@sanity/types/parts" />
3
+
4
+ import {Observable, BehaviorSubject} from 'rxjs'
5
+ import config from 'config:@sanity/google-maps-input'
6
+
7
+ const callbackName = '___sanity_googleMapsApiCallback'
8
+ const authFailureCallbackName = 'gm_authFailure'
9
+ const locale = (typeof window !== 'undefined' && window.navigator.language) || 'en'
10
+
11
+ export interface LoadingState {
12
+ loadState: 'loading'
13
+ }
14
+
15
+ export interface LoadedState {
16
+ loadState: 'loaded'
17
+ api: typeof window.google.maps
18
+ }
19
+
20
+ export interface LoadErrorState {
21
+ loadState: 'loadError'
22
+ error: Error
23
+ }
24
+
25
+ export interface AuthErrorState {
26
+ loadState: 'authError'
27
+ }
28
+
29
+ export type GoogleLoadState = LoadingState | LoadedState | LoadErrorState | AuthErrorState
30
+
31
+ let subject: BehaviorSubject<GoogleLoadState>
32
+
33
+ export function loadGoogleMapsApi(): Observable<GoogleLoadState> {
34
+ const selectedLocale = config.defaultLocale || locale || 'en-US'
35
+
36
+ if (subject) {
37
+ return subject
38
+ }
39
+
40
+ subject = new BehaviorSubject<GoogleLoadState>({loadState: 'loading'})
41
+
42
+ window[authFailureCallbackName] = () => {
43
+ delete window[authFailureCallbackName]
44
+ subject.next({loadState: 'authError'})
45
+ }
46
+
47
+ window[callbackName] = () => {
48
+ delete window[callbackName]
49
+ subject.next({loadState: 'loaded', api: window.google.maps})
50
+ }
51
+
52
+ const script = document.createElement('script')
53
+ script.onerror = (
54
+ event: Event | string,
55
+ source?: string,
56
+ lineno?: number,
57
+ colno?: number,
58
+ error?: Error
59
+ ) =>
60
+ subject.next({
61
+ loadState: 'loadError',
62
+ error: coeerceError(event, error),
63
+ } as LoadErrorState)
64
+
65
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${config.apiKey}&libraries=places&callback=${callbackName}&language=${selectedLocale}`
66
+ document.getElementsByTagName('head')[0].appendChild(script)
67
+
68
+ return subject
69
+ }
70
+
71
+ function coeerceError(event: Event | string, error?: Error): Error {
72
+ if (error) {
73
+ return error
74
+ }
75
+
76
+ if (typeof event === 'string') {
77
+ return new Error(event)
78
+ }
79
+
80
+ return new Error(isErrorEvent(event) ? event.message : 'Failed to load Google Maps API')
81
+ }
82
+
83
+ function isErrorEvent(event: unknown): event is ErrorEvent {
84
+ if (typeof event !== 'object' || event === null) {
85
+ return false
86
+ }
87
+
88
+ if (!('message' in event)) {
89
+ return false
90
+ }
91
+
92
+ return typeof (event as ErrorEvent).message === 'string'
93
+ }