@dxos/react-ui-geo 0.8.3 → 0.8.4-main.1068cf700f
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 +404 -458
- package/dist/lib/browser/index.mjs.map +4 -4
- 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 +404 -458
- package/dist/lib/node-esm/index.mjs.map +4 -4
- 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 +25 -9
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.d.ts +28 -18
- package/dist/types/src/components/Map/Map.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.stories.d.ts +14 -8
- 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 +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +12 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- 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 +29 -23
- package/src/components/Globe/Globe.stories.tsx +85 -37
- package/src/components/Globe/Globe.tsx +80 -63
- package/src/components/Map/Map.stories.tsx +25 -14
- package/src/components/Map/Map.tsx +183 -96
- package/src/components/Toolbar/Controls.tsx +14 -20
- package/src/components/index.ts +0 -2
- package/src/hooks/context.tsx +22 -16
- package/src/hooks/useGlobeZoomHandler.ts +9 -3
- package/src/hooks/useMapZoomHandler.ts +1 -1
- package/src/hooks/useSpinner.ts +2 -1
- package/src/hooks/useTour.ts +10 -8
- package/src/index.ts +2 -1
- package/src/translations.ts +20 -0
- 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
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-geo",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4-main.1068cf700f",
|
|
4
4
|
"description": "Geo components.",
|
|
5
5
|
"homepage": "https://github.com/dxos",
|
|
6
6
|
"bugs": "https://github.com/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
13
|
"sideEffects": true,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
"./data": {
|
|
17
|
+
"source": "./src/data.ts",
|
|
13
18
|
"types": "./dist/types/src/data.d.ts",
|
|
14
19
|
"browser": "./dist/lib/browser/data.mjs",
|
|
15
20
|
"node": "./dist/lib/node-esm/data.mjs"
|
|
16
21
|
},
|
|
17
22
|
".": {
|
|
23
|
+
"source": "./src/index.ts",
|
|
18
24
|
"types": "./dist/types/src/index.d.ts",
|
|
19
25
|
"browser": "./dist/lib/browser/index.mjs",
|
|
20
26
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
@@ -34,51 +40,51 @@
|
|
|
34
40
|
"src"
|
|
35
41
|
],
|
|
36
42
|
"dependencies": {
|
|
37
|
-
"@
|
|
43
|
+
"@radix-ui/react-context": "1.1.1",
|
|
38
44
|
"d3": "^7.9.0",
|
|
39
45
|
"d3-geo-projection": "^4.0.0",
|
|
40
46
|
"d3-hexbin": "^0.2.2",
|
|
41
47
|
"geojson": "^0.5.0",
|
|
42
48
|
"leaflet": "^1.9.4",
|
|
43
49
|
"lodash.defaultsdeep": "^4.6.1",
|
|
44
|
-
"react-leaflet": "^
|
|
50
|
+
"react-leaflet": "^5.0.0",
|
|
45
51
|
"react-resize-detector": "^11.0.1",
|
|
46
52
|
"topojson-client": "^3.1.0",
|
|
47
53
|
"topojson-simplify": "^3.0.3",
|
|
48
54
|
"versor": "^0.2.0",
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/util": "0.8.
|
|
53
|
-
"@dxos/
|
|
55
|
+
"@dxos/debug": "0.8.4-main.1068cf700f",
|
|
56
|
+
"@dxos/async": "0.8.4-main.1068cf700f",
|
|
57
|
+
"@dxos/node-std": "0.8.4-main.1068cf700f",
|
|
58
|
+
"@dxos/util": "0.8.4-main.1068cf700f",
|
|
59
|
+
"@dxos/log": "0.8.4-main.1068cf700f"
|
|
54
60
|
},
|
|
55
61
|
"devDependencies": {
|
|
56
|
-
"@react-three/drei": "^
|
|
57
|
-
"@react-three/fiber": "^
|
|
62
|
+
"@react-three/drei": "^10.7.7",
|
|
63
|
+
"@react-three/fiber": "^9.5.0",
|
|
58
64
|
"@types/d3": "^7.4.3",
|
|
59
65
|
"@types/geojson": "^7946.0.14",
|
|
60
66
|
"@types/leaflet": "^1.9.16",
|
|
61
|
-
"@types/react": "~
|
|
62
|
-
"@types/react-dom": "~
|
|
67
|
+
"@types/react": "~19.2.7",
|
|
68
|
+
"@types/react-dom": "~19.2.3",
|
|
63
69
|
"@types/three": "0.165.0",
|
|
64
70
|
"@types/topojson-client": "^3.1.4",
|
|
65
71
|
"@types/topojson-simplify": "^3.0.3",
|
|
66
72
|
"@types/topojson-specification": "^1.0.5",
|
|
67
73
|
"JSONStream": "^1.3.5",
|
|
68
74
|
"geojson2h3": "^1.2.0",
|
|
69
|
-
"leva": "^0.
|
|
70
|
-
"react": "~
|
|
71
|
-
"react-dom": "~
|
|
72
|
-
"three": "0.
|
|
73
|
-
"@dxos/react-ui": "0.8.
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/storybook-utils": "0.8.
|
|
75
|
+
"leva": "^0.10.1",
|
|
76
|
+
"react": "~19.2.3",
|
|
77
|
+
"react-dom": "~19.2.3",
|
|
78
|
+
"three": "^0.178.0",
|
|
79
|
+
"@dxos/react-ui": "0.8.4-main.1068cf700f",
|
|
80
|
+
"@dxos/ui-theme": "0.8.4-main.1068cf700f",
|
|
81
|
+
"@dxos/storybook-utils": "0.8.4-main.1068cf700f"
|
|
76
82
|
},
|
|
77
83
|
"peerDependencies": {
|
|
78
|
-
"react": "~
|
|
79
|
-
"react-dom": "~
|
|
80
|
-
"@dxos/react-ui": "0.8.
|
|
81
|
-
"@dxos/
|
|
84
|
+
"react": "~19.2.3",
|
|
85
|
+
"react-dom": "~19.2.3",
|
|
86
|
+
"@dxos/react-ui": "0.8.4-main.1068cf700f",
|
|
87
|
+
"@dxos/ui-theme": "0.8.4-main.1068cf700f"
|
|
82
88
|
},
|
|
83
89
|
"publishConfig": {
|
|
84
90
|
"access": "public"
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
// Copyright 2018 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import { type Meta } from '@storybook/react';
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import { type FeatureCollection, type Geometry, type Position } from 'geojson';
|
|
9
7
|
import { Leva } from 'leva';
|
|
10
8
|
import React, { useMemo, useRef, useState } from 'react';
|
|
11
9
|
import { type Topology } from 'topojson-specification';
|
|
12
10
|
|
|
13
11
|
import { useAsyncState } from '@dxos/react-ui';
|
|
14
|
-
import { withTheme
|
|
12
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
15
13
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
14
|
+
import { type Vector, useDrag, useGlobeZoomHandler, useSpinner, useTour } from '../../hooks';
|
|
15
|
+
import { type LatLngLiteral } from '../../types';
|
|
16
|
+
import { type StyleSet, closestPoint } from '../../util';
|
|
19
17
|
import { type ControlProps } from '../Toolbar';
|
|
20
18
|
|
|
19
|
+
import { Globe, type GlobeCanvasProps, type GlobeController, type GlobeRootProps } from './Globe';
|
|
20
|
+
|
|
21
21
|
// TODO(burdon): Load from JSON at runtime?
|
|
22
22
|
const useTopology = () => {
|
|
23
23
|
return useAsyncState(async () => (await import('../../../data/countries-110m.ts')).default);
|
|
@@ -95,8 +95,11 @@ const createTrip = (
|
|
|
95
95
|
routes: Record<string, string[]>,
|
|
96
96
|
points: Position[] = [],
|
|
97
97
|
) => {
|
|
98
|
-
let previousHub:
|
|
99
|
-
return Object.entries(routes).reduce<{
|
|
98
|
+
let previousHub: LatLngLiteral;
|
|
99
|
+
return Object.entries(routes).reduce<{
|
|
100
|
+
points: LatLngLiteral[];
|
|
101
|
+
lines: { source: LatLngLiteral; target: LatLngLiteral }[];
|
|
102
|
+
}>(
|
|
100
103
|
(features, [hub, regional]) => {
|
|
101
104
|
const hubAirport = airports.features.find(({ properties }) => properties.iata === hub);
|
|
102
105
|
if (hubAirport) {
|
|
@@ -125,7 +128,7 @@ const createTrip = (
|
|
|
125
128
|
);
|
|
126
129
|
};
|
|
127
130
|
|
|
128
|
-
type StoryProps = Pick<GlobeRootProps, '
|
|
131
|
+
type StoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
|
|
129
132
|
Pick<GlobeCanvasProps, 'projection' | 'styles'> & {
|
|
130
133
|
drag?: boolean;
|
|
131
134
|
spin?: boolean;
|
|
@@ -133,12 +136,12 @@ type StoryProps = Pick<GlobeRootProps, 'scale' | 'translation' | 'rotation'> &
|
|
|
133
136
|
xAxis?: boolean;
|
|
134
137
|
};
|
|
135
138
|
|
|
136
|
-
const
|
|
137
|
-
|
|
139
|
+
const DefaultStory = ({
|
|
140
|
+
zoom: _zoom = 1,
|
|
138
141
|
translation,
|
|
139
142
|
rotation = [0, 0, 0],
|
|
140
143
|
projection,
|
|
141
|
-
styles,
|
|
144
|
+
styles = defaultStyles,
|
|
142
145
|
drag = false,
|
|
143
146
|
spin = false,
|
|
144
147
|
tour = false,
|
|
@@ -154,6 +157,7 @@ const Story = ({
|
|
|
154
157
|
});
|
|
155
158
|
const [topology] = useTopology();
|
|
156
159
|
const [airports] = useAsyncState(async () => (await import('../../../data/airports.ts')).default);
|
|
160
|
+
|
|
157
161
|
const features = useMemo(() => {
|
|
158
162
|
return airports ? createTrip(airports, routes, (dots?.objects.dots as any)?.geometries[0].coordinates) : undefined;
|
|
159
163
|
}, [airports, routes, dots]);
|
|
@@ -188,18 +192,18 @@ const Story = ({
|
|
|
188
192
|
break;
|
|
189
193
|
}
|
|
190
194
|
case 'zoom-in': {
|
|
191
|
-
controller.current.
|
|
195
|
+
controller.current.setZoom((scale) => scale * 1.1);
|
|
192
196
|
break;
|
|
193
197
|
}
|
|
194
198
|
case 'zoom-out': {
|
|
195
|
-
controller.current.
|
|
199
|
+
controller.current.setZoom((scale) => scale * 0.9);
|
|
196
200
|
break;
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
};
|
|
200
204
|
|
|
201
205
|
return (
|
|
202
|
-
<Globe.Root classNames='absolute inset-0'
|
|
206
|
+
<Globe.Root classNames='absolute inset-0' zoom={_zoom} translation={translation} rotation={rotation}>
|
|
203
207
|
<Globe.Canvas
|
|
204
208
|
ref={controller}
|
|
205
209
|
topology={styles?.dots ? dots : topology}
|
|
@@ -210,7 +214,7 @@ const Story = ({
|
|
|
210
214
|
<Globe.Zoom onAction={handleAction} />
|
|
211
215
|
<Globe.Action onAction={handleAction} />
|
|
212
216
|
<Globe.Debug />
|
|
213
|
-
<Globe.Panel position='topright' classNames='
|
|
217
|
+
<Globe.Panel position='topright' classNames='is-20 bs-20'>
|
|
214
218
|
<Leva />
|
|
215
219
|
</Globe.Panel>
|
|
216
220
|
</Globe.Root>
|
|
@@ -219,11 +223,15 @@ const Story = ({
|
|
|
219
223
|
|
|
220
224
|
const initialRotation: Vector = [0, -40, 0];
|
|
221
225
|
|
|
222
|
-
const meta
|
|
226
|
+
const meta = {
|
|
223
227
|
title: 'ui/react-ui-geo/Globe',
|
|
224
228
|
component: Globe.Root,
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
render: DefaultStory,
|
|
230
|
+
decorators: [withTheme()],
|
|
231
|
+
parameters: {
|
|
232
|
+
layout: 'fullscreen',
|
|
233
|
+
},
|
|
234
|
+
} satisfies Meta;
|
|
227
235
|
|
|
228
236
|
export default meta;
|
|
229
237
|
|
|
@@ -234,8 +242,8 @@ export const Earth1 = () => {
|
|
|
234
242
|
useDrag(controller);
|
|
235
243
|
|
|
236
244
|
return (
|
|
237
|
-
<Globe.Root
|
|
238
|
-
<Globe.Canvas ref={setController} topology={topology} />
|
|
245
|
+
<Globe.Root zoom={1.2} rotation={[Math.random() * 360, 0, 0]}>
|
|
246
|
+
<Globe.Canvas ref={setController} topology={topology} styles={defaultStyles} />
|
|
239
247
|
<Globe.Zoom onAction={handleAction} />
|
|
240
248
|
</Globe.Root>
|
|
241
249
|
);
|
|
@@ -249,8 +257,8 @@ export const Earth2 = () => {
|
|
|
249
257
|
|
|
250
258
|
return (
|
|
251
259
|
<div className='absolute bottom-0 left-0 right-0 '>
|
|
252
|
-
<Globe.Root classNames='h-[400px]'
|
|
253
|
-
<Globe.Canvas ref={setController} topology={topology} />
|
|
260
|
+
<Globe.Root classNames='h-[400px]' zoom={2.8} translation={{ x: 0, y: 400 }}>
|
|
261
|
+
<Globe.Canvas ref={setController} topology={topology} styles={defaultStyles} />
|
|
254
262
|
<Globe.Zoom onAction={handleAction} />
|
|
255
263
|
</Globe.Root>
|
|
256
264
|
</div>
|
|
@@ -283,33 +291,73 @@ export const Mercator = () => {
|
|
|
283
291
|
useDrag(controller);
|
|
284
292
|
|
|
285
293
|
return (
|
|
286
|
-
<Globe.Root classNames='flex grow overflow-hidden'
|
|
294
|
+
<Globe.Root classNames='flex grow overflow-hidden' zoom={0.7} rotation={initialRotation}>
|
|
287
295
|
<Globe.Canvas ref={setController} topology={topology} projection='mercator' styles={monochrome} />
|
|
288
296
|
<Globe.Zoom onAction={handleAction} />
|
|
289
297
|
</Globe.Root>
|
|
290
298
|
);
|
|
291
299
|
};
|
|
292
300
|
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
type Story = StoryObj<typeof DefaultStory>;
|
|
302
|
+
|
|
303
|
+
export const Globe1: Story = {
|
|
304
|
+
args: {
|
|
305
|
+
drag: true,
|
|
306
|
+
projection: 'mercator',
|
|
307
|
+
zoom: 0.8,
|
|
308
|
+
rotation: initialRotation,
|
|
309
|
+
styles: defaultStyles,
|
|
310
|
+
},
|
|
295
311
|
};
|
|
296
312
|
|
|
297
|
-
export const Globe2 =
|
|
298
|
-
|
|
313
|
+
export const Globe2: Story = {
|
|
314
|
+
args: {
|
|
315
|
+
drag: true,
|
|
316
|
+
projection: 'transverse-mercator',
|
|
317
|
+
zoom: 0.8,
|
|
318
|
+
rotation: initialRotation,
|
|
319
|
+
styles: defaultStyles,
|
|
320
|
+
},
|
|
299
321
|
};
|
|
300
322
|
|
|
301
|
-
export const Globe3 =
|
|
302
|
-
|
|
323
|
+
export const Globe3: Story = {
|
|
324
|
+
args: {
|
|
325
|
+
drag: true,
|
|
326
|
+
spin: true,
|
|
327
|
+
zoom: 1.5,
|
|
328
|
+
rotation: initialRotation,
|
|
329
|
+
styles: defaultStyles,
|
|
330
|
+
},
|
|
303
331
|
};
|
|
304
332
|
|
|
305
|
-
export const Globe4 =
|
|
306
|
-
|
|
333
|
+
export const Globe4: Story = {
|
|
334
|
+
args: {
|
|
335
|
+
drag: true,
|
|
336
|
+
tour: true,
|
|
337
|
+
zoom: 2,
|
|
338
|
+
rotation: initialRotation,
|
|
339
|
+
styles: defaultStyles,
|
|
340
|
+
},
|
|
307
341
|
};
|
|
308
342
|
|
|
309
|
-
export const Globe5 =
|
|
310
|
-
|
|
343
|
+
export const Globe5: Story = {
|
|
344
|
+
args: {
|
|
345
|
+
drag: true,
|
|
346
|
+
tour: true,
|
|
347
|
+
zoom: 0.9,
|
|
348
|
+
rotation: initialRotation,
|
|
349
|
+
styles: dotStyles,
|
|
350
|
+
},
|
|
311
351
|
};
|
|
312
352
|
|
|
313
|
-
export const Globe6 =
|
|
314
|
-
|
|
353
|
+
export const Globe6: Story = {
|
|
354
|
+
args: {
|
|
355
|
+
drag: true,
|
|
356
|
+
xAxis: true,
|
|
357
|
+
tour: true,
|
|
358
|
+
zoom: 2,
|
|
359
|
+
translation: { x: 0, y: 600 },
|
|
360
|
+
rotation: [0, -20, 0],
|
|
361
|
+
styles: dotStyles,
|
|
362
|
+
},
|
|
315
363
|
};
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type GeoProjection,
|
|
7
|
+
easeLinear,
|
|
8
|
+
easeSinOut,
|
|
7
9
|
geoMercator,
|
|
8
10
|
geoOrthographic,
|
|
9
11
|
geoPath,
|
|
10
12
|
geoTransverseMercator,
|
|
11
13
|
interpolateNumber,
|
|
12
14
|
transition,
|
|
13
|
-
easeLinear,
|
|
14
|
-
easeSinOut,
|
|
15
15
|
} from 'd3';
|
|
16
16
|
import { type ControlPosition } from 'leaflet';
|
|
17
17
|
import React, {
|
|
@@ -26,8 +26,8 @@ import React, {
|
|
|
26
26
|
import { useResizeDetector } from 'react-resize-detector';
|
|
27
27
|
import { type Topology } from 'topojson-specification';
|
|
28
28
|
|
|
29
|
-
import { type
|
|
30
|
-
import { mx } from '@dxos/
|
|
29
|
+
import { type ThemeMode, type ThemedClassName, useDynamicRef, useThemeContext } from '@dxos/react-ui';
|
|
30
|
+
import { mx } from '@dxos/ui-theme';
|
|
31
31
|
|
|
32
32
|
import {
|
|
33
33
|
GlobeContextProvider,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
renderLayers,
|
|
45
45
|
timer,
|
|
46
46
|
} from '../../util';
|
|
47
|
-
import {
|
|
47
|
+
import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
|
|
@@ -103,7 +103,7 @@ const defaultStyles: Record<ThemeMode, StyleSet> = {
|
|
|
103
103
|
export type GlobeController = {
|
|
104
104
|
canvas: HTMLCanvasElement;
|
|
105
105
|
projection: GeoProjection;
|
|
106
|
-
} & Pick<GlobeContextType, '
|
|
106
|
+
} & Pick<GlobeContextType, 'zoom' | 'translation' | 'rotation' | 'setZoom' | 'setTranslation' | 'setRotation'>;
|
|
107
107
|
|
|
108
108
|
export type ProjectionType = 'orthographic' | 'mercator' | 'transverse-mercator';
|
|
109
109
|
|
|
@@ -130,6 +130,7 @@ type GlobeRootProps = PropsWithChildren<ThemedClassName<GlobeContextProviderProp
|
|
|
130
130
|
|
|
131
131
|
const GlobeRoot = ({ classNames, children, ...props }: GlobeRootProps) => {
|
|
132
132
|
const { ref, width, height } = useResizeDetector<HTMLDivElement>();
|
|
133
|
+
|
|
133
134
|
return (
|
|
134
135
|
<div ref={ref} className={mx('relative flex grow overflow-hidden', classNames)}>
|
|
135
136
|
<GlobeContextProvider size={{ width, height }} {...props}>
|
|
@@ -154,17 +155,18 @@ type GlobeCanvasProps = {
|
|
|
154
155
|
* Basic globe renderer.
|
|
155
156
|
* https://github.com/topojson/world-atlas
|
|
156
157
|
*/
|
|
158
|
+
// TODO(burdon): Move controller to root.
|
|
157
159
|
const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
158
|
-
({ projection:
|
|
160
|
+
({ projection: projectionProp, topology, features, styles: stylesProp }, forwardRef) => {
|
|
159
161
|
const { themeMode } = useThemeContext();
|
|
160
|
-
const styles = useMemo(() =>
|
|
162
|
+
const styles = useMemo(() => stylesProp ?? defaultStyles[themeMode], [stylesProp, themeMode]);
|
|
161
163
|
|
|
162
164
|
// Canvas.
|
|
163
165
|
const [canvas, setCanvas] = useState<HTMLCanvasElement>(null);
|
|
164
166
|
const canvasRef = (canvas: HTMLCanvasElement) => setCanvas(canvas);
|
|
165
167
|
|
|
166
168
|
// Projection.
|
|
167
|
-
const projection = useMemo(() => getProjection(
|
|
169
|
+
const projection = useMemo(() => getProjection(projectionProp), [projectionProp]);
|
|
168
170
|
|
|
169
171
|
// Layers.
|
|
170
172
|
// TODO(burdon): Generate on the fly based on what is visible.
|
|
@@ -173,55 +175,50 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
173
175
|
}, [topology, features, styles]);
|
|
174
176
|
|
|
175
177
|
// State.
|
|
176
|
-
const { size, center,
|
|
178
|
+
const { size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation } =
|
|
177
179
|
useGlobeContext();
|
|
178
|
-
|
|
179
|
-
const scaleRef = useDynamicRef(scale);
|
|
180
|
+
const zoomRef = useDynamicRef(zoom);
|
|
180
181
|
|
|
181
182
|
// Update rotation.
|
|
182
183
|
useEffect(() => {
|
|
183
184
|
if (center) {
|
|
184
|
-
|
|
185
|
+
setZoom(1);
|
|
185
186
|
setRotation(positionToRotation(geoToPosition(center)));
|
|
186
187
|
}
|
|
187
188
|
}, [center]);
|
|
188
189
|
|
|
189
190
|
// External controller.
|
|
190
191
|
const zooming = useRef(false);
|
|
191
|
-
useImperativeHandle<GlobeController, GlobeController>(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
[canvas],
|
|
224
|
-
);
|
|
192
|
+
useImperativeHandle<GlobeController, GlobeController>(forwardRef, () => {
|
|
193
|
+
return {
|
|
194
|
+
canvas,
|
|
195
|
+
projection,
|
|
196
|
+
center,
|
|
197
|
+
get zoom() {
|
|
198
|
+
return zoomRef.current;
|
|
199
|
+
},
|
|
200
|
+
translation,
|
|
201
|
+
rotation,
|
|
202
|
+
setCenter,
|
|
203
|
+
setZoom: (state) => {
|
|
204
|
+
if (typeof state === 'function') {
|
|
205
|
+
const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
|
|
206
|
+
// Stop easing if already zooming.
|
|
207
|
+
transition()
|
|
208
|
+
.ease(zooming.current ? easeLinear : easeSinOut)
|
|
209
|
+
.duration(200)
|
|
210
|
+
.tween('scale', () => (t) => setZoom(is(t)))
|
|
211
|
+
.on('end', () => {
|
|
212
|
+
zooming.current = false;
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
setZoom(state);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
setTranslation,
|
|
219
|
+
setRotation,
|
|
220
|
+
};
|
|
221
|
+
}, [canvas]);
|
|
225
222
|
|
|
226
223
|
// https://d3js.org/d3-geo/path#geoPath
|
|
227
224
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
|
|
@@ -236,14 +233,14 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
236
233
|
timer(() => {
|
|
237
234
|
// https://d3js.org/d3-geo/projection
|
|
238
235
|
projection
|
|
239
|
-
.scale((Math.min(size.width, size.height) / 2) *
|
|
236
|
+
.scale((Math.min(size.width, size.height) / 2) * zoom)
|
|
240
237
|
.translate([size.width / 2 + (translation?.x ?? 0), size.height / 2 + (translation?.y ?? 0)])
|
|
241
238
|
.rotate(rotation ?? [0, 0, 0]);
|
|
242
239
|
|
|
243
|
-
renderLayers(generator, layers,
|
|
240
|
+
renderLayers(generator, layers, zoom, styles);
|
|
244
241
|
});
|
|
245
242
|
}
|
|
246
|
-
}, [generator, size,
|
|
243
|
+
}, [generator, size, zoom, translation, rotation, layers]);
|
|
247
244
|
|
|
248
245
|
if (!size.width || !size.height) {
|
|
249
246
|
return null;
|
|
@@ -253,22 +250,30 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
253
250
|
},
|
|
254
251
|
);
|
|
255
252
|
|
|
253
|
+
//
|
|
254
|
+
// Debug
|
|
255
|
+
//
|
|
256
|
+
|
|
256
257
|
const GlobeDebug = ({ position = 'topleft' }: { position?: ControlPosition }) => {
|
|
257
|
-
const { size,
|
|
258
|
+
const { size, zoom, translation, rotation } = useGlobeContext();
|
|
258
259
|
return (
|
|
259
260
|
<div
|
|
260
261
|
className={mx(
|
|
261
|
-
'z-10 absolute
|
|
262
|
+
'z-10 absolute is-96 p-2 overflow-hidden border border-green-700 rounded',
|
|
262
263
|
controlPositions[position],
|
|
263
264
|
)}
|
|
264
265
|
>
|
|
265
266
|
<pre className='font-mono text-xs text-green-700'>
|
|
266
|
-
{JSON.stringify({ size,
|
|
267
|
+
{JSON.stringify({ size, zoom, translation, rotation }, null, 2)}
|
|
267
268
|
</pre>
|
|
268
269
|
</div>
|
|
269
270
|
);
|
|
270
271
|
};
|
|
271
272
|
|
|
273
|
+
//
|
|
274
|
+
// Panel
|
|
275
|
+
//
|
|
276
|
+
|
|
272
277
|
const GlobePanel = ({
|
|
273
278
|
position,
|
|
274
279
|
classNames,
|
|
@@ -277,25 +282,37 @@ const GlobePanel = ({
|
|
|
277
282
|
return <div className={mx('z-10 absolute overflow-hidden', controlPositions[position], classNames)}>{children}</div>;
|
|
278
283
|
};
|
|
279
284
|
|
|
285
|
+
//
|
|
286
|
+
// Controls
|
|
287
|
+
//
|
|
288
|
+
|
|
280
289
|
const CustomControl = ({ position, children }: PropsWithChildren<{ position: ControlPosition }>) => {
|
|
281
290
|
return <div className={mx('z-10 absolute overflow-hidden', controlPositions[position])}>{children}</div>;
|
|
282
291
|
};
|
|
283
292
|
|
|
284
293
|
type GlobeControlProps = { position?: ControlPosition } & Pick<ControlProps, 'onAction'>;
|
|
285
294
|
|
|
295
|
+
const GlobeZoom = ({ onAction, position = 'bottomleft', ...props }: GlobeControlProps) => (
|
|
296
|
+
<CustomControl position={position} {...props}>
|
|
297
|
+
<ZoomControls onAction={onAction} />
|
|
298
|
+
</CustomControl>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const GlobeAction = ({ onAction, position = 'bottomright', ...props }: GlobeControlProps) => (
|
|
302
|
+
<CustomControl position={position} {...props}>
|
|
303
|
+
<ActionControls onAction={onAction} />
|
|
304
|
+
</CustomControl>
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
//
|
|
308
|
+
// Globe
|
|
309
|
+
//
|
|
310
|
+
|
|
286
311
|
export const Globe = {
|
|
287
312
|
Root: GlobeRoot,
|
|
288
313
|
Canvas: GlobeCanvas,
|
|
289
|
-
Zoom:
|
|
290
|
-
|
|
291
|
-
<ZoomControls onAction={onAction} />
|
|
292
|
-
</CustomControl>
|
|
293
|
-
),
|
|
294
|
-
Action: ({ onAction, position = 'bottomright', ...props }: GlobeControlProps) => (
|
|
295
|
-
<CustomControl position={position} {...props}>
|
|
296
|
-
<ActionControls onAction={onAction} />
|
|
297
|
-
</CustomControl>
|
|
298
|
-
),
|
|
314
|
+
Zoom: GlobeZoom,
|
|
315
|
+
Action: GlobeAction,
|
|
299
316
|
Debug: GlobeDebug,
|
|
300
317
|
Panel: GlobePanel,
|
|
301
318
|
};
|