@sanity/google-maps-input 4.1.1 → 4.2.1
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/README.md +61 -3
- package/dist/index.cjs +483 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +484 -74
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/diff/GeopointMove.tsx +2 -2
- package/src/diff/GeopointRadiusFieldDiff.tsx +136 -0
- package/src/diff/GeopointRadiusMove.tsx +92 -0
- package/src/diff/resolver.ts +5 -0
- package/src/index.ts +13 -1
- package/src/input/GeopointRadiusInput.tsx +258 -0
- package/src/input/GeopointRadiusSelect.tsx +193 -0
- package/src/plugin.tsx +57 -1
- package/src/types.ts +17 -0
package/src/index.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
export {GeopointInput, type GeopointInputProps} from './input/GeopointInput'
|
|
2
|
+
export {GeopointRadiusInput, type GeopointRadiusInputProps} from './input/GeopointRadiusInput'
|
|
2
3
|
|
|
3
4
|
export {GeopointArrayDiff, type DiffProps as GeopointArrayDiffProps} from './diff/GeopointArrayDiff'
|
|
4
5
|
export {GeopointFieldDiff, type DiffProps as GeopointFieldDiffProps} from './diff/GeopointFieldDiff'
|
|
6
|
+
export {
|
|
7
|
+
GeopointRadiusFieldDiff,
|
|
8
|
+
type DiffProps as GeopointRadiusFieldDiffProps,
|
|
9
|
+
} from './diff/GeopointRadiusFieldDiff'
|
|
5
10
|
|
|
6
|
-
export type {
|
|
11
|
+
export type {
|
|
12
|
+
LatLng,
|
|
13
|
+
GeopointSchemaType,
|
|
14
|
+
Geopoint,
|
|
15
|
+
GeopointRadius,
|
|
16
|
+
GeopointRadiusSchemaType,
|
|
17
|
+
GoogleMapsInputConfig,
|
|
18
|
+
} from './types'
|
|
7
19
|
|
|
8
20
|
export {googleMapsInput} from './plugin'
|
|
@@ -0,0 +1,258 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
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>(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
|
+
}
|
package/src/plugin.tsx
CHANGED
|
@@ -1,12 +1,60 @@
|
|
|
1
1
|
import {definePlugin, type SchemaType} from 'sanity'
|
|
2
2
|
import {GeopointInput, type GeopointInputProps} from './input/GeopointInput'
|
|
3
|
+
import {GeopointRadiusInput, type GeopointRadiusInputProps} from './input/GeopointRadiusInput'
|
|
3
4
|
import {setGeoConfig} from './global-workaround'
|
|
4
|
-
import type {GeopointSchemaType, GoogleMapsInputConfig} from './types'
|
|
5
|
+
import type {GeopointSchemaType, GeopointRadiusSchemaType, GoogleMapsInputConfig} from './types'
|
|
5
6
|
|
|
6
7
|
export const googleMapsInput = definePlugin<GoogleMapsInputConfig>((config) => {
|
|
7
8
|
setGeoConfig(config)
|
|
8
9
|
return {
|
|
9
10
|
name: 'google-maps-input',
|
|
11
|
+
schema: {
|
|
12
|
+
types: [
|
|
13
|
+
{
|
|
14
|
+
name: 'geopointRadius',
|
|
15
|
+
title: 'Geopoint with Radius',
|
|
16
|
+
type: 'object',
|
|
17
|
+
fields: [
|
|
18
|
+
{
|
|
19
|
+
name: 'lat',
|
|
20
|
+
title: 'Latitude',
|
|
21
|
+
type: 'number',
|
|
22
|
+
validation: (Rule: any) => Rule.required().min(-90).max(90),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'lng',
|
|
26
|
+
title: 'Longitude',
|
|
27
|
+
type: 'number',
|
|
28
|
+
validation: (Rule: any) => Rule.required().min(-180).max(180),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'alt',
|
|
32
|
+
title: 'Altitude',
|
|
33
|
+
type: 'number',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'radius',
|
|
37
|
+
title: 'Radius (meters)',
|
|
38
|
+
type: 'number',
|
|
39
|
+
validation: (Rule: any) => Rule.required().min(1).max(50000),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
preview: {
|
|
43
|
+
select: {
|
|
44
|
+
lat: 'lat',
|
|
45
|
+
lng: 'lng',
|
|
46
|
+
radius: 'radius',
|
|
47
|
+
},
|
|
48
|
+
prepare({lat, lng, radius}: {lat: number; lng: number; radius: number}) {
|
|
49
|
+
return {
|
|
50
|
+
title: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
|
|
51
|
+
subtitle: radius ? `Radius: ${radius}m` : 'No radius set',
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
10
58
|
form: {
|
|
11
59
|
components: {
|
|
12
60
|
input(props) {
|
|
@@ -14,6 +62,10 @@ export const googleMapsInput = definePlugin<GoogleMapsInputConfig>((config) => {
|
|
|
14
62
|
const castedProps = props as unknown as Omit<GeopointInputProps, 'geoConfig'>
|
|
15
63
|
return <GeopointInput {...castedProps} geoConfig={config} />
|
|
16
64
|
}
|
|
65
|
+
if (isGeopointRadius(props.schemaType)) {
|
|
66
|
+
const castedProps = props as unknown as Omit<GeopointRadiusInputProps, 'geoConfig'>
|
|
67
|
+
return <GeopointRadiusInput {...castedProps} geoConfig={config} />
|
|
68
|
+
}
|
|
17
69
|
return props.renderDefault(props)
|
|
18
70
|
},
|
|
19
71
|
},
|
|
@@ -25,6 +77,10 @@ function isGeopoint(schemaType: SchemaType): schemaType is GeopointSchemaType {
|
|
|
25
77
|
return isType('geopoint', schemaType)
|
|
26
78
|
}
|
|
27
79
|
|
|
80
|
+
function isGeopointRadius(schemaType: SchemaType): schemaType is GeopointRadiusSchemaType {
|
|
81
|
+
return isType('geopointRadius', schemaType)
|
|
82
|
+
}
|
|
83
|
+
|
|
28
84
|
function isType(name: string, schema?: SchemaType): boolean {
|
|
29
85
|
if (schema?.name === name) {
|
|
30
86
|
return true
|
package/src/types.ts
CHANGED
|
@@ -14,10 +14,25 @@ export interface Geopoint {
|
|
|
14
14
|
alt?: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface GeopointRadius {
|
|
18
|
+
_type: 'geopointRadius'
|
|
19
|
+
_key?: string
|
|
20
|
+
lat: number
|
|
21
|
+
lng: number
|
|
22
|
+
alt?: number
|
|
23
|
+
radius: number
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export interface GeopointSchemaType extends ObjectSchemaType {
|
|
18
27
|
diffComponent?: DiffComponent<ObjectDiff<Geopoint>> | DiffComponentOptions<ObjectDiff<Geopoint>>
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
export interface GeopointRadiusSchemaType extends ObjectSchemaType {
|
|
31
|
+
diffComponent?:
|
|
32
|
+
| DiffComponent<ObjectDiff<GeopointRadius>>
|
|
33
|
+
| DiffComponentOptions<ObjectDiff<GeopointRadius>>
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export interface GoogleMapsInputConfig {
|
|
22
37
|
apiKey: string
|
|
23
38
|
defaultZoom?: number
|
|
@@ -26,4 +41,6 @@ export interface GoogleMapsInputConfig {
|
|
|
26
41
|
lat: number
|
|
27
42
|
lng: number
|
|
28
43
|
}
|
|
44
|
+
defaultRadiusZoom?: number
|
|
45
|
+
defaultRadius?: number
|
|
29
46
|
}
|