@deck.gl/widgets 9.3.0-alpha.1 → 9.3.0-alpha.3
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/context-menu-widget.d.ts +7 -5
- package/dist/context-menu-widget.d.ts.map +1 -1
- package/dist/context-menu-widget.js +2 -1
- package/dist/context-menu-widget.js.map +1 -1
- package/dist/dist.dev.js +687 -555
- 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 +468 -309
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/info-widget.d.ts +1 -0
- package/dist/info-widget.d.ts.map +1 -1
- package/dist/info-widget.js +6 -0
- package/dist/info-widget.js.map +1 -1
- package/dist/lib/components/dropdown-menu.d.ts +3 -2
- package/dist/lib/components/dropdown-menu.d.ts.map +1 -1
- package/dist/lib/components/dropdown-menu.js +12 -8
- 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.js +1 -1
- package/dist/lib/components/popover.js.map +1 -1
- 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 +1 -0
- package/dist/popup-widget.d.ts.map +1 -1
- package/dist/popup-widget.js +6 -0
- package/dist/popup-widget.js.map +1 -1
- 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 +2 -1
- package/dist/stats-widget.d.ts.map +1 -1
- package/dist/stats-widget.js +37 -8
- package/dist/stats-widget.js.map +1 -1
- package/dist/stylesheet.css +50 -13
- package/dist/timeline-widget.d.ts.map +1 -1
- package/dist/timeline-widget.js +6 -1
- 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.min.js +2 -2
- package/package.json +3 -3
- package/src/context-menu-widget.tsx +9 -9
- package/src/icon-widget.tsx +69 -0
- package/src/index.ts +11 -9
- package/src/info-widget.tsx +7 -0
- package/src/lib/components/dropdown-menu.tsx +22 -15
- package/src/lib/components/icon-button.tsx +15 -3
- package/src/lib/components/popover.tsx +1 -1
- package/src/lib/data-url.ts +5 -0
- package/src/popup-widget.tsx +7 -0
- package/src/selector-widget.tsx +111 -0
- package/src/splitter-widget.tsx +281 -96
- package/src/stats-widget.tsx +50 -9
- package/src/stylesheet.css +50 -13
- package/src/timeline-widget.tsx +8 -1
- package/src/toggle-widget.tsx +101 -0
- 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/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 -33
- package/dist/lib/components/icon-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/icon-menu.tsx +0 -70
- 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,7 +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';
|
|
10
|
+
import {IconButton} from './lib/components/icon-button';
|
|
9
11
|
|
|
10
12
|
const DEFAULT_COUNT_FORMATTER = (stat: Stat): string => `${stat.name}: ${stat.count}`;
|
|
11
13
|
|
|
@@ -97,17 +99,25 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
97
99
|
super.setProps(props);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
+
onRemove() {
|
|
103
|
+
if (this.rootElement) {
|
|
104
|
+
// Make sure all preact hooks are finalized
|
|
105
|
+
render(null, this.rootElement);
|
|
106
|
+
}
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
onRenderHTML(rootElement: HTMLElement): void {
|
|
105
|
-
const stats = this._getStats();
|
|
106
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();
|
|
107
117
|
const title = this.props.title || ('id' in stats ? stats.id : null) || 'Stats';
|
|
108
118
|
const items: JSX.Element[] = [];
|
|
109
119
|
|
|
110
|
-
if (
|
|
120
|
+
if (stats) {
|
|
111
121
|
stats.forEach(stat => {
|
|
112
122
|
const lines = this._getLines(stat).split('\n');
|
|
113
123
|
if (this._resetOnUpdate && this._resetOnUpdate[stat.name]) {
|
|
@@ -132,19 +142,21 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
132
142
|
>
|
|
133
143
|
<b>{title}</b>
|
|
134
144
|
<button className="deck-widget-dropdown-button">
|
|
135
|
-
<span className=
|
|
145
|
+
<span className="deck-widget-dropdown-icon open" />
|
|
136
146
|
</button>
|
|
137
147
|
</div>
|
|
138
|
-
|
|
148
|
+
<div className="deck-widget-stats-content">{items}</div>
|
|
139
149
|
</div>,
|
|
140
150
|
rootElement
|
|
141
151
|
);
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
onRedraw(): void {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
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
|
+
}
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
|
|
@@ -173,6 +185,11 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
173
185
|
this.updateHTML();
|
|
174
186
|
};
|
|
175
187
|
|
|
188
|
+
protected _getFps = (): number => {
|
|
189
|
+
// @ts-expect-error metrics is protected
|
|
190
|
+
return Math.round(this.deck?.metrics.fps ?? 0);
|
|
191
|
+
};
|
|
192
|
+
|
|
176
193
|
protected _getLines(stat: Stat | [key: string, value: number]): string {
|
|
177
194
|
if ('count' in stat) {
|
|
178
195
|
const formatter =
|
|
@@ -189,3 +206,27 @@ export class StatsWidget extends Widget<StatsWidgetProps> {
|
|
|
189
206
|
return `${key}: ${formattedValue}`;
|
|
190
207
|
}
|
|
191
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
|
+
}
|
package/src/stylesheet.css
CHANGED
|
@@ -257,19 +257,6 @@
|
|
|
257
257
|
padding: 4px;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
/* FPS widget styles */
|
|
261
|
-
|
|
262
|
-
.deck-widget.deck-widget-fps button {
|
|
263
|
-
pointer-events: none;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.deck-widget.deck-widget-fps .text {
|
|
267
|
-
color: var(--button-text, rgb(24, 24, 26));
|
|
268
|
-
font-family: monospace;
|
|
269
|
-
font-size: 8px;
|
|
270
|
-
font-weight: 700;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
260
|
/* Loading styles */
|
|
274
261
|
@keyframes spin {
|
|
275
262
|
to {
|
|
@@ -451,6 +438,12 @@
|
|
|
451
438
|
}
|
|
452
439
|
|
|
453
440
|
/* Stats styles */
|
|
441
|
+
.deck-widget.deck-widget-stats button .text {
|
|
442
|
+
color: var(--button-text, rgb(24, 24, 26));
|
|
443
|
+
font-family: monospace;
|
|
444
|
+
font-size: 8px;
|
|
445
|
+
font-weight: 700;
|
|
446
|
+
}
|
|
454
447
|
.deck-widget-stats-container {
|
|
455
448
|
border-radius: var(--button-corner-radius, 8px);
|
|
456
449
|
display: inline-flex;
|
|
@@ -647,3 +640,47 @@
|
|
|
647
640
|
.deck-widget-scrollbar[data-placement^=bottom-] .deck-widget-range--horizontal {
|
|
648
641
|
bottom: 0;
|
|
649
642
|
}
|
|
643
|
+
|
|
644
|
+
/** Splitter */
|
|
645
|
+
.deck-widget.deck-widget-splitter {
|
|
646
|
+
position: absolute;
|
|
647
|
+
margin: 0;
|
|
648
|
+
left: 0;
|
|
649
|
+
right: 0;
|
|
650
|
+
top: 0;
|
|
651
|
+
bottom: 0;
|
|
652
|
+
}
|
|
653
|
+
.deck-widget-splitter-handle {
|
|
654
|
+
position: absolute;
|
|
655
|
+
background: var(--button-background, #fff);
|
|
656
|
+
z-index: 10;
|
|
657
|
+
pointer-events: auto;
|
|
658
|
+
box-shadow: inset 1px 1px 1px var(--button-icon-idle, #616166);
|
|
659
|
+
}
|
|
660
|
+
.deck-widget-splitter-handle.active {
|
|
661
|
+
box-shadow: var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25));
|
|
662
|
+
}
|
|
663
|
+
.deck-widget-splitter-handle:before {
|
|
664
|
+
content: ' ';
|
|
665
|
+
display: block;
|
|
666
|
+
position: absolute;
|
|
667
|
+
box-sizing: content-box;
|
|
668
|
+
width: 100%;
|
|
669
|
+
height: 100%;
|
|
670
|
+
padding: 4px;
|
|
671
|
+
margin: -4px;
|
|
672
|
+
}
|
|
673
|
+
.deck-widget-splitter-handle--horizontal {
|
|
674
|
+
top: 0;
|
|
675
|
+
bottom: 0;
|
|
676
|
+
width: 2px;
|
|
677
|
+
margin-left: -1px;
|
|
678
|
+
cursor: col-resize;
|
|
679
|
+
}
|
|
680
|
+
.deck-widget-splitter-handle--vertical {
|
|
681
|
+
left: 0;
|
|
682
|
+
right: 0;
|
|
683
|
+
height: 2px;
|
|
684
|
+
margin-top: -1px;
|
|
685
|
+
cursor: row-resize;
|
|
686
|
+
}
|
package/src/timeline-widget.tsx
CHANGED
|
@@ -151,6 +151,13 @@ export class TimelineWidget extends Widget<TimelineWidgetProps> {
|
|
|
151
151
|
|
|
152
152
|
public play(): void {
|
|
153
153
|
this.playing = true;
|
|
154
|
+
const {
|
|
155
|
+
timeRange: [min, max]
|
|
156
|
+
} = this.props;
|
|
157
|
+
if (this.currentTime >= max) {
|
|
158
|
+
this.currentTime = min;
|
|
159
|
+
this.props.onTimeChange(min);
|
|
160
|
+
}
|
|
154
161
|
this.updateHTML();
|
|
155
162
|
this.tick();
|
|
156
163
|
}
|
|
@@ -171,7 +178,7 @@ export class TimelineWidget extends Widget<TimelineWidgetProps> {
|
|
|
171
178
|
loop
|
|
172
179
|
} = this.props;
|
|
173
180
|
if (step > 0) {
|
|
174
|
-
let next = Math.
|
|
181
|
+
let next = Math.round(this.currentTime / step) * step + step;
|
|
175
182
|
if (next > max) {
|
|
176
183
|
if (this.currentTime < max) {
|
|
177
184
|
next = max;
|