@deck.gl/widgets 9.1.12 → 9.2.0-alpha.2
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/dist/compass-widget.d.ts +11 -34
- package/dist/compass-widget.d.ts.map +1 -1
- package/dist/compass-widget.js +27 -62
- package/dist/compass-widget.js.map +1 -1
- package/dist/context-menu-widget.d.ts +37 -0
- package/dist/context-menu-widget.d.ts.map +1 -0
- package/dist/context-menu-widget.js +74 -0
- package/dist/context-menu-widget.js.map +1 -0
- package/dist/dist.dev.js +2271 -581
- package/dist/fps-widget.d.ts +29 -0
- package/dist/fps-widget.d.ts.map +1 -0
- package/dist/fps-widget.js +51 -0
- package/dist/fps-widget.js.map +1 -0
- package/dist/fullscreen-widget.d.ts +15 -33
- package/dist/fullscreen-widget.d.ts.map +1 -1
- package/dist/fullscreen-widget.js +20 -51
- package/dist/fullscreen-widget.js.map +1 -1
- package/dist/geocoder-widget.d.ts +51 -0
- package/dist/geocoder-widget.d.ts.map +1 -0
- package/dist/geocoder-widget.js +138 -0
- package/dist/geocoder-widget.js.map +1 -0
- package/dist/gimbal-widget.d.ts +33 -0
- package/dist/gimbal-widget.d.ts.map +1 -0
- package/dist/gimbal-widget.js +116 -0
- package/dist/gimbal-widget.js.map +1 -0
- package/dist/index.cjs +1587 -341
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +37 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -7
- package/dist/index.js.map +1 -1
- package/dist/info-widget.d.ts +37 -0
- package/dist/info-widget.d.ts.map +1 -0
- package/dist/info-widget.js +183 -0
- package/dist/info-widget.js.map +1 -0
- package/dist/lib/components/button-group.d.ts +8 -0
- package/dist/lib/components/button-group.d.ts.map +1 -0
- package/dist/lib/components/button-group.js +7 -0
- package/dist/lib/components/button-group.js.map +1 -0
- package/dist/lib/components/dropdown-menu.d.ts +8 -0
- package/dist/lib/components/dropdown-menu.d.ts.map +1 -0
- package/dist/lib/components/dropdown-menu.js +55 -0
- package/dist/lib/components/dropdown-menu.js.map +1 -0
- package/dist/lib/components/grouped-icon-button.d.ts +8 -0
- package/dist/lib/components/grouped-icon-button.d.ts.map +1 -0
- package/dist/lib/components/grouped-icon-button.js +7 -0
- package/dist/lib/components/grouped-icon-button.js.map +1 -0
- package/dist/lib/components/icon-button.d.ts +11 -0
- package/dist/lib/components/icon-button.d.ts.map +1 -0
- package/dist/lib/components/icon-button.js +7 -0
- package/dist/lib/components/icon-button.js.map +1 -0
- package/dist/lib/components/icon-menu.d.ts +16 -0
- package/dist/lib/components/icon-menu.d.ts.map +1 -0
- package/dist/lib/components/icon-menu.js +49 -0
- package/dist/lib/components/icon-menu.js.map +1 -0
- package/dist/lib/components/simple-menu.d.ts +16 -0
- package/dist/lib/components/simple-menu.d.ts.map +1 -0
- package/dist/lib/components/simple-menu.js +30 -0
- package/dist/lib/components/simple-menu.js.map +1 -0
- package/dist/lib/geocode/geocoder-history.d.ts +23 -0
- package/dist/lib/geocode/geocoder-history.d.ts.map +1 -0
- package/dist/lib/geocode/geocoder-history.js +64 -0
- package/dist/lib/geocode/geocoder-history.js.map +1 -0
- package/dist/lib/geocode/geocoder.d.ts +15 -0
- package/dist/lib/geocode/geocoder.d.ts.map +1 -0
- package/dist/lib/geocode/geocoder.js +5 -0
- package/dist/lib/geocode/geocoder.js.map +1 -0
- package/dist/lib/geocode/geocoders.d.ts +68 -0
- package/dist/lib/geocode/geocoders.d.ts.map +1 -0
- package/dist/lib/geocode/geocoders.js +182 -0
- package/dist/lib/geocode/geocoders.js.map +1 -0
- package/dist/loading-widget.d.ts +26 -0
- package/dist/loading-widget.d.ts.map +1 -0
- package/dist/loading-widget.js +41 -0
- package/dist/loading-widget.js.map +1 -0
- package/dist/reset-view-widget.d.ts +4 -4
- package/dist/reset-view-widget.d.ts.map +1 -1
- package/dist/reset-view-widget.js +7 -10
- package/dist/reset-view-widget.js.map +1 -1
- package/dist/scale-widget.d.ts +28 -0
- package/dist/scale-widget.d.ts.map +1 -0
- package/dist/scale-widget.js +111 -0
- package/dist/scale-widget.js.map +1 -0
- package/dist/screenshot-widget.d.ts +4 -4
- package/dist/screenshot-widget.d.ts.map +1 -1
- package/dist/screenshot-widget.js +7 -14
- package/dist/screenshot-widget.js.map +1 -1
- package/dist/splitter-widget.d.ts +32 -0
- package/dist/splitter-widget.d.ts.map +1 -0
- package/dist/splitter-widget.js +123 -0
- package/dist/splitter-widget.js.map +1 -0
- package/dist/stats-widget.d.ts +36 -0
- package/dist/stats-widget.d.ts.map +1 -0
- package/dist/stats-widget.js +115 -0
- package/dist/stats-widget.js.map +1 -0
- package/dist/stylesheet.css +121 -4
- package/dist/theme-widget.d.ts +31 -0
- package/dist/theme-widget.d.ts.map +1 -0
- package/dist/theme-widget.js +83 -0
- package/dist/theme-widget.js.map +1 -0
- package/dist/themes.d.ts +65 -36
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +20 -8
- package/dist/themes.js.map +1 -1
- package/dist/timeline-widget.d.ts +59 -0
- package/dist/timeline-widget.d.ts.map +1 -0
- package/dist/timeline-widget.js +83 -0
- package/dist/timeline-widget.js.map +1 -0
- package/dist/view-selector-widget.d.ts +33 -0
- package/dist/view-selector-widget.d.ts.map +1 -0
- package/dist/view-selector-widget.js +62 -0
- package/dist/view-selector-widget.js.map +1 -0
- package/dist/zoom-widget.d.ts +14 -43
- package/dist/zoom-widget.d.ts.map +1 -1
- package/dist/zoom-widget.js +14 -56
- package/dist/zoom-widget.js.map +1 -1
- package/dist.min.js +3 -1
- package/package.json +4 -3
- package/src/compass-widget.tsx +44 -106
- package/src/context-menu-widget.tsx +115 -0
- package/src/fps-widget.tsx +67 -0
- package/src/fullscreen-widget.tsx +33 -86
- package/src/geocoder-widget.tsx +209 -0
- package/src/gimbal-widget.tsx +188 -0
- package/src/index.ts +58 -8
- package/src/info-widget.tsx +225 -0
- package/src/lib/components/button-group.tsx +16 -0
- package/src/lib/components/dropdown-menu.tsx +98 -0
- package/src/lib/components/grouped-icon-button.tsx +24 -0
- package/src/lib/components/icon-button.tsx +30 -0
- package/src/lib/components/icon-menu.tsx +89 -0
- package/src/lib/components/simple-menu.tsx +52 -0
- package/src/lib/geocode/geocoder-history.ts +73 -0
- package/src/lib/geocode/geocoder.ts +15 -0
- package/src/lib/geocode/geocoders.ts +199 -0
- package/src/loading-widget.tsx +67 -0
- package/src/reset-view-widget.tsx +8 -11
- package/src/scale-widget.tsx +183 -0
- package/src/screenshot-widget.tsx +9 -11
- package/src/splitter-widget.tsx +179 -0
- package/src/stats-widget.tsx +166 -0
- package/src/stylesheet.css +121 -4
- package/src/theme-widget.tsx +120 -0
- package/src/themes.ts +45 -12
- package/src/timeline-widget.tsx +155 -0
- package/src/view-selector-widget.tsx +108 -0
- package/src/zoom-widget.tsx +39 -113
- package/dist/components.d.ts +0 -4
- package/dist/components.d.ts.map +0 -1
- package/dist/components.js +0 -17
- package/dist/components.js.map +0 -1
- package/dist/widget-impl.d.ts +0 -29
- package/dist/widget-impl.d.ts.map +0 -1
- package/dist/widget-impl.js +0 -60
- package/dist/widget-impl.js.map +0 -1
- package/src/components.tsx +0 -38
- package/src/widget-impl.ts +0 -88
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* global document */
|
|
2
|
+
import {Widget, WidgetProps} from '@deck.gl/core';
|
|
3
|
+
import type {Deck, PickingInfo} from '@deck.gl/core';
|
|
4
|
+
import {render} from 'preact';
|
|
5
|
+
import {SimpleMenu} from './lib/components/simple-menu';
|
|
6
|
+
|
|
7
|
+
/** The standard, modern way is to use event.button === 2, where button is the standardized property (0 = left, 1 = middle, 2 = right). */
|
|
8
|
+
const MOUSE_BUTTON_RIGHT = 2;
|
|
9
|
+
/** A name for the legacy MouseEvent.which value that corresponds to the right-mouse button. In older browsers, the check is: if (event.which === 3) */
|
|
10
|
+
const MOUSE_WHICH_RIGHT = 3;
|
|
11
|
+
|
|
12
|
+
export type ContextWidgetMenuItem = {
|
|
13
|
+
label: string;
|
|
14
|
+
key: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ContextMenuWidgetProps = WidgetProps & {
|
|
18
|
+
/** View to attach to and interact with. Required when using multiple views. */
|
|
19
|
+
viewId?: string | null;
|
|
20
|
+
/** Controls visibility of the context menu */
|
|
21
|
+
visible?: boolean;
|
|
22
|
+
/** Screen position at which to place the menu */
|
|
23
|
+
position: {x: number; y: number};
|
|
24
|
+
/** Items to render */
|
|
25
|
+
menuItems: ContextWidgetMenuItem[];
|
|
26
|
+
/** Provide menu items for the menu given the picked object */
|
|
27
|
+
getMenuItems: (info: PickingInfo, widget: ContextMenuWidget) => ContextWidgetMenuItem[] | null;
|
|
28
|
+
/** Callback with the selected item */
|
|
29
|
+
onMenuItemSelected?: (key: string, pickInfo: PickingInfo | null) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class ContextMenuWidget extends Widget<ContextMenuWidgetProps> {
|
|
33
|
+
static defaultProps: Required<ContextMenuWidgetProps> = {
|
|
34
|
+
...Widget.defaultProps,
|
|
35
|
+
viewId: null,
|
|
36
|
+
visible: false,
|
|
37
|
+
position: {x: 0, y: 0},
|
|
38
|
+
getMenuItems: undefined!,
|
|
39
|
+
menuItems: [],
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
onMenuItemSelected: (key, pickInfo) => console.log('Context menu item selected:', key, pickInfo)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
className = 'deck-widget-context-menu';
|
|
45
|
+
placement = 'fill' as const;
|
|
46
|
+
|
|
47
|
+
pickInfo: PickingInfo | null = null;
|
|
48
|
+
|
|
49
|
+
constructor(props: ContextMenuWidgetProps) {
|
|
50
|
+
super(props, ContextMenuWidget.defaultProps);
|
|
51
|
+
this.pickInfo = null;
|
|
52
|
+
this.setProps(props);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onAdd({deck}: {deck: Deck<any>}): HTMLDivElement {
|
|
56
|
+
const element = document.createElement('div');
|
|
57
|
+
element.classList.add('deck-widget', 'deck-widget-context-menu');
|
|
58
|
+
const style = {
|
|
59
|
+
margin: '0px',
|
|
60
|
+
top: '0px',
|
|
61
|
+
left: '0px',
|
|
62
|
+
position: 'absolute',
|
|
63
|
+
pointerEvents: 'auto'
|
|
64
|
+
};
|
|
65
|
+
Object.entries(style).forEach(([key, value]) => element.style.setProperty(key, value));
|
|
66
|
+
|
|
67
|
+
deck.getCanvas()?.addEventListener('click', () => this.hide());
|
|
68
|
+
deck.getCanvas()?.addEventListener('contextmenu', event => this.handleContextMenu(event));
|
|
69
|
+
return element;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onRenderHTML(rootElement: HTMLElement): void {
|
|
73
|
+
const {visible, position, menuItems} = this.props;
|
|
74
|
+
|
|
75
|
+
const ui =
|
|
76
|
+
visible && menuItems.length ? (
|
|
77
|
+
<SimpleMenu
|
|
78
|
+
menuItems={menuItems}
|
|
79
|
+
onItemSelected={key => this.props.onMenuItemSelected(key, this.pickInfo)}
|
|
80
|
+
position={position}
|
|
81
|
+
style={{pointerEvents: 'auto'}}
|
|
82
|
+
/>
|
|
83
|
+
) : null;
|
|
84
|
+
render(ui, rootElement);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
handleContextMenu(srcEvent: MouseEvent): boolean {
|
|
88
|
+
if (
|
|
89
|
+
srcEvent &&
|
|
90
|
+
(srcEvent.button === MOUSE_BUTTON_RIGHT || srcEvent.which === MOUSE_WHICH_RIGHT)
|
|
91
|
+
) {
|
|
92
|
+
this.pickInfo =
|
|
93
|
+
this.deck?.pickObject({
|
|
94
|
+
x: srcEvent.clientX,
|
|
95
|
+
y: srcEvent.clientY
|
|
96
|
+
}) || null;
|
|
97
|
+
const menuItems = (this.pickInfo && this.props.getMenuItems?.(this.pickInfo, this)) || [];
|
|
98
|
+
const visible = menuItems.length > 0;
|
|
99
|
+
this.setProps({
|
|
100
|
+
visible,
|
|
101
|
+
position: {x: srcEvent.clientX, y: srcEvent.clientY},
|
|
102
|
+
menuItems
|
|
103
|
+
});
|
|
104
|
+
this.updateHTML();
|
|
105
|
+
srcEvent.preventDefault();
|
|
106
|
+
return visible;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
hide(): void {
|
|
113
|
+
this.setProps({visible: false});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// deck.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Widget, WidgetProps} from '@deck.gl/core';
|
|
6
|
+
import type {WidgetPlacement, Deck} from '@deck.gl/core';
|
|
7
|
+
|
|
8
|
+
/** Properties for the FpsWidget. */
|
|
9
|
+
export type FpsWidgetProps = WidgetProps & {
|
|
10
|
+
/** Widget positioning within the view. Default 'top-left'. */
|
|
11
|
+
placement?: WidgetPlacement;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Displays the average frames per second reported by the Deck instance.
|
|
16
|
+
*/
|
|
17
|
+
export class FpsWidget extends Widget<FpsWidgetProps> {
|
|
18
|
+
static defaultProps: Required<FpsWidgetProps> = {
|
|
19
|
+
...Widget.defaultProps,
|
|
20
|
+
id: 'fps',
|
|
21
|
+
placement: 'top-left'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
className = 'deck-widget-fps';
|
|
25
|
+
placement: WidgetPlacement = 'top-left';
|
|
26
|
+
|
|
27
|
+
private _lastFps: number = -1;
|
|
28
|
+
|
|
29
|
+
constructor(props: FpsWidgetProps = {}) {
|
|
30
|
+
super(props, FpsWidget.defaultProps);
|
|
31
|
+
this.placement = props.placement ?? this.placement;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setProps(props: Partial<FpsWidgetProps>): void {
|
|
35
|
+
if (props.placement) {
|
|
36
|
+
this.placement = props.placement;
|
|
37
|
+
}
|
|
38
|
+
super.setProps(props);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onRenderHTML(rootElement: HTMLElement): void {
|
|
42
|
+
const fps = this._getFps();
|
|
43
|
+
rootElement.innerText = `FPS:\n${fps}`;
|
|
44
|
+
rootElement.style.backgroundColor = 'white';
|
|
45
|
+
rootElement.style.fontFamily = 'monospace';
|
|
46
|
+
rootElement.style.fontSize = '8px';
|
|
47
|
+
rootElement.style.fontWeight = '700'; // Make font bolder on click
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onRedraw({}: {viewports: any[]; layers: any[]}): void {
|
|
51
|
+
const fps = this._getFps();
|
|
52
|
+
if (fps !== this._lastFps) {
|
|
53
|
+
this._lastFps = fps;
|
|
54
|
+
this.updateHTML();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onAdd({}: {deck: Deck<any>; viewId: string | null}): void {
|
|
59
|
+
this._lastFps = this._getFps();
|
|
60
|
+
this.updateHTML();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_getFps(): number {
|
|
64
|
+
// @ts-expect-error protected
|
|
65
|
+
return Math.round(this.deck.metrics.fps ?? 0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -3,121 +3,68 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
/* global document */
|
|
6
|
-
import {
|
|
7
|
-
_deepEqual as deepEqual,
|
|
8
|
-
_applyStyles as applyStyles,
|
|
9
|
-
_removeStyles as removeStyles
|
|
10
|
-
} from '@deck.gl/core';
|
|
11
|
-
import type {Deck, Widget, WidgetPlacement} from '@deck.gl/core';
|
|
6
|
+
import {Widget, type WidgetProps, type WidgetPlacement} from '@deck.gl/core';
|
|
12
7
|
import {render} from 'preact';
|
|
13
|
-
import {IconButton} from './components';
|
|
8
|
+
import {IconButton} from './lib/components/icon-button';
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
/* eslint-enable max-len */
|
|
11
|
+
|
|
12
|
+
export type FullscreenWidgetProps = WidgetProps & {
|
|
16
13
|
id?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Widget positioning within the view. Default 'top-left'.
|
|
19
|
-
*/
|
|
14
|
+
/** Widget positioning within the view. Default 'top-left'. */
|
|
20
15
|
placement?: WidgetPlacement;
|
|
21
|
-
/**
|
|
22
|
-
* A [compatible DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen#Compatible_elements) which should be made full screen.
|
|
23
|
-
* By default, the map container element will be made full screen.
|
|
24
|
-
*/
|
|
25
|
-
/* eslint-enable max-len */
|
|
26
|
-
container?: HTMLElement;
|
|
27
|
-
/**
|
|
28
|
-
* Tooltip message when out of fullscreen.
|
|
29
|
-
*/
|
|
16
|
+
/** Tooltip message when out of fullscreen. */
|
|
30
17
|
enterLabel?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Tooltip message when fullscreen.
|
|
33
|
-
*/
|
|
18
|
+
/** Tooltip message when fullscreen. */
|
|
34
19
|
exitLabel?: string;
|
|
35
20
|
/**
|
|
36
|
-
*
|
|
21
|
+
* A compatible DOM element which should be made full screen. By default, the map container element will be made full screen.
|
|
22
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen#Compatible_elements
|
|
37
23
|
*/
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Additional CSS class.
|
|
41
|
-
*/
|
|
42
|
-
className?: string;
|
|
24
|
+
container?: HTMLElement;
|
|
43
25
|
};
|
|
44
26
|
|
|
45
|
-
export class FullscreenWidget
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
export class FullscreenWidget extends Widget<FullscreenWidgetProps> {
|
|
28
|
+
static defaultProps: Required<FullscreenWidgetProps> = {
|
|
29
|
+
...Widget.defaultProps,
|
|
30
|
+
id: 'fullscreen',
|
|
31
|
+
placement: 'top-left',
|
|
32
|
+
enterLabel: 'Enter Fullscreen',
|
|
33
|
+
exitLabel: 'Exit Fullscreen',
|
|
34
|
+
container: undefined!
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
className = 'deck-widget-fullscreen';
|
|
48
38
|
placement: WidgetPlacement = 'top-left';
|
|
49
|
-
|
|
50
|
-
deck?: Deck<any>;
|
|
51
|
-
element?: HTMLDivElement;
|
|
52
|
-
|
|
53
39
|
fullscreen: boolean = false;
|
|
54
40
|
|
|
55
|
-
constructor(props: FullscreenWidgetProps) {
|
|
56
|
-
|
|
41
|
+
constructor(props: FullscreenWidgetProps = {}) {
|
|
42
|
+
super(props, FullscreenWidget.defaultProps);
|
|
57
43
|
this.placement = props.placement ?? this.placement;
|
|
58
|
-
|
|
59
|
-
this.props = {
|
|
60
|
-
...props,
|
|
61
|
-
enterLabel: props.enterLabel ?? 'Enter Fullscreen',
|
|
62
|
-
exitLabel: props.exitLabel ?? 'Exit Fullscreen',
|
|
63
|
-
style: props.style ?? {}
|
|
64
|
-
};
|
|
65
44
|
}
|
|
66
45
|
|
|
67
|
-
onAdd(
|
|
68
|
-
const {style, className} = this.props;
|
|
69
|
-
const el = document.createElement('div');
|
|
70
|
-
el.classList.add('deck-widget', 'deck-widget-fullscreen');
|
|
71
|
-
if (className) el.classList.add(className);
|
|
72
|
-
applyStyles(el, style);
|
|
73
|
-
this.deck = deck;
|
|
74
|
-
this.element = el;
|
|
75
|
-
this.update();
|
|
46
|
+
onAdd(): void {
|
|
76
47
|
document.addEventListener('fullscreenchange', this.onFullscreenChange.bind(this));
|
|
77
|
-
return el;
|
|
78
48
|
}
|
|
79
49
|
|
|
80
50
|
onRemove() {
|
|
81
|
-
this.deck = undefined;
|
|
82
|
-
this.element = undefined;
|
|
83
51
|
document.removeEventListener('fullscreenchange', this.onFullscreenChange.bind(this));
|
|
84
52
|
}
|
|
85
53
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const element = this.element;
|
|
89
|
-
if (!element) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const ui = (
|
|
54
|
+
onRenderHTML(rootElement: HTMLElement): void {
|
|
55
|
+
render(
|
|
94
56
|
<IconButton
|
|
95
57
|
onClick={this.handleClick.bind(this)}
|
|
96
|
-
label={this.fullscreen ? exitLabel : enterLabel}
|
|
58
|
+
label={this.fullscreen ? this.props.exitLabel : this.props.enterLabel}
|
|
97
59
|
className={this.fullscreen ? 'deck-widget-fullscreen-exit' : 'deck-widget-fullscreen-enter'}
|
|
98
|
-
|
|
60
|
+
/>,
|
|
61
|
+
rootElement
|
|
99
62
|
);
|
|
100
|
-
render(ui, element);
|
|
101
63
|
}
|
|
102
64
|
|
|
103
65
|
setProps(props: Partial<FullscreenWidgetProps>) {
|
|
104
66
|
this.placement = props.placement ?? this.placement;
|
|
105
|
-
|
|
106
|
-
const el = this.element;
|
|
107
|
-
if (el) {
|
|
108
|
-
if (oldProps.className !== props.className) {
|
|
109
|
-
if (oldProps.className) el.classList.remove(oldProps.className);
|
|
110
|
-
if (props.className) el.classList.add(props.className);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!deepEqual(oldProps.style, props.style, 1)) {
|
|
114
|
-
removeStyles(el, oldProps.style);
|
|
115
|
-
applyStyles(el, props.style);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
Object.assign(this.props, props);
|
|
120
|
-
this.update();
|
|
67
|
+
super.setProps(props);
|
|
121
68
|
}
|
|
122
69
|
|
|
123
70
|
getContainer() {
|
|
@@ -130,7 +77,7 @@ export class FullscreenWidget implements Widget<FullscreenWidgetProps> {
|
|
|
130
77
|
if (prevFullscreen !== fullscreen) {
|
|
131
78
|
this.fullscreen = !this.fullscreen;
|
|
132
79
|
}
|
|
133
|
-
this.
|
|
80
|
+
this.updateHTML();
|
|
134
81
|
}
|
|
135
82
|
|
|
136
83
|
async handleClick() {
|
|
@@ -139,7 +86,7 @@ export class FullscreenWidget implements Widget<FullscreenWidgetProps> {
|
|
|
139
86
|
} else {
|
|
140
87
|
await this.requestFullscreen();
|
|
141
88
|
}
|
|
142
|
-
this.
|
|
89
|
+
this.updateHTML();
|
|
143
90
|
}
|
|
144
91
|
|
|
145
92
|
async requestFullscreen() {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// deck.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Widget, WidgetProps} from '@deck.gl/core';
|
|
6
|
+
import type {WidgetPlacement, Viewport} from '@deck.gl/core';
|
|
7
|
+
import {FlyToInterpolator, LinearInterpolator} from '@deck.gl/core';
|
|
8
|
+
import {render} from 'preact';
|
|
9
|
+
import {DropdownMenu} from './lib/components/dropdown-menu';
|
|
10
|
+
import {type Geocoder} from './lib/geocode/geocoder';
|
|
11
|
+
import {GeocoderHistory} from './lib/geocode/geocoder-history';
|
|
12
|
+
import {
|
|
13
|
+
GoogleGeocoder,
|
|
14
|
+
MapboxGeocoder,
|
|
15
|
+
OpenCageGeocoder,
|
|
16
|
+
CoordinatesGeocoder,
|
|
17
|
+
CurrentLocationGeocoder
|
|
18
|
+
} from './lib/geocode/geocoders';
|
|
19
|
+
|
|
20
|
+
/** @todo - is the the best we can do? */
|
|
21
|
+
type ViewState = Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
const CURRENT_LOCATION = 'current';
|
|
24
|
+
|
|
25
|
+
/** Properties for the GeocoderWidget */
|
|
26
|
+
export type GeocoderWidgetProps = WidgetProps & {
|
|
27
|
+
viewId?: string;
|
|
28
|
+
/** Widget positioning within the view. Default 'top-left'. */
|
|
29
|
+
placement?: WidgetPlacement;
|
|
30
|
+
/** Tooltip message */
|
|
31
|
+
label?: string;
|
|
32
|
+
transitionDuration?: number;
|
|
33
|
+
/** Geocoding service selector, for declarative usage */
|
|
34
|
+
geocoder?: 'google' | 'mapbox' | 'opencage' | 'coordinates' | 'custom';
|
|
35
|
+
/** Custom geocoding service (Used when geocoder = 'custom') */
|
|
36
|
+
customGeocoder?: Geocoder;
|
|
37
|
+
/** API key used for geocoding services */
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
/** Whether to use geolocation @note Experimental*/
|
|
40
|
+
_geolocation?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A widget that display a text box that lets user type in a location
|
|
45
|
+
* and a button that moves the view to that location.
|
|
46
|
+
* @todo For now only supports coordinates, Could be extended with location service integrations.
|
|
47
|
+
*/
|
|
48
|
+
export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
|
|
49
|
+
static defaultProps: Required<GeocoderWidgetProps> = {
|
|
50
|
+
...Widget.defaultProps,
|
|
51
|
+
id: 'geolocate',
|
|
52
|
+
viewId: undefined!,
|
|
53
|
+
placement: 'top-left',
|
|
54
|
+
label: 'Geolocate',
|
|
55
|
+
transitionDuration: 200,
|
|
56
|
+
geocoder: 'coordinates',
|
|
57
|
+
customGeocoder: CoordinatesGeocoder,
|
|
58
|
+
apiKey: '',
|
|
59
|
+
_geolocation: false
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
className = 'deck-widget-geocoder';
|
|
63
|
+
placement: WidgetPlacement = 'top-left';
|
|
64
|
+
|
|
65
|
+
geocodeHistory = new GeocoderHistory({});
|
|
66
|
+
addressText: string = '';
|
|
67
|
+
geocoder: Geocoder = CoordinatesGeocoder;
|
|
68
|
+
|
|
69
|
+
constructor(props: GeocoderWidgetProps = {}) {
|
|
70
|
+
super(props, GeocoderWidget.defaultProps);
|
|
71
|
+
this.placement = props.placement ?? this.placement;
|
|
72
|
+
this.setProps(props);
|
|
73
|
+
this.geocoder = getGeocoder(props);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setProps(props: Partial<GeocoderWidgetProps>): void {
|
|
77
|
+
super.setProps(props);
|
|
78
|
+
this.placement = props.placement ?? this.placement;
|
|
79
|
+
this.geocoder = getGeocoder(this.props);
|
|
80
|
+
if (this.geocoder.requiresApiKey && !this.props.apiKey) {
|
|
81
|
+
throw new Error(`API key is required for the ${this.geocoder.name} geocoder`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onRenderHTML(rootElement: HTMLElement): void {
|
|
86
|
+
const menuItems = this.props._geolocation
|
|
87
|
+
? [CURRENT_LOCATION, ...this.geocodeHistory.addressHistory]
|
|
88
|
+
: [...this.geocodeHistory.addressHistory];
|
|
89
|
+
render(
|
|
90
|
+
<div
|
|
91
|
+
className="deck-widget-geocoder"
|
|
92
|
+
style={{
|
|
93
|
+
pointerEvents: 'auto',
|
|
94
|
+
display: 'flex',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
flexWrap: 'wrap' // Allows wrapping on smaller screens
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<input
|
|
100
|
+
type="text"
|
|
101
|
+
placeholder={this.geocoder.placeholderLocation ?? 'Enter address or location'}
|
|
102
|
+
value={this.geocodeHistory.addressText}
|
|
103
|
+
// @ts-expect-error event type
|
|
104
|
+
onInput={e => this.setInput(e.target?.value || '')}
|
|
105
|
+
onKeyPress={this.handleKeyPress}
|
|
106
|
+
style={{
|
|
107
|
+
flex: '1 1 auto',
|
|
108
|
+
minWidth: '200px',
|
|
109
|
+
margin: 0,
|
|
110
|
+
padding: '8px',
|
|
111
|
+
boxSizing: 'border-box'
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
<DropdownMenu
|
|
115
|
+
menuItems={menuItems}
|
|
116
|
+
onSelect={this.handleSelect}
|
|
117
|
+
style={{
|
|
118
|
+
margin: 2,
|
|
119
|
+
padding: '4px 2px',
|
|
120
|
+
boxSizing: 'border-box'
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
{this.geocodeHistory.errorText && (
|
|
124
|
+
<div className="error">{this.geocodeHistory.errorText}</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>,
|
|
127
|
+
rootElement
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setInput = (text: string) => {
|
|
132
|
+
this.addressText = text;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
handleKeyPress = e => {
|
|
136
|
+
if (e.key === 'Enter') {
|
|
137
|
+
this.handleSubmit();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
handleSelect = (address: string) => {
|
|
142
|
+
this.setInput(address);
|
|
143
|
+
this.handleSubmit();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Sync wrapper for async geocode() */
|
|
147
|
+
handleSubmit = () => {
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
149
|
+
this.geocode(this.addressText);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Perform geocoding */
|
|
153
|
+
geocode: (address: string) => Promise<void> = async address => {
|
|
154
|
+
const useGeolocation = this.props._geolocation && address === CURRENT_LOCATION;
|
|
155
|
+
const geocoder = useGeolocation ? CurrentLocationGeocoder : this.geocoder;
|
|
156
|
+
const coordinates = await this.geocodeHistory.geocode(
|
|
157
|
+
geocoder,
|
|
158
|
+
this.addressText,
|
|
159
|
+
this.props.apiKey
|
|
160
|
+
);
|
|
161
|
+
if (coordinates) {
|
|
162
|
+
this.setViewState(coordinates);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// TODO - MOVE TO WIDGETIMPL?
|
|
167
|
+
setViewState(viewState: ViewState) {
|
|
168
|
+
const viewId = this.props.viewId || (viewState?.id as string) || 'default-view';
|
|
169
|
+
const viewport = this.viewports[viewId] || {};
|
|
170
|
+
const nextViewState: ViewState = {
|
|
171
|
+
...viewport,
|
|
172
|
+
...viewState
|
|
173
|
+
};
|
|
174
|
+
if (this.props.transitionDuration > 0) {
|
|
175
|
+
nextViewState.transitionDuration = this.props.transitionDuration;
|
|
176
|
+
nextViewState.transitionInterpolator =
|
|
177
|
+
'latitude' in nextViewState ? new FlyToInterpolator() : new LinearInterpolator();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// @ts-ignore Using private method temporary until there's a public one
|
|
181
|
+
this.deck._onViewStateChange({viewId, viewState: nextViewState, interactionState: {}});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
onViewportChange(viewport: Viewport) {
|
|
185
|
+
this.viewports[viewport.id] = viewport;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
viewports: Record<string, Viewport> = {};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getGeocoder(props: {geocoder?: string; customGeocoder?: Geocoder}): Geocoder {
|
|
192
|
+
switch (props.geocoder) {
|
|
193
|
+
case 'google':
|
|
194
|
+
return GoogleGeocoder;
|
|
195
|
+
case 'mapbox':
|
|
196
|
+
return MapboxGeocoder;
|
|
197
|
+
case 'opencage':
|
|
198
|
+
return OpenCageGeocoder;
|
|
199
|
+
case 'coordinates':
|
|
200
|
+
return CoordinatesGeocoder;
|
|
201
|
+
case 'custom':
|
|
202
|
+
if (!props.customGeocoder) {
|
|
203
|
+
throw new Error('Custom geocoder is not defined');
|
|
204
|
+
}
|
|
205
|
+
return props.customGeocoder;
|
|
206
|
+
default:
|
|
207
|
+
throw new Error(`Unknown geocoder: ${props.geocoder}`);
|
|
208
|
+
}
|
|
209
|
+
}
|