@deck.gl/widgets 9.2.11 → 9.3.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.js +5 -5
- package/dist/compass-widget.js.map +1 -1
- package/dist/context-menu-widget.d.ts +27 -20
- package/dist/context-menu-widget.d.ts.map +1 -1
- package/dist/context-menu-widget.js +45 -48
- package/dist/context-menu-widget.js.map +1 -1
- package/dist/dist.dev.js +3114 -869
- package/dist/fullscreen-widget.js +1 -1
- package/dist/fullscreen-widget.js.map +1 -1
- package/dist/geocoder-widget.d.ts +2 -4
- package/dist/geocoder-widget.d.ts.map +1 -1
- package/dist/geocoder-widget.js +16 -20
- package/dist/geocoder-widget.js.map +1 -1
- package/dist/gimbal-widget.d.ts +0 -3
- package/dist/gimbal-widget.d.ts.map +1 -1
- package/dist/gimbal-widget.js +4 -22
- package/dist/gimbal-widget.js.map +1 -1
- package/dist/icon-widget.d.ts +28 -0
- package/dist/icon-widget.d.ts.map +1 -0
- package/dist/icon-widget.js +35 -0
- package/dist/icon-widget.js.map +1 -0
- package/dist/index.cjs +1271 -561
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +16 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/info-widget.d.ts +39 -17
- package/dist/info-widget.d.ts.map +1 -1
- package/dist/info-widget.js +46 -130
- package/dist/info-widget.js.map +1 -1
- package/dist/lib/components/dropdown-menu.d.ts +12 -5
- package/dist/lib/components/dropdown-menu.d.ts.map +1 -1
- package/dist/lib/components/dropdown-menu.js +20 -11
- package/dist/lib/components/dropdown-menu.js.map +1 -1
- package/dist/lib/components/icon-button.d.ts +3 -0
- package/dist/lib/components/icon-button.d.ts.map +1 -1
- package/dist/lib/components/icon-button.js +10 -2
- package/dist/lib/components/icon-button.js.map +1 -1
- package/dist/lib/components/popover.d.ts +30 -0
- package/dist/lib/components/popover.d.ts.map +1 -0
- package/dist/lib/components/popover.js +93 -0
- package/dist/lib/components/popover.js.map +1 -0
- package/dist/lib/components/range-input.d.ts +26 -0
- package/dist/lib/components/range-input.d.ts.map +1 -0
- package/dist/lib/components/range-input.js +261 -0
- package/dist/lib/components/range-input.js.map +1 -0
- package/dist/lib/components/user-content.d.ts +8 -0
- package/dist/lib/components/user-content.d.ts.map +1 -0
- package/dist/lib/components/user-content.js +15 -0
- package/dist/lib/components/user-content.js.map +1 -0
- package/dist/lib/data-url.d.ts +5 -0
- package/dist/lib/data-url.d.ts.map +1 -0
- package/dist/lib/data-url.js +7 -0
- package/dist/lib/data-url.js.map +1 -0
- package/dist/popup-widget.d.ts +64 -0
- package/dist/popup-widget.d.ts.map +1 -0
- package/dist/popup-widget.js +76 -0
- package/dist/popup-widget.js.map +1 -0
- package/dist/reset-view-widget.d.ts +1 -1
- package/dist/reset-view-widget.d.ts.map +1 -1
- package/dist/reset-view-widget.js +12 -11
- package/dist/reset-view-widget.js.map +1 -1
- package/dist/scrollbar-widget.d.ts +68 -0
- package/dist/scrollbar-widget.d.ts.map +1 -0
- package/dist/scrollbar-widget.js +181 -0
- package/dist/scrollbar-widget.js.map +1 -0
- package/dist/selector-widget.d.ts +44 -0
- package/dist/selector-widget.d.ts.map +1 -0
- package/dist/selector-widget.js +64 -0
- package/dist/selector-widget.js.map +1 -0
- package/dist/splitter-widget.d.ts +52 -11
- package/dist/splitter-widget.d.ts.map +1 -1
- package/dist/splitter-widget.js +177 -76
- package/dist/splitter-widget.js.map +1 -1
- package/dist/stats-widget.d.ts +6 -1
- package/dist/stats-widget.d.ts.map +1 -1
- package/dist/stats-widget.js +39 -10
- package/dist/stats-widget.js.map +1 -1
- package/dist/stylesheet.css +291 -39
- package/dist/theme-widget.d.ts.map +1 -1
- package/dist/theme-widget.js +7 -5
- package/dist/theme-widget.js.map +1 -1
- package/dist/themes.d.ts +30 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +28 -4
- package/dist/themes.js.map +1 -1
- package/dist/timeline-widget.d.ts +25 -6
- package/dist/timeline-widget.d.ts.map +1 -1
- package/dist/timeline-widget.js +51 -18
- package/dist/timeline-widget.js.map +1 -1
- package/dist/toggle-widget.d.ts +38 -0
- package/dist/toggle-widget.d.ts.map +1 -0
- package/dist/toggle-widget.js +47 -0
- package/dist/toggle-widget.js.map +1 -0
- package/dist/zoom-widget.d.ts +2 -8
- package/dist/zoom-widget.d.ts.map +1 -1
- package/dist/zoom-widget.js +23 -18
- package/dist/zoom-widget.js.map +1 -1
- package/dist.min.js +2 -2
- package/package.json +4 -3
- package/src/compass-widget.tsx +5 -5
- package/src/context-menu-widget.tsx +88 -78
- package/src/fullscreen-widget.tsx +1 -1
- package/src/geocoder-widget.tsx +17 -24
- package/src/gimbal-widget.tsx +4 -26
- package/src/icon-widget.tsx +69 -0
- package/src/index.ts +22 -15
- package/src/info-widget.tsx +100 -157
- package/src/lib/components/dropdown-menu.tsx +58 -29
- package/src/lib/components/icon-button.tsx +15 -3
- package/src/lib/components/popover.tsx +167 -0
- package/src/lib/components/range-input.tsx +432 -0
- package/src/lib/components/user-content.tsx +30 -0
- package/src/lib/data-url.ts +5 -0
- package/src/popup-widget.tsx +170 -0
- package/src/reset-view-widget.tsx +12 -11
- package/src/scrollbar-widget.tsx +288 -0
- package/src/selector-widget.tsx +111 -0
- package/src/splitter-widget.tsx +281 -96
- package/src/stats-widget.tsx +59 -12
- package/src/stylesheet.css +291 -39
- package/src/theme-widget.tsx +7 -5
- package/src/themes.ts +35 -4
- package/src/timeline-widget.tsx +103 -33
- package/src/toggle-widget.tsx +101 -0
- package/src/zoom-widget.tsx +25 -21
- package/dist/fps-widget.d.ts +0 -28
- package/dist/fps-widget.d.ts.map +0 -1
- package/dist/fps-widget.js +0 -51
- package/dist/fps-widget.js.map +0 -1
- package/dist/lib/components/grouped-icon-button.d.ts +0 -10
- package/dist/lib/components/grouped-icon-button.d.ts.map +0 -1
- package/dist/lib/components/grouped-icon-button.js +0 -7
- package/dist/lib/components/grouped-icon-button.js.map +0 -1
- package/dist/lib/components/icon-menu.d.ts +0 -16
- package/dist/lib/components/icon-menu.d.ts.map +0 -1
- package/dist/lib/components/icon-menu.js +0 -34
- package/dist/lib/components/icon-menu.js.map +0 -1
- package/dist/lib/components/simple-menu.d.ts +0 -16
- package/dist/lib/components/simple-menu.d.ts.map +0 -1
- package/dist/lib/components/simple-menu.js +0 -30
- package/dist/lib/components/simple-menu.js.map +0 -1
- package/dist/view-selector-widget.d.ts +0 -35
- package/dist/view-selector-widget.d.ts.map +0 -1
- package/dist/view-selector-widget.js +0 -63
- package/dist/view-selector-widget.js.map +0 -1
- package/src/fps-widget.tsx +0 -77
- package/src/lib/components/grouped-icon-button.tsx +0 -27
- package/src/lib/components/icon-menu.tsx +0 -71
- package/src/lib/components/simple-menu.tsx +0 -52
- package/src/view-selector-widget.tsx +0 -151
package/src/splitter-widget.tsx
CHANGED
|
@@ -2,22 +2,157 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {useState, useRef} from 'preact/hooks';
|
|
7
|
-
import {
|
|
5
|
+
import {JSX, render} from 'preact';
|
|
6
|
+
import {useState, useRef, useEffect} from 'preact/hooks';
|
|
7
|
+
import {
|
|
8
|
+
Widget,
|
|
9
|
+
_deepEqual as deepEqual,
|
|
10
|
+
type Deck,
|
|
11
|
+
type WidgetProps,
|
|
12
|
+
type View
|
|
13
|
+
} from '@deck.gl/core';
|
|
14
|
+
|
|
15
|
+
export type ViewLayout = {
|
|
16
|
+
/** Stacking orientation of the sub views */
|
|
17
|
+
orientation: 'vertical' | 'horizontal';
|
|
18
|
+
/** Initial instances that describe the sub views.
|
|
19
|
+
* x, y, width and height of the views' props will be overwritten by the SplitterWidget as split changes. */
|
|
20
|
+
views: [view1: View | ViewLayout, view2: View | ViewLayout];
|
|
21
|
+
/** The ratio of view1's share over the whole available height (vertical) or width (horizontal). Between 0-1.
|
|
22
|
+
* @default 0.5
|
|
23
|
+
*/
|
|
24
|
+
initialSplit?: number;
|
|
25
|
+
/** Whether the split can be changed by dragging the border between the two views.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
editable?: boolean;
|
|
29
|
+
/** Min value of the split
|
|
30
|
+
* @default 0.05
|
|
31
|
+
*/
|
|
32
|
+
minSplit?: number;
|
|
33
|
+
/** Max value of the split
|
|
34
|
+
* @default 0.95
|
|
35
|
+
*/
|
|
36
|
+
maxSplit?: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type ManagedViewLayout = {
|
|
40
|
+
id: number;
|
|
41
|
+
orientation: 'vertical' | 'horizontal';
|
|
42
|
+
views: [view1: View | ManagedViewLayout, view2: View | ManagedViewLayout];
|
|
43
|
+
split: number;
|
|
44
|
+
editable: boolean;
|
|
45
|
+
minSplit: number;
|
|
46
|
+
maxSplit: number;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function parseViewLayout(root: ViewLayout): ManagedViewLayout[] {
|
|
54
|
+
const layoutsById: ManagedViewLayout[] = [];
|
|
55
|
+
const isViewLayout = (v: View | ViewLayout): v is ViewLayout => 'views' in v;
|
|
56
|
+
function createManagedViewLayout(l: ViewLayout): ManagedViewLayout {
|
|
57
|
+
const id = layoutsById.length;
|
|
58
|
+
const minSplit = l.minSplit ?? 0.05;
|
|
59
|
+
const maxSplit = l.maxSplit ?? 0.95;
|
|
60
|
+
const split = Math.min(Math.max(l.initialSplit ?? 0.5, minSplit), maxSplit);
|
|
61
|
+
const managed: ManagedViewLayout = {
|
|
62
|
+
id,
|
|
63
|
+
orientation: l.orientation,
|
|
64
|
+
views: l.views as [view1: View | ManagedViewLayout, view2: View | ManagedViewLayout],
|
|
65
|
+
split,
|
|
66
|
+
editable: l.editable ?? true,
|
|
67
|
+
minSplit,
|
|
68
|
+
maxSplit,
|
|
69
|
+
x: 0,
|
|
70
|
+
y: 0,
|
|
71
|
+
width: 0,
|
|
72
|
+
height: 0
|
|
73
|
+
};
|
|
74
|
+
layoutsById.push(managed);
|
|
75
|
+
managed.views = [
|
|
76
|
+
isViewLayout(l.views[0]) ? createManagedViewLayout(l.views[0]) : l.views[0],
|
|
77
|
+
isViewLayout(l.views[1]) ? createManagedViewLayout(l.views[1]) : l.views[1]
|
|
78
|
+
];
|
|
79
|
+
return managed;
|
|
80
|
+
}
|
|
81
|
+
createManagedViewLayout(root);
|
|
82
|
+
return layoutsById;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function evaluateViews(root: ManagedViewLayout): View[] {
|
|
86
|
+
const views: View[] = [];
|
|
87
|
+
function evaluateViewLayout(
|
|
88
|
+
l: ManagedViewLayout,
|
|
89
|
+
x: number,
|
|
90
|
+
y: number,
|
|
91
|
+
width: number,
|
|
92
|
+
height: number
|
|
93
|
+
) {
|
|
94
|
+
l.x = x;
|
|
95
|
+
l.y = y;
|
|
96
|
+
l.width = width;
|
|
97
|
+
l.height = height;
|
|
98
|
+
|
|
99
|
+
const child1X = x;
|
|
100
|
+
const child1Y = y;
|
|
101
|
+
let child1Width = width;
|
|
102
|
+
let child1Height = height;
|
|
103
|
+
let child2X = x;
|
|
104
|
+
let child2Y = y;
|
|
105
|
+
let child2Width = width;
|
|
106
|
+
let child2Height = height;
|
|
107
|
+
|
|
108
|
+
if (l.orientation === 'horizontal') {
|
|
109
|
+
child1Width = width * l.split;
|
|
110
|
+
child2X = x + child1Width;
|
|
111
|
+
child2Width = width - child1Width;
|
|
112
|
+
} else {
|
|
113
|
+
child1Height = height * l.split;
|
|
114
|
+
child2Y = y + child1Height;
|
|
115
|
+
child2Height = height - child1Height;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const [view1, view2] = l.views;
|
|
119
|
+
if ('views' in view1) {
|
|
120
|
+
evaluateViewLayout(view1, child1X, child1Y, child1Width, child1Height);
|
|
121
|
+
} else {
|
|
122
|
+
views.push(
|
|
123
|
+
view1.clone({
|
|
124
|
+
x: `${child1X}%`,
|
|
125
|
+
y: `${child1Y}%`,
|
|
126
|
+
width: `${child1Width}%`,
|
|
127
|
+
height: `${child1Height}%`
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if ('views' in view2) {
|
|
133
|
+
evaluateViewLayout(view2, child2X, child2Y, child2Width, child2Height);
|
|
134
|
+
} else {
|
|
135
|
+
views.push(
|
|
136
|
+
view2.clone({
|
|
137
|
+
x: `${child2X}%`,
|
|
138
|
+
y: `${child2Y}%`,
|
|
139
|
+
width: `${child2Width}%`,
|
|
140
|
+
height: `${child2Height}%`
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
evaluateViewLayout(root, 0, 0, 100, 100);
|
|
147
|
+
return views;
|
|
148
|
+
}
|
|
8
149
|
|
|
9
150
|
/** Properties for the SplitterWidget */
|
|
10
151
|
export type SplitterWidgetProps = WidgetProps & {
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
/** The view id for the second view */
|
|
14
|
-
viewId2: string;
|
|
15
|
-
/** Orientation of the splitter: vertical (default) or horizontal */
|
|
16
|
-
orientation?: 'vertical' | 'horizontal';
|
|
17
|
-
/** The initial split percentage (0 to 1) for the first view, default 0.5 */
|
|
18
|
-
initialSplit?: number;
|
|
152
|
+
/** Stacking views descriptor */
|
|
153
|
+
viewLayout: ViewLayout;
|
|
19
154
|
/** Callback invoked when the splitter is dragged with the new split value */
|
|
20
|
-
onChange?: (
|
|
155
|
+
onChange?: (views: View[]) => void;
|
|
21
156
|
/** Callback invoked when dragging starts */
|
|
22
157
|
onDragStart?: () => void;
|
|
23
158
|
/** Callback invoked when dragging ends */
|
|
@@ -29,14 +164,11 @@ export type SplitterWidgetProps = WidgetProps & {
|
|
|
29
164
|
* across the deck.gl canvas. It positions itself based on the split percentage
|
|
30
165
|
* of the first view and provides callbacks when dragged.
|
|
31
166
|
*/
|
|
32
|
-
export class SplitterWidget extends Widget<SplitterWidgetProps> {
|
|
167
|
+
export class SplitterWidget extends Widget<SplitterWidgetProps, View[]> {
|
|
33
168
|
static defaultProps: Required<SplitterWidgetProps> = {
|
|
34
169
|
...Widget.defaultProps,
|
|
35
170
|
id: 'splitter-widget',
|
|
36
|
-
|
|
37
|
-
viewId2: '',
|
|
38
|
-
orientation: 'vertical',
|
|
39
|
-
initialSplit: 0.5,
|
|
171
|
+
viewLayout: undefined!,
|
|
40
172
|
onChange: () => {},
|
|
41
173
|
onDragStart: () => {},
|
|
42
174
|
onDragEnd: () => {}
|
|
@@ -44,33 +176,88 @@ export class SplitterWidget extends Widget<SplitterWidgetProps> {
|
|
|
44
176
|
|
|
45
177
|
className = 'deck-widget-splitter';
|
|
46
178
|
placement = 'fill' as const;
|
|
179
|
+
viewLayouts!: ManagedViewLayout[];
|
|
180
|
+
/** evaluated from the current viewLayouts */
|
|
181
|
+
views!: View[];
|
|
182
|
+
/** views set in the last update */
|
|
183
|
+
lastViews?: View[];
|
|
184
|
+
needsUpdate = true;
|
|
47
185
|
|
|
48
186
|
constructor(props: SplitterWidgetProps) {
|
|
49
187
|
super(props);
|
|
188
|
+
this.viewLayouts = parseViewLayout(this.props.viewLayout);
|
|
50
189
|
}
|
|
51
190
|
|
|
52
191
|
setProps(props: Partial<SplitterWidgetProps>) {
|
|
192
|
+
if (props.viewLayout && !deepEqual(props.viewLayout, this.props.viewLayout, -1)) {
|
|
193
|
+
this.viewLayouts = parseViewLayout(props.viewLayout);
|
|
194
|
+
this.views = undefined!;
|
|
195
|
+
}
|
|
53
196
|
super.setProps(props);
|
|
54
197
|
}
|
|
55
198
|
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
199
|
+
onRedraw() {
|
|
200
|
+
// Actually update DOM
|
|
201
|
+
super.updateHTML();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Usually widgets rerender their DOM elements here
|
|
205
|
+
// In this case we need the widget UI to synchronize with deck view states
|
|
206
|
+
// so we update deck props here and rerender DOM in the next onRedraw
|
|
207
|
+
updateHTML() {
|
|
208
|
+
if (!this.views) {
|
|
209
|
+
// viewLayouts has changed, re-evaluate
|
|
210
|
+
this.views = evaluateViews(this.viewLayouts[0]);
|
|
211
|
+
// we send a copy to the callback so that externally set views can be differentiated from internal
|
|
212
|
+
this.props.onChange(this.views.slice());
|
|
213
|
+
}
|
|
214
|
+
// This method is called inside deck.setProps > widgetManager.setProps > widget.setProps
|
|
215
|
+
// Calling deck.setProps immediately would cause infinite loop
|
|
216
|
+
requestAnimationFrame(() => {
|
|
217
|
+
this.doUpdate();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private doUpdate() {
|
|
222
|
+
if (this.deck) {
|
|
223
|
+
const deckViews = this.deck.props.views;
|
|
224
|
+
const isManagedExternally =
|
|
225
|
+
// is not empty
|
|
226
|
+
deckViews &&
|
|
227
|
+
// is not set by us
|
|
228
|
+
deckViews !== this.lastViews;
|
|
65
229
|
|
|
230
|
+
if (!isManagedExternally && this.lastViews !== this.views) {
|
|
231
|
+
this.lastViews = this.views;
|
|
232
|
+
this.deck.setProps({views: this.views});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private onChange(newSplit: number, layout: ManagedViewLayout) {
|
|
238
|
+
layout.split = newSplit;
|
|
239
|
+
// layout has updated, re-evaluate
|
|
240
|
+
this.views = evaluateViews(this.viewLayouts[0]);
|
|
241
|
+
// we send a copy to the callback so that externally set views can be differentiated from internal
|
|
242
|
+
this.props.onChange(this.views.slice());
|
|
243
|
+
this.doUpdate();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
onRenderHTML(rootElement: HTMLElement): void {
|
|
66
247
|
render(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
248
|
+
<>
|
|
249
|
+
{this.viewLayouts.map(
|
|
250
|
+
layout =>
|
|
251
|
+
layout.editable && (
|
|
252
|
+
<Splitter
|
|
253
|
+
{...layout}
|
|
254
|
+
onChange={newSplit => this.onChange(newSplit, layout)}
|
|
255
|
+
onDragStart={() => this.props.onDragStart()}
|
|
256
|
+
onDragEnd={() => this.props.onDragStart()}
|
|
257
|
+
/>
|
|
258
|
+
)
|
|
259
|
+
)}
|
|
260
|
+
</>,
|
|
74
261
|
rootElement
|
|
75
262
|
);
|
|
76
263
|
}
|
|
@@ -83,94 +270,92 @@ export class SplitterWidget extends Widget<SplitterWidgetProps> {
|
|
|
83
270
|
*/
|
|
84
271
|
function Splitter({
|
|
85
272
|
orientation,
|
|
86
|
-
|
|
273
|
+
x,
|
|
274
|
+
y,
|
|
275
|
+
width,
|
|
276
|
+
height,
|
|
277
|
+
split,
|
|
278
|
+
minSplit,
|
|
279
|
+
maxSplit,
|
|
87
280
|
onChange,
|
|
88
281
|
onDragStart,
|
|
89
282
|
onDragEnd
|
|
90
283
|
}: {
|
|
91
284
|
orientation: 'vertical' | 'horizontal';
|
|
92
|
-
|
|
285
|
+
x: number;
|
|
286
|
+
y: number;
|
|
287
|
+
width: number;
|
|
288
|
+
height: number;
|
|
289
|
+
split: number;
|
|
290
|
+
minSplit: number;
|
|
291
|
+
maxSplit: number;
|
|
93
292
|
onChange?: (newSplit: number) => void;
|
|
94
293
|
onDragStart?: () => void;
|
|
95
294
|
onDragEnd?: () => void;
|
|
96
295
|
}) {
|
|
97
|
-
const [
|
|
98
|
-
const dragging = useRef(false);
|
|
296
|
+
const [dragging, setDragging] = useState(false);
|
|
99
297
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
100
298
|
|
|
101
|
-
|
|
102
|
-
dragging
|
|
103
|
-
|
|
104
|
-
document.addEventListener('mousemove', handleDragging);
|
|
105
|
-
document.addEventListener('mouseup', handleDragEnd);
|
|
106
|
-
event.preventDefault();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const handleDragging = (event: MouseEvent) => {
|
|
110
|
-
if (!dragging.current || !containerRef.current) return;
|
|
111
|
-
const rect = containerRef.current.getBoundingClientRect();
|
|
112
|
-
let newSplit: number;
|
|
113
|
-
if (orientation === 'vertical') {
|
|
114
|
-
newSplit = (event.clientX - rect.left) / rect.width;
|
|
115
|
-
} else {
|
|
116
|
-
newSplit = (event.clientY - rect.top) / rect.height;
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
if (!dragging) {
|
|
301
|
+
return undefined;
|
|
117
302
|
}
|
|
118
|
-
// Clamp newSplit between 5% and 95%
|
|
119
|
-
newSplit = Math.min(Math.max(newSplit, 0.05), 0.95);
|
|
120
|
-
setSplit(newSplit);
|
|
121
|
-
onChange?.(newSplit);
|
|
122
|
-
};
|
|
123
303
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
304
|
+
const handleDragging = (event: PointerEvent) => {
|
|
305
|
+
if (!containerRef.current) return;
|
|
306
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
307
|
+
let newSplit: number;
|
|
308
|
+
if (orientation === 'horizontal') {
|
|
309
|
+
newSplit = (event.clientX - rect.left) / rect.width;
|
|
310
|
+
} else {
|
|
311
|
+
newSplit = (event.clientY - rect.top) / rect.height;
|
|
312
|
+
}
|
|
313
|
+
// Clamp newSplit between 5% and 95%
|
|
314
|
+
newSplit = Math.min(Math.max(newSplit, minSplit), maxSplit);
|
|
315
|
+
onChange?.(newSplit);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const handleDragEnd = () => {
|
|
319
|
+
onDragEnd?.();
|
|
320
|
+
setDragging(false);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
document.addEventListener('pointermove', handleDragging);
|
|
324
|
+
document.addEventListener('pointerup', handleDragEnd);
|
|
325
|
+
document.addEventListener('pointerleave', handleDragEnd);
|
|
326
|
+
|
|
327
|
+
return () => {
|
|
328
|
+
document.removeEventListener('pointermove', handleDragging);
|
|
329
|
+
document.removeEventListener('pointerup', handleDragEnd);
|
|
330
|
+
document.removeEventListener('pointerleave', handleDragEnd);
|
|
331
|
+
};
|
|
332
|
+
}, [dragging]);
|
|
333
|
+
|
|
334
|
+
const handleDragStart = (event: PointerEvent) => {
|
|
335
|
+
setDragging(true);
|
|
336
|
+
onDragStart?.();
|
|
337
|
+
event.preventDefault();
|
|
130
338
|
};
|
|
131
339
|
|
|
132
340
|
// The splitter line style based on orientation and the current split percentage.
|
|
133
|
-
const splitterStyle:
|
|
134
|
-
orientation === '
|
|
135
|
-
? {
|
|
136
|
-
position: 'absolute',
|
|
137
|
-
top: 0,
|
|
138
|
-
bottom: 0,
|
|
139
|
-
left: `${split * 100}%`,
|
|
140
|
-
width: '4px',
|
|
141
|
-
cursor: 'col-resize',
|
|
142
|
-
background: '#ccc',
|
|
143
|
-
zIndex: 10,
|
|
144
|
-
pointerEvents: 'auto',
|
|
145
|
-
boxShadow: 'inset -1px 0 0 white, inset 1px 0 0 white'
|
|
146
|
-
}
|
|
147
|
-
: {
|
|
148
|
-
position: 'absolute',
|
|
149
|
-
left: 0,
|
|
150
|
-
right: 0,
|
|
151
|
-
top: `${split * 100}%`,
|
|
152
|
-
height: '4px',
|
|
153
|
-
cursor: 'row-resize',
|
|
154
|
-
background: '#ccc',
|
|
155
|
-
zIndex: 10,
|
|
156
|
-
pointerEvents: 'auto',
|
|
157
|
-
boxShadow: 'inset -1px 0 0 white, inset 1px 0 0 white'
|
|
158
|
-
};
|
|
341
|
+
const splitterStyle: JSX.CSSProperties =
|
|
342
|
+
orientation === 'horizontal' ? {left: `${split * 100}%`} : {top: `${split * 100}%`};
|
|
159
343
|
|
|
160
344
|
// Container style to fill the entire deck.gl canvas.
|
|
161
|
-
const containerStyle:
|
|
345
|
+
const containerStyle: JSX.CSSProperties = {
|
|
162
346
|
position: 'absolute',
|
|
163
|
-
top:
|
|
164
|
-
left:
|
|
165
|
-
|
|
166
|
-
|
|
347
|
+
top: `${y}%`,
|
|
348
|
+
left: `${x}%`,
|
|
349
|
+
width: `${width}%`,
|
|
350
|
+
height: `${height}%`
|
|
167
351
|
};
|
|
168
352
|
|
|
169
353
|
return (
|
|
170
354
|
<div ref={containerRef} style={containerStyle}>
|
|
171
355
|
<div
|
|
356
|
+
className={`deck-widget-splitter-handle deck-widget-splitter-handle--${orientation} ${dragging ? 'active' : ''}`}
|
|
172
357
|
style={splitterStyle}
|
|
173
|
-
|
|
358
|
+
onPointerDown={handleDragStart} // Use the appropriate Preact event type.
|
|
174
359
|
/>
|
|
175
360
|
</div>
|
|
176
361
|
);
|
package/src/stats-widget.tsx
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
import {Widget, type WidgetPlacement, type WidgetProps} from '@deck.gl/core';
|
|
6
6
|
import {luma} from '@luma.gl/core';
|
|
7
7
|
import {render} from 'preact';
|
|
8
|
+
import {useEffect, useState} from 'preact/hooks';
|
|
8
9
|
import type {Stats, Stat} from '@probe.gl/stats';
|
|
9
|
-
|
|
10
|
-
const RIGHT_ARROW = '\u25b6';
|
|
11
|
-
const DOWN_ARROW = '\u2b07';
|
|
10
|
+
import {IconButton} from './lib/components/icon-button';
|
|
12
11
|
|
|
13
12
|
const DEFAULT_COUNT_FORMATTER = (stat: Stat): string => `${stat.name}: ${stat.count}`;
|
|
14
13
|
|
|
@@ -36,6 +35,10 @@ export type StatsWidgetProps = WidgetProps & {
|
|
|
36
35
|
viewId?: string | null;
|
|
37
36
|
/** Type of stats to display. */
|
|
38
37
|
type?: 'deck' | 'luma' | 'device' | 'custom';
|
|
38
|
+
/** Expand the stats UI by default.
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
defaultIsExpanded?: boolean;
|
|
39
42
|
/** Stats object to visualize. */
|
|
40
43
|
stats?: Stats;
|
|
41
44
|
/** Title shown in the header of the pop-up. Defaults to stats.id. */
|
|
@@ -55,6 +58,7 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
55
58
|
type: 'deck',
|
|
56
59
|
placement: 'top-left',
|
|
57
60
|
viewId: null,
|
|
61
|
+
defaultIsExpanded: false,
|
|
58
62
|
stats: undefined!,
|
|
59
63
|
title: 'Stats',
|
|
60
64
|
framesPerUpdate: 1,
|
|
@@ -75,6 +79,7 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
75
79
|
super(props);
|
|
76
80
|
this._formatters = {...DEFAULT_FORMATTERS};
|
|
77
81
|
this._resetOnUpdate = {...this.props.resetOnUpdate};
|
|
82
|
+
this.collapsed = !props.defaultIsExpanded;
|
|
78
83
|
this.setProps(props);
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -94,17 +99,25 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
94
99
|
super.setProps(props);
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
this.
|
|
102
|
+
onRemove() {
|
|
103
|
+
if (this.rootElement) {
|
|
104
|
+
// Make sure all preact hooks are finalized
|
|
105
|
+
render(null, this.rootElement);
|
|
106
|
+
}
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
onRenderHTML(rootElement: HTMLElement): void {
|
|
102
|
-
const stats = this._getStats();
|
|
103
110
|
const collapsed = this.collapsed;
|
|
111
|
+
if (collapsed) {
|
|
112
|
+
render(<FpsIcon getFps={this._getFps} onClick={this._toggleCollapsed} />, rootElement);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const stats = this._getStats();
|
|
104
117
|
const title = this.props.title || ('id' in stats ? stats.id : null) || 'Stats';
|
|
105
118
|
const items: JSX.Element[] = [];
|
|
106
119
|
|
|
107
|
-
if (
|
|
120
|
+
if (stats) {
|
|
108
121
|
stats.forEach(stat => {
|
|
109
122
|
const lines = this._getLines(stat).split('\n');
|
|
110
123
|
if (this._resetOnUpdate && this._resetOnUpdate[stat.name]) {
|
|
@@ -127,18 +140,23 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
127
140
|
style={{cursor: 'pointer', pointerEvents: 'auto'}}
|
|
128
141
|
onClick={this._toggleCollapsed}
|
|
129
142
|
>
|
|
130
|
-
{
|
|
143
|
+
<b>{title}</b>
|
|
144
|
+
<button className="deck-widget-dropdown-button">
|
|
145
|
+
<span className="deck-widget-dropdown-icon open" />
|
|
146
|
+
</button>
|
|
131
147
|
</div>
|
|
132
|
-
|
|
148
|
+
<div className="deck-widget-stats-content">{items}</div>
|
|
133
149
|
</div>,
|
|
134
150
|
rootElement
|
|
135
151
|
);
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
onRedraw(): void {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.
|
|
155
|
+
if (!this.collapsed) {
|
|
156
|
+
const framesPerUpdate = Math.max(1, this.props.framesPerUpdate || 1);
|
|
157
|
+
if (this._counter++ % framesPerUpdate === 0) {
|
|
158
|
+
this.updateHTML();
|
|
159
|
+
}
|
|
142
160
|
}
|
|
143
161
|
}
|
|
144
162
|
|
|
@@ -167,6 +185,11 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
167
185
|
this.updateHTML();
|
|
168
186
|
};
|
|
169
187
|
|
|
188
|
+
protected _getFps = (): number => {
|
|
189
|
+
// @ts-expect-error metrics is protected
|
|
190
|
+
return Math.round(this.deck?.metrics.fps ?? 0);
|
|
191
|
+
};
|
|
192
|
+
|
|
170
193
|
protected _getLines(stat: Stat | [key: string, value: number]): string {
|
|
171
194
|
if ('count' in stat) {
|
|
172
195
|
const formatter =
|
|
@@ -183,3 +206,27 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
183
206
|
return `${key}: ${formattedValue}`;
|
|
184
207
|
}
|
|
185
208
|
}
|
|
209
|
+
|
|
210
|
+
function FpsIcon({getFps, onClick}: {getFps: () => number; onClick: () => void}) {
|
|
211
|
+
const [fps, setFps] = useState(getFps());
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const onUpdate = () => {
|
|
214
|
+
setFps(getFps());
|
|
215
|
+
timer = requestAnimationFrame(onUpdate);
|
|
216
|
+
};
|
|
217
|
+
let timer = requestAnimationFrame(onUpdate);
|
|
218
|
+
return () => {
|
|
219
|
+
cancelAnimationFrame(timer);
|
|
220
|
+
};
|
|
221
|
+
}, [getFps]);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<IconButton onClick={onClick}>
|
|
225
|
+
<div className="text">
|
|
226
|
+
FPS
|
|
227
|
+
<br />
|
|
228
|
+
{fps}
|
|
229
|
+
</div>
|
|
230
|
+
</IconButton>
|
|
231
|
+
);
|
|
232
|
+
}
|