@sanity/google-maps-input 4.2.0 → 5.0.0
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.
- package/LICENSE +1 -1
- package/assets/google-maps-input.png +0 -0
- package/assets/google-maps-radius-input.png +0 -0
- package/dist/index.d.ts +54 -84
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +736 -652
- package/dist/index.js.map +1 -1
- package/package.json +35 -79
- package/dist/index.cjs +0 -1014
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -85
- package/sanity.json +0 -8
- package/src/diff/GeopointArrayDiff.tsx +0 -86
- package/src/diff/GeopointFieldDiff.styles.tsx +0 -20
- package/src/diff/GeopointFieldDiff.tsx +0 -97
- package/src/diff/GeopointMove.tsx +0 -48
- package/src/diff/GeopointRadiusFieldDiff.tsx +0 -136
- package/src/diff/GeopointRadiusMove.tsx +0 -92
- package/src/diff/resolver.ts +0 -26
- package/src/global-workaround.ts +0 -11
- package/src/index.ts +0 -20
- package/src/input/GeopointInput.styles.tsx +0 -11
- package/src/input/GeopointInput.tsx +0 -179
- package/src/input/GeopointRadiusInput.tsx +0 -258
- package/src/input/GeopointRadiusSelect.tsx +0 -193
- package/src/input/GeopointSelect.tsx +0 -79
- package/src/loader/GoogleMapsLoadProxy.tsx +0 -62
- package/src/loader/LoadError.tsx +0 -43
- package/src/loader/loadGoogleMapsApi.ts +0 -77
- package/src/map/Arrow.tsx +0 -76
- package/src/map/Map.styles.tsx +0 -10
- package/src/map/Map.tsx +0 -125
- package/src/map/Marker.tsx +0 -130
- package/src/map/SearchInput.styles.tsx +0 -8
- package/src/map/SearchInput.tsx +0 -56
- package/src/map/util.ts +0 -14
- package/src/plugin.tsx +0 -92
- package/src/types.ts +0 -46
- package/v2-incompatible.js +0 -10
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import React, {useCallback, useEffect, useId, useRef, useState} from 'react'
|
|
2
|
-
import {Box, Button, Dialog, Grid, Stack, TextInput, Label} from '@sanity/ui'
|
|
3
|
-
import {EditIcon, TrashIcon} from '@sanity/icons'
|
|
4
|
-
import {ObjectInputProps, set, setIfMissing, unset, ChangeIndicator, Path} from 'sanity'
|
|
5
|
-
import {GoogleMapsLoadProxy} from '../loader/GoogleMapsLoadProxy'
|
|
6
|
-
import type {
|
|
7
|
-
GeopointRadius,
|
|
8
|
-
GeopointRadiusSchemaType,
|
|
9
|
-
GoogleMapsInputConfig,
|
|
10
|
-
LatLng,
|
|
11
|
-
} from '../types'
|
|
12
|
-
import {getGeoConfig} from '../global-workaround'
|
|
13
|
-
import {DialogInnerContainer, PreviewImage} from './GeopointInput.styles'
|
|
14
|
-
import {GeopointRadiusSelect} from './GeopointRadiusSelect'
|
|
15
|
-
|
|
16
|
-
const EMPTY_PATH: Path = []
|
|
17
|
-
|
|
18
|
-
// Helper function to generate circle points
|
|
19
|
-
const generateCirclePoints = (
|
|
20
|
-
lat: number,
|
|
21
|
-
lng: number,
|
|
22
|
-
radius: number,
|
|
23
|
-
): Array<{lat: number; lng: number}> => {
|
|
24
|
-
const points = []
|
|
25
|
-
const steps = 32 // Number of points to create the circle
|
|
26
|
-
|
|
27
|
-
for (let i = 0; i <= steps; i++) {
|
|
28
|
-
const angle = (i / steps) * 2 * Math.PI
|
|
29
|
-
const latOffset = (radius / 111000) * Math.cos(angle) // Rough conversion to degrees
|
|
30
|
-
const lngOffset = (radius / (111000 * Math.cos((lat * Math.PI) / 180))) * Math.sin(angle)
|
|
31
|
-
|
|
32
|
-
points.push({
|
|
33
|
-
lat: lat + latOffset,
|
|
34
|
-
lng: lng + lngOffset,
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return points
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const getStaticImageUrl = (value: LatLng & {radius?: number}, apiKey: string) => {
|
|
42
|
-
const loc = `${value.lat},${value.lng}`
|
|
43
|
-
|
|
44
|
-
// Calculate appropriate zoom level based on radius
|
|
45
|
-
let zoom = 13
|
|
46
|
-
if (value.radius) {
|
|
47
|
-
// Use logarithmic formula for better zoom calculation
|
|
48
|
-
// Add padding to ensure circle is fully visible
|
|
49
|
-
const radius = value.radius + value.radius / 2
|
|
50
|
-
const scale = radius / 500
|
|
51
|
-
const calculatedZoom = 16 - Math.log(scale) / Math.log(2)
|
|
52
|
-
// Add small offset to ensure circle fits well in view
|
|
53
|
-
zoom = Math.max(8, Math.min(16, Math.round(calculatedZoom - 0.4)))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const qs = new URLSearchParams({
|
|
57
|
-
key: apiKey,
|
|
58
|
-
center: loc,
|
|
59
|
-
markers: loc,
|
|
60
|
-
zoom: zoom.toString(),
|
|
61
|
-
scale: '2',
|
|
62
|
-
size: '640x300',
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Add circle if radius is present
|
|
66
|
-
if (value.radius) {
|
|
67
|
-
// Create a circle path using multiple points
|
|
68
|
-
const points = generateCirclePoints(value.lat, value.lng, value.radius)
|
|
69
|
-
const path = points.map((p) => `${p.lat},${p.lng}`).join('|')
|
|
70
|
-
qs.append('path', `fillcolor:0x4285F480|color:0x4285F4|weight:2|${path}`)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return `https://maps.googleapis.com/maps/api/staticmap?${qs.toString()}`
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type GeopointRadiusInputProps = ObjectInputProps<
|
|
77
|
-
GeopointRadius,
|
|
78
|
-
GeopointRadiusSchemaType
|
|
79
|
-
> & {
|
|
80
|
-
geoConfig: GoogleMapsInputConfig
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function GeopointRadiusInput(props: GeopointRadiusInputProps) {
|
|
84
|
-
const {
|
|
85
|
-
changed,
|
|
86
|
-
elementProps,
|
|
87
|
-
focused,
|
|
88
|
-
geoConfig: config,
|
|
89
|
-
onChange,
|
|
90
|
-
onPathFocus,
|
|
91
|
-
path,
|
|
92
|
-
readOnly,
|
|
93
|
-
schemaType,
|
|
94
|
-
value,
|
|
95
|
-
} = props
|
|
96
|
-
|
|
97
|
-
const {
|
|
98
|
-
id,
|
|
99
|
-
ref: inputRef,
|
|
100
|
-
onBlur: handleBlur,
|
|
101
|
-
onFocus: handleFocus,
|
|
102
|
-
'aria-describedby': ariaDescribedBy,
|
|
103
|
-
} = elementProps
|
|
104
|
-
|
|
105
|
-
const schemaTypeName = schemaType.name
|
|
106
|
-
const dialogId = useId()
|
|
107
|
-
const dialogRef = useRef<HTMLDivElement | null>(null)
|
|
108
|
-
const handleFocusButton = useCallback(() => inputRef?.current?.focus(), [inputRef])
|
|
109
|
-
const [modalOpen, setModalOpen] = useState(false)
|
|
110
|
-
|
|
111
|
-
const handleCloseModal = useCallback(() => {
|
|
112
|
-
if (dialogRef.current) dialogRef.current.blur()
|
|
113
|
-
setModalOpen(false)
|
|
114
|
-
handleFocusButton()
|
|
115
|
-
}, [setModalOpen, handleFocusButton])
|
|
116
|
-
|
|
117
|
-
const handleToggleModal = useCallback(
|
|
118
|
-
() => setModalOpen((currentState) => !currentState),
|
|
119
|
-
[setModalOpen],
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
const handleChange = useCallback(
|
|
123
|
-
(latLng: google.maps.LatLng, radius?: number) => {
|
|
124
|
-
const currentRadius = radius ?? value?.radius ?? config.defaultRadius ?? 1000
|
|
125
|
-
onChange([
|
|
126
|
-
setIfMissing({_type: schemaTypeName}),
|
|
127
|
-
set(latLng.lat(), ['lat']),
|
|
128
|
-
set(latLng.lng(), ['lng']),
|
|
129
|
-
set(currentRadius, ['radius']),
|
|
130
|
-
])
|
|
131
|
-
},
|
|
132
|
-
[schemaTypeName, onChange, value?.radius, config.defaultRadius],
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
const handleRadiusChange = useCallback(
|
|
136
|
-
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
137
|
-
if (value) {
|
|
138
|
-
onChange([set(Math.round(Number(event.currentTarget.value)), ['radius'])])
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
[onChange, value],
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
const handleClear = useCallback(() => {
|
|
145
|
-
onChange(unset())
|
|
146
|
-
}, [onChange])
|
|
147
|
-
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (modalOpen) {
|
|
150
|
-
onPathFocus(EMPTY_PATH)
|
|
151
|
-
}
|
|
152
|
-
}, [modalOpen, onPathFocus])
|
|
153
|
-
|
|
154
|
-
if (!config || !config.apiKey) {
|
|
155
|
-
return (
|
|
156
|
-
<div>
|
|
157
|
-
<p>
|
|
158
|
-
The <a href="https://sanity.io/docs/schema-types/geopoint-type">Geopoint Radius type</a>{' '}
|
|
159
|
-
needs a Google Maps API key with access to:
|
|
160
|
-
</p>
|
|
161
|
-
<ul>
|
|
162
|
-
<li>Google Maps JavaScript API</li>
|
|
163
|
-
<li>Google Places API Web Service</li>
|
|
164
|
-
<li>Google Static Maps API</li>
|
|
165
|
-
</ul>
|
|
166
|
-
<p>
|
|
167
|
-
Please enter the API key with access to these services in your googleMapsInput plugin
|
|
168
|
-
config.
|
|
169
|
-
</p>
|
|
170
|
-
</div>
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<Stack space={3}>
|
|
176
|
-
{value && (
|
|
177
|
-
<ChangeIndicator path={path} isChanged={changed} hasFocus={!!focused}>
|
|
178
|
-
<PreviewImage
|
|
179
|
-
src={getStaticImageUrl(value, config.apiKey)}
|
|
180
|
-
alt="Map location with radius"
|
|
181
|
-
onClick={handleFocusButton}
|
|
182
|
-
onDoubleClick={handleToggleModal}
|
|
183
|
-
/>
|
|
184
|
-
</ChangeIndicator>
|
|
185
|
-
)}
|
|
186
|
-
|
|
187
|
-
{value && (
|
|
188
|
-
<Stack space={2}>
|
|
189
|
-
<Label>Radius (meters)</Label>
|
|
190
|
-
<TextInput
|
|
191
|
-
type="number"
|
|
192
|
-
value={Math.round(value.radius || config.defaultRadius || 1000)}
|
|
193
|
-
onChange={handleRadiusChange}
|
|
194
|
-
disabled={readOnly}
|
|
195
|
-
min={1}
|
|
196
|
-
max={50000}
|
|
197
|
-
step={1}
|
|
198
|
-
/>
|
|
199
|
-
</Stack>
|
|
200
|
-
)}
|
|
201
|
-
|
|
202
|
-
<Box>
|
|
203
|
-
<Grid columns={value ? 2 : 1} gap={3}>
|
|
204
|
-
<Button
|
|
205
|
-
aria-describedby={ariaDescribedBy}
|
|
206
|
-
disabled={readOnly}
|
|
207
|
-
icon={value && EditIcon}
|
|
208
|
-
id={id}
|
|
209
|
-
mode="ghost"
|
|
210
|
-
onClick={handleToggleModal}
|
|
211
|
-
onFocus={handleFocus}
|
|
212
|
-
padding={3}
|
|
213
|
-
ref={inputRef}
|
|
214
|
-
text={value ? 'Edit' : 'Set location and radius'}
|
|
215
|
-
/>
|
|
216
|
-
|
|
217
|
-
{value && (
|
|
218
|
-
<Button
|
|
219
|
-
disabled={readOnly}
|
|
220
|
-
icon={TrashIcon}
|
|
221
|
-
mode="ghost"
|
|
222
|
-
onClick={handleClear}
|
|
223
|
-
padding={3}
|
|
224
|
-
text="Remove"
|
|
225
|
-
tone="critical"
|
|
226
|
-
/>
|
|
227
|
-
)}
|
|
228
|
-
</Grid>
|
|
229
|
-
</Box>
|
|
230
|
-
|
|
231
|
-
{modalOpen && (
|
|
232
|
-
<Dialog
|
|
233
|
-
header="Place the marker and set radius on the map"
|
|
234
|
-
id={`${dialogId}_dialog`}
|
|
235
|
-
onBlur={handleBlur}
|
|
236
|
-
onClose={handleCloseModal}
|
|
237
|
-
ref={dialogRef}
|
|
238
|
-
width={1}
|
|
239
|
-
>
|
|
240
|
-
<DialogInnerContainer>
|
|
241
|
-
<GoogleMapsLoadProxy config={getGeoConfig()}>
|
|
242
|
-
{(api) => (
|
|
243
|
-
<GeopointRadiusSelect
|
|
244
|
-
api={api}
|
|
245
|
-
value={value || undefined}
|
|
246
|
-
onChange={readOnly ? undefined : handleChange}
|
|
247
|
-
defaultLocation={config.defaultLocation}
|
|
248
|
-
defaultRadiusZoom={config.defaultRadiusZoom}
|
|
249
|
-
defaultRadius={config.defaultRadius}
|
|
250
|
-
/>
|
|
251
|
-
)}
|
|
252
|
-
</GoogleMapsLoadProxy>
|
|
253
|
-
</DialogInnerContainer>
|
|
254
|
-
</Dialog>
|
|
255
|
-
)}
|
|
256
|
-
</Stack>
|
|
257
|
-
)
|
|
258
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import React, {type FC, useCallback, useEffect, useRef} from 'react'
|
|
2
|
-
import {SearchInput} from '../map/SearchInput'
|
|
3
|
-
import {GoogleMap} from '../map/Map'
|
|
4
|
-
import {Marker} from '../map/Marker'
|
|
5
|
-
import type {LatLng, GeopointRadius} from '../types'
|
|
6
|
-
|
|
7
|
-
const fallbackLatLng: LatLng = {lat: 40.7058254, lng: -74.1180863}
|
|
8
|
-
|
|
9
|
-
// Component to sync marker drag with circle position
|
|
10
|
-
const MarkerDragSync: FC<{
|
|
11
|
-
api: typeof window.google.maps
|
|
12
|
-
marker: google.maps.Marker
|
|
13
|
-
circleRef: React.MutableRefObject<google.maps.Circle | null>
|
|
14
|
-
isMarkerDragging: React.MutableRefObject<boolean>
|
|
15
|
-
}> = ({api, marker, circleRef, isMarkerDragging}) => {
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const handleDrag = () => {
|
|
18
|
-
isMarkerDragging.current = true
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const handleDragEnd = () => {
|
|
22
|
-
isMarkerDragging.current = false
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const dragListener = api.event.addListener(marker, 'drag', handleDrag)
|
|
26
|
-
const dragEndListener = api.event.addListener(marker, 'dragend', handleDragEnd)
|
|
27
|
-
|
|
28
|
-
return () => {
|
|
29
|
-
api.event.removeListener(dragListener)
|
|
30
|
-
api.event.removeListener(dragEndListener)
|
|
31
|
-
}
|
|
32
|
-
}, [api, marker, circleRef, isMarkerDragging])
|
|
33
|
-
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface SelectProps {
|
|
38
|
-
api: typeof window.google.maps
|
|
39
|
-
value?: GeopointRadius
|
|
40
|
-
onChange?: (latLng: google.maps.LatLng, radius?: number) => void
|
|
41
|
-
defaultLocation?: LatLng
|
|
42
|
-
defaultRadiusZoom?: number
|
|
43
|
-
defaultRadius?: number
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const GeopointRadiusSelect: FC<SelectProps> = ({
|
|
47
|
-
api,
|
|
48
|
-
value,
|
|
49
|
-
onChange,
|
|
50
|
-
defaultLocation = {lng: 10.74609, lat: 59.91273},
|
|
51
|
-
defaultRadiusZoom = 12,
|
|
52
|
-
defaultRadius = 1000,
|
|
53
|
-
}) => {
|
|
54
|
-
const circleRef = useRef<google.maps.Circle | null>(null)
|
|
55
|
-
const markerRef = useRef<google.maps.Marker | undefined>()
|
|
56
|
-
const isMarkerDragging = useRef(false)
|
|
57
|
-
|
|
58
|
-
const getCenter = useCallback(() => {
|
|
59
|
-
const point: LatLng = {...fallbackLatLng, ...defaultLocation, ...value}
|
|
60
|
-
return point
|
|
61
|
-
}, [value, defaultLocation])
|
|
62
|
-
|
|
63
|
-
const setValue = useCallback(
|
|
64
|
-
(geoPoint: google.maps.LatLng, radius?: number) => {
|
|
65
|
-
if (onChange) {
|
|
66
|
-
const roundedRadius = radius ? Math.round(radius) : undefined
|
|
67
|
-
onChange(geoPoint, roundedRadius)
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
[onChange],
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
const handlePlaceChanged = useCallback(
|
|
74
|
-
(place: google.maps.places.PlaceResult) => {
|
|
75
|
-
if (!place.geometry?.location) {
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
setValue(place.geometry.location, value?.radius || defaultRadius)
|
|
79
|
-
},
|
|
80
|
-
[setValue, value?.radius, defaultRadius],
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const handleMarkerDragEnd = useCallback(
|
|
84
|
-
(event: google.maps.MapMouseEvent) => {
|
|
85
|
-
if (event.latLng) {
|
|
86
|
-
// Update circle position when marker drag ends
|
|
87
|
-
if (circleRef.current) {
|
|
88
|
-
circleRef.current.setCenter(event.latLng)
|
|
89
|
-
}
|
|
90
|
-
setValue(event.latLng, value?.radius || defaultRadius)
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
[setValue, value?.radius, defaultRadius],
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
const handleMapClick = useCallback(
|
|
97
|
-
(event: google.maps.MapMouseEvent) => {
|
|
98
|
-
if (event.latLng) {
|
|
99
|
-
setValue(event.latLng, value?.radius || defaultRadius)
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
[setValue, value?.radius, defaultRadius],
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
// Create or update circle when value changes
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (value && circleRef.current) {
|
|
108
|
-
circleRef.current.setCenter({lat: value.lat, lng: value.lng})
|
|
109
|
-
circleRef.current.setRadius(value.radius)
|
|
110
|
-
}
|
|
111
|
-
}, [value])
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<GoogleMap
|
|
115
|
-
api={api}
|
|
116
|
-
location={getCenter()}
|
|
117
|
-
onClick={handleMapClick}
|
|
118
|
-
defaultZoom={defaultRadiusZoom}
|
|
119
|
-
>
|
|
120
|
-
{(map) => {
|
|
121
|
-
// Create circle if it doesn't exist and we have a value
|
|
122
|
-
if (value && !circleRef.current) {
|
|
123
|
-
circleRef.current = new api.Circle({
|
|
124
|
-
map,
|
|
125
|
-
center: {lat: value.lat, lng: value.lng},
|
|
126
|
-
radius: value.radius,
|
|
127
|
-
fillColor: '#4285F4',
|
|
128
|
-
fillOpacity: 0.2,
|
|
129
|
-
strokeColor: '#4285F4',
|
|
130
|
-
strokeOpacity: 0.8,
|
|
131
|
-
strokeWeight: 2,
|
|
132
|
-
editable: true,
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
// Add event listeners for circle interactions
|
|
136
|
-
circleRef.current.addListener('center_changed', () => {
|
|
137
|
-
if (circleRef.current && markerRef.current && !isMarkerDragging.current) {
|
|
138
|
-
// When circle center is dragged, move the marker to match
|
|
139
|
-
const circleCenter = circleRef.current.getCenter()
|
|
140
|
-
if (circleCenter) {
|
|
141
|
-
markerRef.current.setPosition(circleCenter)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
circleRef.current.addListener('radius_changed', () => {
|
|
147
|
-
if (circleRef.current) {
|
|
148
|
-
const center = circleRef.current.getCenter()
|
|
149
|
-
const radius = circleRef.current.getRadius()
|
|
150
|
-
if (center) {
|
|
151
|
-
setValue(center, Math.round(radius))
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
circleRef.current.addListener('dragend', () => {
|
|
157
|
-
if (circleRef.current) {
|
|
158
|
-
const center = circleRef.current.getCenter()
|
|
159
|
-
const radius = circleRef.current.getRadius()
|
|
160
|
-
if (center) {
|
|
161
|
-
setValue(center, Math.round(radius))
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<>
|
|
169
|
-
<SearchInput api={api} map={map} onChange={handlePlaceChanged} />
|
|
170
|
-
{value && (
|
|
171
|
-
<Marker
|
|
172
|
-
api={api}
|
|
173
|
-
map={map}
|
|
174
|
-
position={value}
|
|
175
|
-
onMove={onChange ? handleMarkerDragEnd : undefined}
|
|
176
|
-
markerRef={markerRef}
|
|
177
|
-
/>
|
|
178
|
-
)}
|
|
179
|
-
{/* Add drag event listener to marker for circle sync */}
|
|
180
|
-
{value && markerRef.current && (
|
|
181
|
-
<MarkerDragSync
|
|
182
|
-
api={api}
|
|
183
|
-
marker={markerRef.current}
|
|
184
|
-
circleRef={circleRef}
|
|
185
|
-
isMarkerDragging={isMarkerDragging}
|
|
186
|
-
/>
|
|
187
|
-
)}
|
|
188
|
-
</>
|
|
189
|
-
)
|
|
190
|
-
}}
|
|
191
|
-
</GoogleMap>
|
|
192
|
-
)
|
|
193
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import {type FC, useCallback} from 'react'
|
|
2
|
-
import {SearchInput} from '../map/SearchInput'
|
|
3
|
-
import {GoogleMap} from '../map/Map'
|
|
4
|
-
import {Marker} from '../map/Marker'
|
|
5
|
-
import type {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 const GeopointSelect: FC<SelectProps> = ({
|
|
18
|
-
api,
|
|
19
|
-
value,
|
|
20
|
-
onChange,
|
|
21
|
-
defaultLocation = {lng: 10.74609, lat: 59.91273},
|
|
22
|
-
defaultZoom = 8,
|
|
23
|
-
}) => {
|
|
24
|
-
const getCenter = useCallback(() => {
|
|
25
|
-
const point: LatLng = {...fallbackLatLng, ...defaultLocation, ...value}
|
|
26
|
-
return point
|
|
27
|
-
}, [value, defaultLocation])
|
|
28
|
-
|
|
29
|
-
const setValue = useCallback(
|
|
30
|
-
(geoPoint: google.maps.LatLng) => {
|
|
31
|
-
if (onChange) {
|
|
32
|
-
onChange(geoPoint)
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
[onChange],
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const handlePlaceChanged = useCallback(
|
|
39
|
-
(place: google.maps.places.PlaceResult) => {
|
|
40
|
-
if (!place.geometry?.location) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
setValue(place.geometry.location)
|
|
44
|
-
},
|
|
45
|
-
[setValue],
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
const handleMarkerDragEnd = useCallback(
|
|
49
|
-
(event: google.maps.MapMouseEvent) => {
|
|
50
|
-
if (event.latLng) setValue(event.latLng)
|
|
51
|
-
},
|
|
52
|
-
[setValue],
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const handleMapClick = useCallback(
|
|
56
|
-
(event: google.maps.MapMouseEvent) => {
|
|
57
|
-
if (event.latLng) setValue(event.latLng)
|
|
58
|
-
},
|
|
59
|
-
[setValue],
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<GoogleMap api={api} location={getCenter()} onClick={handleMapClick} defaultZoom={defaultZoom}>
|
|
64
|
-
{(map) => (
|
|
65
|
-
<>
|
|
66
|
-
<SearchInput api={api} map={map} onChange={handlePlaceChanged} />
|
|
67
|
-
{value && (
|
|
68
|
-
<Marker
|
|
69
|
-
api={api}
|
|
70
|
-
map={map}
|
|
71
|
-
position={value}
|
|
72
|
-
onMove={onChange ? handleMarkerDragEnd : undefined}
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
75
|
-
</>
|
|
76
|
-
)}
|
|
77
|
-
</GoogleMap>
|
|
78
|
-
)
|
|
79
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import {type ReactElement, useEffect, useState} from 'react'
|
|
2
|
-
import type {GoogleMapsInputConfig} from '../types'
|
|
3
|
-
import {AuthError, loadGoogleMapsApi} from './loadGoogleMapsApi'
|
|
4
|
-
import {LoadError as LoadErrorView} from './LoadError'
|
|
5
|
-
|
|
6
|
-
interface LoadProps {
|
|
7
|
-
children: (api: typeof window.google.maps) => ReactElement
|
|
8
|
-
config: GoogleMapsInputConfig
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const browserLocale = (typeof window !== 'undefined' && window.navigator.language) || 'en'
|
|
12
|
-
|
|
13
|
-
type LoadState =
|
|
14
|
-
| {
|
|
15
|
-
type: 'loading'
|
|
16
|
-
}
|
|
17
|
-
| {
|
|
18
|
-
type: 'loaded'
|
|
19
|
-
api: typeof window.google.maps
|
|
20
|
-
}
|
|
21
|
-
| {
|
|
22
|
-
type: 'error'
|
|
23
|
-
error: {type: 'loadError' | 'authError'; message: string}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function useLoadGoogleMapsApi(config: {defaultLocale?: string; apiKey: string}): LoadState {
|
|
27
|
-
const locale = config.defaultLocale || browserLocale || 'en-US'
|
|
28
|
-
|
|
29
|
-
const [state, setState] = useState<LoadState>({type: 'loading'})
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (typeof window === 'undefined') {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
loadGoogleMapsApi({locale, apiKey: config.apiKey}).then(
|
|
37
|
-
(api) => setState({type: 'loaded', api}),
|
|
38
|
-
(err) =>
|
|
39
|
-
setState({
|
|
40
|
-
type: 'error',
|
|
41
|
-
error: {type: err instanceof AuthError ? 'authError' : 'loadError', message: err.message},
|
|
42
|
-
}),
|
|
43
|
-
)
|
|
44
|
-
}, [locale, config.apiKey])
|
|
45
|
-
return state
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function GoogleMapsLoadProxy(props: LoadProps) {
|
|
49
|
-
const loadState = useLoadGoogleMapsApi(props.config)
|
|
50
|
-
switch (loadState.type) {
|
|
51
|
-
case 'error':
|
|
52
|
-
return (
|
|
53
|
-
<LoadErrorView error={loadState.error} isAuthError={loadState.error.type === 'authError'} />
|
|
54
|
-
)
|
|
55
|
-
case 'loading':
|
|
56
|
-
return <div>Loading Google Maps API</div>
|
|
57
|
-
case 'loaded':
|
|
58
|
-
return props.children(loadState.api)
|
|
59
|
-
default:
|
|
60
|
-
return null
|
|
61
|
-
}
|
|
62
|
-
}
|
package/src/loader/LoadError.tsx
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {Card, Box, Text, Code} from '@sanity/ui'
|
|
2
|
-
|
|
3
|
-
type Props = {error: {message?: string}; isAuthError: false} | {isAuthError: true}
|
|
4
|
-
|
|
5
|
-
export function LoadError(props: Props) {
|
|
6
|
-
return (
|
|
7
|
-
<Card tone="critical" radius={1}>
|
|
8
|
-
<Box as="header" paddingX={4} paddingTop={4} paddingBottom={1}>
|
|
9
|
-
<Text as="h2" weight="bold">
|
|
10
|
-
Google Maps failed to load
|
|
11
|
-
</Text>
|
|
12
|
-
</Box>
|
|
13
|
-
|
|
14
|
-
<Box paddingX={4} paddingTop={4} paddingBottom={1}>
|
|
15
|
-
{props.isAuthError ? (
|
|
16
|
-
<AuthError />
|
|
17
|
-
) : (
|
|
18
|
-
<>
|
|
19
|
-
<Text as="h3">Error details:</Text>
|
|
20
|
-
<pre>
|
|
21
|
-
<Code size={1}>{'error' in props && props.error?.message}</Code>
|
|
22
|
-
</pre>
|
|
23
|
-
</>
|
|
24
|
-
)}
|
|
25
|
-
</Box>
|
|
26
|
-
</Card>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function AuthError() {
|
|
31
|
-
return (
|
|
32
|
-
<Text>
|
|
33
|
-
<p>The error appears to be related to authentication</p>
|
|
34
|
-
<p>Common causes include:</p>
|
|
35
|
-
<ul>
|
|
36
|
-
<li>Incorrect API key</li>
|
|
37
|
-
<li>Referer not allowed</li>
|
|
38
|
-
<li>Missing authentication scope</li>
|
|
39
|
-
</ul>
|
|
40
|
-
<p>Check the browser developer tools for more information.</p>
|
|
41
|
-
</Text>
|
|
42
|
-
)
|
|
43
|
-
}
|