@dxos/react-ui-geo 0.8.3 → 0.8.4-main.28f8d3d
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/data/airports.ts +1 -1
- package/data/cities.ts +1 -1
- package/data/countries-110m.ts +1 -1
- package/data/countries-dots-3.ts +1 -1
- package/data/countries-dots-4.ts +1 -1
- package/dist/lib/browser/chunk-GMWLKTLN.mjs +9 -0
- package/dist/lib/browser/{countries-110m-WI4PCLDF.mjs → countries-110m-ZM3ZIEFS.mjs} +2 -2
- package/dist/lib/browser/countries-110m-ZM3ZIEFS.mjs.map +7 -0
- package/dist/lib/browser/data.mjs +1 -1
- package/dist/lib/browser/index.mjs +187 -159
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/{chunk-PIIEDZEU.mjs → chunk-JODBF4CC.mjs} +3 -3
- package/dist/lib/node-esm/{countries-110m-DQ4XRC4B.mjs → countries-110m-3SFASWVD.mjs} +2 -2
- package/dist/lib/node-esm/countries-110m-3SFASWVD.mjs.map +7 -0
- package/dist/lib/node-esm/data.mjs +1 -1
- package/dist/lib/node-esm/index.mjs +187 -159
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.stories.d.ts +1 -1
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.d.ts +25 -12
- package/dist/types/src/components/Map/Map.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.stories.d.ts +3 -3
- package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/context.d.ts +7 -7
- package/dist/types/src/hooks/context.d.ts.map +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useSpinner.d.ts +1 -1
- package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
- package/dist/types/src/hooks/useTour.d.ts +4 -3
- package/dist/types/src/hooks/useTour.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +2 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/path.d.ts +5 -8
- package/dist/types/src/util/path.d.ts.map +1 -1
- package/dist/types/src/util/render.d.ts +4 -4
- package/dist/types/src/util/render.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -11
- package/src/components/Globe/Globe.stories.tsx +29 -24
- package/src/components/Globe/Globe.tsx +74 -58
- package/src/components/Map/Map.stories.tsx +16 -7
- package/src/components/Map/Map.tsx +206 -91
- package/src/components/Toolbar/Controls.tsx +2 -6
- package/src/components/index.ts +0 -2
- package/src/hooks/context.tsx +10 -10
- package/src/hooks/useGlobeZoomHandler.ts +3 -3
- package/src/hooks/useMapZoomHandler.ts +1 -1
- package/src/hooks/useSpinner.ts +2 -1
- package/src/hooks/useTour.ts +9 -8
- package/src/index.ts +1 -1
- package/src/types.ts +3 -1
- package/src/util/inertia.ts +1 -1
- package/src/util/path.ts +5 -6
- package/src/util/render.ts +5 -3
- package/dist/lib/browser/chunk-ENCWOTYX.mjs +0 -9
- package/dist/lib/browser/countries-110m-WI4PCLDF.mjs.map +0 -7
- package/dist/lib/node/chunk-LAICG6L2.cjs +0 -40
- package/dist/lib/node/chunk-LAICG6L2.cjs.map +0 -7
- package/dist/lib/node/countries-110m-KQ5WAB2O.cjs +0 -37877
- package/dist/lib/node/countries-110m-KQ5WAB2O.cjs.map +0 -7
- package/dist/lib/node/data.cjs +0 -28
- package/dist/lib/node/data.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -1187
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node-esm/countries-110m-DQ4XRC4B.mjs.map +0 -7
- package/dist/types/src/components/types.d.ts +0 -15
- package/dist/types/src/components/types.d.ts.map +0 -1
- package/src/components/types.ts +0 -19
- /package/dist/lib/browser/{chunk-ENCWOTYX.mjs.map → chunk-GMWLKTLN.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-PIIEDZEU.mjs.map → chunk-JODBF4CC.mjs.map} +0 -0
|
@@ -2,109 +2,223 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
// eslint-disable-next-line no-restricted-imports
|
|
6
5
|
import 'leaflet/dist/leaflet.css';
|
|
7
6
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
7
|
+
import { createContext } from '@radix-ui/react-context';
|
|
8
|
+
import L, { Control, type ControlPosition, DomEvent, DomUtil, type LatLngLiteral, latLngBounds } from 'leaflet';
|
|
9
|
+
import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
10
10
|
import { createRoot } from 'react-dom/client';
|
|
11
11
|
import type { MapContainerProps } from 'react-leaflet';
|
|
12
12
|
import { MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet';
|
|
13
|
-
import { useResizeDetector } from 'react-resize-detector';
|
|
14
13
|
|
|
15
14
|
import { debounce } from '@dxos/async';
|
|
16
|
-
import { ThemeProvider,
|
|
15
|
+
import { ThemeProvider, type ThemedClassName, Tooltip } from '@dxos/react-ui';
|
|
17
16
|
import { defaultTx, mx } from '@dxos/react-ui-theme';
|
|
18
17
|
|
|
19
|
-
import {
|
|
20
|
-
import { type
|
|
18
|
+
import { type GeoMarker } from '../../types';
|
|
19
|
+
import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
|
|
21
20
|
|
|
22
21
|
// TODO(burdon): Explore plugins: https://www.npmjs.com/search?q=keywords%3Areact-leaflet-v4
|
|
23
22
|
// TODO(burdon): react-leaflet v5 is not compatible with react 18.
|
|
23
|
+
// TODO(burdon): Guess initial location.
|
|
24
24
|
|
|
25
25
|
const defaults = {
|
|
26
|
-
|
|
27
|
-
center: { lat: 51, lng: 0 } as L.LatLngExpression,
|
|
26
|
+
center: { lat: 51, lng: 0 } as L.LatLngLiteral,
|
|
28
27
|
zoom: 4,
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
//
|
|
32
|
-
//
|
|
31
|
+
// Controller
|
|
33
32
|
//
|
|
34
33
|
|
|
35
|
-
type
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const MapRoot = ({ classNames, center = defaults.center, zoom = defaults.zoom, ...props }: MapRootProps) => {
|
|
39
|
-
return (
|
|
40
|
-
<MapContainer
|
|
41
|
-
className={mx('relative grid grow bg-baseSurface', classNames)}
|
|
42
|
-
attributionControl={false}
|
|
43
|
-
// TODO(burdon): Only if attention.
|
|
44
|
-
scrollWheelZoom={true}
|
|
45
|
-
zoomControl={false}
|
|
46
|
-
center={center}
|
|
47
|
-
zoom={zoom}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
34
|
+
type MapController = {
|
|
35
|
+
setCenter: (center: LatLngLiteral, zoom?: number) => void;
|
|
36
|
+
setZoom: (cb: (zoom: number) => number) => void;
|
|
51
37
|
};
|
|
52
38
|
|
|
53
39
|
//
|
|
54
|
-
//
|
|
40
|
+
// Context
|
|
55
41
|
//
|
|
56
42
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
setCenter: (center: LatLngExpression, zoom?: number) => void;
|
|
60
|
-
setZoom: (cb: (zoom: number) => number) => void;
|
|
43
|
+
type MapContextValue = {
|
|
44
|
+
attention?: boolean;
|
|
61
45
|
};
|
|
62
46
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
const [MapContextProvier, useMapContext] = createContext<MapContextValue>('Map');
|
|
48
|
+
|
|
49
|
+
//
|
|
50
|
+
// Root
|
|
51
|
+
//
|
|
52
|
+
|
|
53
|
+
type MapRootProps = ThemedClassName<
|
|
54
|
+
MapContainerProps & {
|
|
55
|
+
onChange?: (ev: { center: LatLngLiteral; zoom: number }) => void;
|
|
56
|
+
}
|
|
57
|
+
>;
|
|
66
58
|
|
|
67
|
-
|
|
59
|
+
/**
|
|
60
|
+
* https://react-leaflet.js.org/docs/api-map
|
|
61
|
+
*/
|
|
62
|
+
const MapRoot = forwardRef<MapController, MapRootProps>(
|
|
63
|
+
(
|
|
64
|
+
{
|
|
65
|
+
classNames,
|
|
66
|
+
scrollWheelZoom = true,
|
|
67
|
+
doubleClickZoom = true,
|
|
68
|
+
touchZoom = true,
|
|
69
|
+
center = defaults.center,
|
|
70
|
+
zoom = defaults.zoom,
|
|
71
|
+
onChange,
|
|
72
|
+
...props
|
|
73
|
+
},
|
|
68
74
|
forwardedRef,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setZoom: (cb) => {
|
|
74
|
-
map.setZoom(cb(map.getZoom()));
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
[map],
|
|
78
|
-
);
|
|
75
|
+
) => {
|
|
76
|
+
const [attention, setAttention] = useState(false);
|
|
77
|
+
const mapRef = useRef<L.Map>(null);
|
|
78
|
+
const map = mapRef.current;
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
useImperativeHandle(
|
|
81
|
+
forwardedRef,
|
|
82
|
+
() => ({
|
|
83
|
+
setCenter: (center: LatLngLiteral, zoom?: number) => {
|
|
84
|
+
mapRef.current?.setView(center, zoom);
|
|
85
|
+
},
|
|
86
|
+
setZoom: (cb: (zoom: number) => number) => {
|
|
87
|
+
mapRef.current?.setZoom(cb(mapRef.current?.getZoom() ?? 0));
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
[],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Events.
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!map) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const handler = debounce(() => {
|
|
100
|
+
setAttention(true);
|
|
101
|
+
onChange?.({
|
|
102
|
+
center: map.getCenter(),
|
|
103
|
+
zoom: map.getZoom(),
|
|
104
|
+
});
|
|
105
|
+
}, 100);
|
|
106
|
+
|
|
107
|
+
map.on('move', handler);
|
|
108
|
+
map.on('zoom', handler);
|
|
109
|
+
map.on('focus', () => setAttention(true));
|
|
110
|
+
map.on('blur', () => setAttention(false));
|
|
111
|
+
return () => {
|
|
112
|
+
map.off('move');
|
|
113
|
+
map.off('zoom');
|
|
114
|
+
map.off('focus');
|
|
115
|
+
map.off('blur');
|
|
116
|
+
};
|
|
117
|
+
}, [map, onChange]);
|
|
118
|
+
|
|
119
|
+
// Enable/disable scroll wheel zoom.
|
|
120
|
+
// TODO(burdon): Use attention:
|
|
121
|
+
// const {hasAttention} = useAttention(props.id);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!map) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (attention) {
|
|
128
|
+
map.scrollWheelZoom.enable();
|
|
129
|
+
} else {
|
|
130
|
+
map.scrollWheelZoom.disable();
|
|
131
|
+
}
|
|
132
|
+
}, [map, attention]);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<MapContextProvier attention={attention}>
|
|
136
|
+
<MapContainer
|
|
137
|
+
{...props}
|
|
138
|
+
ref={mapRef}
|
|
139
|
+
className={mx('group relative grid bs-full is-full !bg-baseSurface dx-focus-ring-inset', classNames)}
|
|
140
|
+
attributionControl={false}
|
|
141
|
+
zoomControl={false}
|
|
142
|
+
scrollWheelZoom={scrollWheelZoom}
|
|
143
|
+
doubleClickZoom={doubleClickZoom}
|
|
144
|
+
touchZoom={touchZoom}
|
|
145
|
+
center={center}
|
|
146
|
+
zoom={zoom}
|
|
147
|
+
// whenReady={() => {}}
|
|
148
|
+
/>
|
|
149
|
+
</MapContextProvier>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
MapRoot.displayName = 'Map.Root';
|
|
155
|
+
|
|
156
|
+
//
|
|
157
|
+
// Tiles
|
|
158
|
+
// https://react-leaflet.js.org/docs/api-components/#tilelayer
|
|
159
|
+
//
|
|
160
|
+
|
|
161
|
+
type MapTilesProps = {};
|
|
86
162
|
|
|
87
|
-
|
|
163
|
+
const MapTiles = (_props: MapTilesProps) => {
|
|
164
|
+
const ref = useRef<L.TileLayer>(null);
|
|
165
|
+
|
|
166
|
+
// NOTE: Need to dynamically update data attribute since TileLayer doesn't update, but
|
|
167
|
+
// Tailwind requires setting the property for static analysis.
|
|
168
|
+
const { attention } = useMapContext(MapTiles.displayName);
|
|
88
169
|
useEffect(() => {
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
} else if (zoom !== undefined) {
|
|
92
|
-
map.setZoom(zoom);
|
|
170
|
+
if (ref.current) {
|
|
171
|
+
ref.current.getContainer().dataset.attention = attention ? '1' : '0';
|
|
93
172
|
}
|
|
94
|
-
}, [
|
|
173
|
+
}, [attention]);
|
|
95
174
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
175
|
+
// TODO(burdon): Option to add class 'invert'.
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
<TileLayer
|
|
179
|
+
ref={ref}
|
|
180
|
+
data-attention={attention}
|
|
181
|
+
detectRetina={true}
|
|
182
|
+
className='dark:grayscale dark:invert data-[attention="0"]:!opacity-80'
|
|
183
|
+
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
|
184
|
+
keepBuffer={4}
|
|
185
|
+
// opacity={attention ? 1 : 0.7}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
{/* Temperature map. */}
|
|
189
|
+
{/* <WMSTileLayer
|
|
190
|
+
url='https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi'
|
|
191
|
+
layers='MODIS_Terra_Land_Surface_Temp_Day'
|
|
192
|
+
format='image/png'
|
|
193
|
+
transparent={true}
|
|
194
|
+
version='1.3.0'
|
|
195
|
+
attribution='NASA GIBS'
|
|
196
|
+
/> */}
|
|
197
|
+
|
|
198
|
+
{/* US Weather. */}
|
|
199
|
+
{/* <WMSTileLayer
|
|
200
|
+
url='https://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi'
|
|
201
|
+
layers='nexrad-n0r' // layers='nexrad-n0r'
|
|
202
|
+
format='image/png'
|
|
203
|
+
transparent={true}
|
|
204
|
+
/> */}
|
|
205
|
+
</>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
MapTiles.displayName = 'Map.Tiles';
|
|
210
|
+
|
|
211
|
+
//
|
|
212
|
+
// Markers
|
|
213
|
+
//
|
|
214
|
+
|
|
215
|
+
type MapMarkersProps = {
|
|
216
|
+
markers?: GeoMarker[];
|
|
217
|
+
selected?: string[];
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const MapMarkers = ({ selected, markers }: MapMarkersProps) => {
|
|
221
|
+
const map = useMap();
|
|
108
222
|
|
|
109
223
|
// Set the viewport around the markers, or show the whole world map if `markers` is empty.
|
|
110
224
|
useEffect(() => {
|
|
@@ -117,14 +231,7 @@ const MapCanvas = forwardRef<MapController, MapCanvasProps>(({ markers, center,
|
|
|
117
231
|
}, [markers]);
|
|
118
232
|
|
|
119
233
|
return (
|
|
120
|
-
|
|
121
|
-
{/* Map tiles. */}
|
|
122
|
-
<TileLayer
|
|
123
|
-
className='dark:filter dark:grayscale dark:invert'
|
|
124
|
-
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
|
125
|
-
/>
|
|
126
|
-
|
|
127
|
-
{/* Markers. */}
|
|
234
|
+
<>
|
|
128
235
|
{markers?.map(({ id, title, location: { lat, lng } }) => {
|
|
129
236
|
return (
|
|
130
237
|
<Marker
|
|
@@ -132,6 +239,7 @@ const MapCanvas = forwardRef<MapController, MapCanvasProps>(({ markers, center,
|
|
|
132
239
|
position={{ lat, lng }}
|
|
133
240
|
icon={
|
|
134
241
|
// TODO(burdon): Create custom icon from bundled assets.
|
|
242
|
+
// TODO(burdon): Selection state.
|
|
135
243
|
new L.Icon({
|
|
136
244
|
iconUrl: 'https://dxos.network/marker-icon.png',
|
|
137
245
|
iconRetinaUrl: 'https://dxos.network/marker-icon-2x.png',
|
|
@@ -147,9 +255,11 @@ const MapCanvas = forwardRef<MapController, MapCanvasProps>(({ markers, center,
|
|
|
147
255
|
</Marker>
|
|
148
256
|
);
|
|
149
257
|
})}
|
|
150
|
-
|
|
258
|
+
</>
|
|
151
259
|
);
|
|
152
|
-
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
MapMarkers.displayName = 'Map.Markers';
|
|
153
263
|
|
|
154
264
|
//
|
|
155
265
|
// Controls
|
|
@@ -192,23 +302,28 @@ const CustomControl = ({
|
|
|
192
302
|
|
|
193
303
|
type MapControlProps = { position?: ControlPosition } & Pick<ControlProps, 'onAction'>;
|
|
194
304
|
|
|
305
|
+
const MapZoom = ({ onAction, position = 'bottomleft', ...props }: MapControlProps) => (
|
|
306
|
+
<CustomControl position={position} {...props}>
|
|
307
|
+
<ZoomControls onAction={onAction} />
|
|
308
|
+
</CustomControl>
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const MapAction = ({ onAction, position = 'bottomright', ...props }: MapControlProps) => (
|
|
312
|
+
<CustomControl position={position} {...props}>
|
|
313
|
+
<ActionControls onAction={onAction} />
|
|
314
|
+
</CustomControl>
|
|
315
|
+
);
|
|
316
|
+
|
|
195
317
|
//
|
|
196
318
|
// Map
|
|
197
319
|
//
|
|
198
320
|
|
|
199
321
|
export const Map = {
|
|
200
322
|
Root: MapRoot,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
</CustomControl>
|
|
206
|
-
),
|
|
207
|
-
Action: ({ onAction, position = 'bottomright', ...props }: MapControlProps) => (
|
|
208
|
-
<CustomControl position={position} {...props}>
|
|
209
|
-
<ActionControls onAction={onAction} />
|
|
210
|
-
</CustomControl>
|
|
211
|
-
),
|
|
323
|
+
Tiles: MapTiles,
|
|
324
|
+
Markers: MapMarkers,
|
|
325
|
+
Zoom: MapZoom,
|
|
326
|
+
Action: MapAction,
|
|
212
327
|
};
|
|
213
328
|
|
|
214
|
-
export { type
|
|
329
|
+
export { type MapController, type MapRootProps, type MapTilesProps, type MapMarkersProps, type MapControlProps };
|
|
@@ -22,9 +22,8 @@ export const controlPositions: Record<ControlPosition, string> = {
|
|
|
22
22
|
|
|
23
23
|
export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
|
|
24
24
|
return (
|
|
25
|
-
<Toolbar.Root classNames={['gap-
|
|
25
|
+
<Toolbar.Root classNames={['gap-2', classNames]}>
|
|
26
26
|
<IconButton
|
|
27
|
-
//
|
|
28
27
|
icon='ph--plus--regular'
|
|
29
28
|
label='zoom in'
|
|
30
29
|
iconOnly
|
|
@@ -33,7 +32,6 @@ export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
|
|
|
33
32
|
onClick={() => onAction?.('zoom-in')}
|
|
34
33
|
/>
|
|
35
34
|
<IconButton
|
|
36
|
-
//
|
|
37
35
|
icon='ph--minus--regular'
|
|
38
36
|
label='zoom out'
|
|
39
37
|
iconOnly
|
|
@@ -47,9 +45,8 @@ export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
|
|
|
47
45
|
|
|
48
46
|
export const ActionControls = ({ classNames, onAction }: ControlProps) => {
|
|
49
47
|
return (
|
|
50
|
-
<Toolbar.Root classNames={['gap-
|
|
48
|
+
<Toolbar.Root classNames={['gap-2', classNames]}>
|
|
51
49
|
<IconButton
|
|
52
|
-
//
|
|
53
50
|
icon='ph--play--regular'
|
|
54
51
|
label='start'
|
|
55
52
|
iconOnly
|
|
@@ -58,7 +55,6 @@ export const ActionControls = ({ classNames, onAction }: ControlProps) => {
|
|
|
58
55
|
onClick={() => onAction?.('start')}
|
|
59
56
|
/>
|
|
60
57
|
<IconButton
|
|
61
|
-
//
|
|
62
58
|
icon='ph--globe-hemisphere-west--regular'
|
|
63
59
|
label='toggle'
|
|
64
60
|
iconOnly
|
package/src/components/index.ts
CHANGED
package/src/hooks/context.tsx
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { type Dispatch, type PropsWithChildren, type SetStateAction, createContext, useContext } from 'react';
|
|
6
6
|
|
|
7
7
|
import { raise } from '@dxos/debug';
|
|
8
8
|
import { useControlledState } from '@dxos/react-ui';
|
|
9
9
|
|
|
10
|
-
import { type
|
|
10
|
+
import { type LatLngLiteral } from '../types';
|
|
11
11
|
|
|
12
12
|
// TODO(burdon): Factor out common geometry types.
|
|
13
13
|
export type Size = { width: number; height: number };
|
|
@@ -16,12 +16,12 @@ export type Vector = [number, number, number];
|
|
|
16
16
|
|
|
17
17
|
export type GlobeContextType = {
|
|
18
18
|
size: Size;
|
|
19
|
-
center
|
|
20
|
-
|
|
19
|
+
center?: LatLngLiteral;
|
|
20
|
+
zoom: number;
|
|
21
21
|
translation: Point;
|
|
22
22
|
rotation: Vector;
|
|
23
|
-
setCenter: Dispatch<SetStateAction<
|
|
24
|
-
|
|
23
|
+
setCenter: Dispatch<SetStateAction<LatLngLiteral>>;
|
|
24
|
+
setZoom: Dispatch<SetStateAction<number>>;
|
|
25
25
|
setTranslation: Dispatch<SetStateAction<Point>>;
|
|
26
26
|
setRotation: Dispatch<SetStateAction<Vector>>;
|
|
27
27
|
};
|
|
@@ -29,25 +29,25 @@ export type GlobeContextType = {
|
|
|
29
29
|
const GlobeContext = createContext<GlobeContextType>(undefined);
|
|
30
30
|
|
|
31
31
|
export type GlobeContextProviderProps = PropsWithChildren<
|
|
32
|
-
Partial<Pick<GlobeContextType, 'size' | 'center' | '
|
|
32
|
+
Partial<Pick<GlobeContextType, 'size' | 'center' | 'zoom' | 'translation' | 'rotation'>>
|
|
33
33
|
>;
|
|
34
34
|
|
|
35
35
|
export const GlobeContextProvider = ({
|
|
36
36
|
children,
|
|
37
37
|
size,
|
|
38
38
|
center: _center,
|
|
39
|
-
|
|
39
|
+
zoom: _zoom,
|
|
40
40
|
translation: _translation,
|
|
41
41
|
rotation: _rotation,
|
|
42
42
|
}: GlobeContextProviderProps) => {
|
|
43
43
|
const [center, setCenter] = useControlledState(_center);
|
|
44
|
-
const [
|
|
44
|
+
const [zoom, setZoom] = useControlledState(_zoom);
|
|
45
45
|
const [translation, setTranslation] = useControlledState<Point>(_translation);
|
|
46
46
|
const [rotation, setRotation] = useControlledState<Vector>(_rotation);
|
|
47
47
|
|
|
48
48
|
return (
|
|
49
49
|
<GlobeContext.Provider
|
|
50
|
-
value={{ size, center,
|
|
50
|
+
value={{ size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation }}
|
|
51
51
|
>
|
|
52
52
|
{children}
|
|
53
53
|
</GlobeContext.Provider>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type ControlProps, type GlobeController } from '../components';
|
|
8
8
|
|
|
9
9
|
export const useGlobeZoomHandler = (controller: GlobeController | null | undefined): ControlProps['onAction'] => {
|
|
10
10
|
return useCallback<ControlProps['onAction']>(
|
|
@@ -15,11 +15,11 @@ export const useGlobeZoomHandler = (controller: GlobeController | null | undefin
|
|
|
15
15
|
|
|
16
16
|
switch (event) {
|
|
17
17
|
case 'zoom-in': {
|
|
18
|
-
controller.
|
|
18
|
+
controller.setZoom((zoom) => zoom * 1.1);
|
|
19
19
|
break;
|
|
20
20
|
}
|
|
21
21
|
case 'zoom-out': {
|
|
22
|
-
controller.
|
|
22
|
+
controller.setZoom((zoom) => zoom * 0.9);
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type ControlProps, type MapController } from '../components';
|
|
8
8
|
|
|
9
9
|
export const useMapZoomHandler = (controller: MapController | null | undefined): ControlProps['onAction'] => {
|
|
10
10
|
return useCallback<ControlProps['onAction']>(
|
package/src/hooks/useSpinner.ts
CHANGED
|
@@ -6,9 +6,10 @@ import { timer as d3Timer } from 'd3';
|
|
|
6
6
|
import { type Timer } from 'd3';
|
|
7
7
|
import { useEffect, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { type Vector } from './context';
|
|
10
9
|
import { type GlobeController } from '../components';
|
|
11
10
|
|
|
11
|
+
import { type Vector } from './context';
|
|
12
|
+
|
|
12
13
|
export type SpinnerOptions = {
|
|
13
14
|
disabled?: boolean;
|
|
14
15
|
delta?: Vector;
|
package/src/hooks/useTour.ts
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { type
|
|
5
|
+
import { selection as d3Selection, geoDistance, geoInterpolate, geoPath } from 'd3';
|
|
6
|
+
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
import versor from 'versor';
|
|
8
8
|
|
|
9
9
|
import type { GlobeController } from '../components';
|
|
10
|
-
import {
|
|
10
|
+
import { type LatLngLiteral } from '../types';
|
|
11
|
+
import { type StyleSet, geoToPosition, positionToRotation } from '../util';
|
|
11
12
|
|
|
12
13
|
const TRANSITION_NAME = 'globe-tour';
|
|
13
14
|
|
|
@@ -29,7 +30,7 @@ export type TourOptions = {
|
|
|
29
30
|
*/
|
|
30
31
|
export const useTour = (
|
|
31
32
|
controller?: GlobeController | null,
|
|
32
|
-
points?:
|
|
33
|
+
points?: LatLngLiteral[],
|
|
33
34
|
options: TourOptions = {},
|
|
34
35
|
): [boolean, Dispatch<SetStateAction<boolean>>] => {
|
|
35
36
|
const selection = useMemo(() => d3Selection(), []);
|
|
@@ -48,7 +49,7 @@ export const useTour = (
|
|
|
48
49
|
const path = geoPath(projection, context).pointRadius(2);
|
|
49
50
|
|
|
50
51
|
const tilt = options.tilt ?? 0;
|
|
51
|
-
let last:
|
|
52
|
+
let last: LatLngLiteral;
|
|
52
53
|
try {
|
|
53
54
|
const p = [...points];
|
|
54
55
|
if (options.loop) {
|
|
@@ -82,14 +83,14 @@ export const useTour = (
|
|
|
82
83
|
{
|
|
83
84
|
context.beginPath();
|
|
84
85
|
context.strokeStyle = options?.styles?.arc?.strokeStyle ?? 'yellow';
|
|
85
|
-
context.lineWidth = (options?.styles?.arc?.lineWidth ?? 1.5) * (controller?.
|
|
86
|
+
context.lineWidth = (options?.styles?.arc?.lineWidth ?? 1.5) * (controller?.zoom ?? 1);
|
|
86
87
|
context.setLineDash(options?.styles?.arc?.lineDash ?? []);
|
|
87
88
|
path({ type: 'LineString', coordinates: [ip(t1), ip(t2)] });
|
|
88
89
|
context.stroke();
|
|
89
90
|
|
|
90
91
|
context.beginPath();
|
|
91
92
|
context.fillStyle = options?.styles?.cursor?.fillStyle ?? 'orange';
|
|
92
|
-
path.pointRadius((options?.styles?.cursor?.pointRadius ?? 2) * (controller?.
|
|
93
|
+
path.pointRadius((options?.styles?.cursor?.pointRadius ?? 2) * (controller?.zoom ?? 1));
|
|
93
94
|
path({ type: 'Point', coordinates: ip(t2) });
|
|
94
95
|
context.fill();
|
|
95
96
|
}
|
|
@@ -104,7 +105,7 @@ export const useTour = (
|
|
|
104
105
|
await transition.end();
|
|
105
106
|
last = next;
|
|
106
107
|
}
|
|
107
|
-
} catch
|
|
108
|
+
} catch {
|
|
108
109
|
// Ignore.
|
|
109
110
|
} finally {
|
|
110
111
|
setRunning(false);
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
package/src/util/inertia.ts
CHANGED
package/src/util/path.ts
CHANGED
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
import { type GeoGeometryObjects, geoCircle as d3GeoCircle } from 'd3';
|
|
6
6
|
import { type Point, type Polygon, type Position } from 'geojson';
|
|
7
|
+
import { type LatLngLiteral } from 'leaflet';
|
|
7
8
|
|
|
8
9
|
import type { Vector } from '../hooks';
|
|
9
10
|
|
|
10
|
-
export type LatLng = { lat: number; lng: number };
|
|
11
|
-
|
|
12
11
|
export const positionToRotation = ([lng, lat]: [number, number], tilt = 0): Vector => [-lng, tilt - lat, 0];
|
|
13
12
|
|
|
14
|
-
export const geoToPosition = ({ lat, lng }:
|
|
13
|
+
export const geoToPosition = ({ lat, lng }: LatLngLiteral): [number, number] => [lng, lat];
|
|
15
14
|
|
|
16
|
-
export const geoPoint = (point:
|
|
15
|
+
export const geoPoint = (point: LatLngLiteral): Point => ({ type: 'Point', coordinates: geoToPosition(point) });
|
|
17
16
|
|
|
18
17
|
// https://github.com/d3/d3-geo#geoCircle
|
|
19
|
-
export const geoCircle = ({ lat, lng }:
|
|
18
|
+
export const geoCircle = ({ lat, lng }: LatLngLiteral, radius: number): Polygon =>
|
|
20
19
|
d3GeoCircle().radius(radius).center([lng, lat])();
|
|
21
20
|
|
|
22
|
-
export const geoLine = (p1:
|
|
21
|
+
export const geoLine = (p1: LatLngLiteral, p2: LatLngLiteral): GeoGeometryObjects => ({
|
|
23
22
|
type: 'LineString',
|
|
24
23
|
coordinates: [
|
|
25
24
|
[p1.lng, p1.lat],
|
package/src/util/render.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { type GeoPath, type GeoPermissibleObjects, geoGraticule } from 'd3';
|
|
|
6
6
|
import { feature, mesh } from 'topojson-client';
|
|
7
7
|
import { type Topology } from 'topojson-specification';
|
|
8
8
|
|
|
9
|
-
import { type
|
|
9
|
+
import { type LatLngLiteral } from '../types';
|
|
10
|
+
|
|
11
|
+
import { geoLine, geoPoint } from './path';
|
|
10
12
|
|
|
11
13
|
export type Styles = Record<string, any>;
|
|
12
14
|
|
|
@@ -25,8 +27,8 @@ export type Style =
|
|
|
25
27
|
export type StyleSet = Partial<Record<Style, Styles>>;
|
|
26
28
|
|
|
27
29
|
export type Features = {
|
|
28
|
-
points?:
|
|
29
|
-
lines?: { source:
|
|
30
|
+
points?: LatLngLiteral[];
|
|
31
|
+
lines?: { source: LatLngLiteral; target: LatLngLiteral }[];
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
export type Layer = {
|