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