@rerun-io/web-viewer 0.17.0-alpha.1 → 0.17.0-alpha.101
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/README.md +1 -1
- package/index.d.ts +174 -253
- package/index.d.ts.map +1 -28
- package/index.js +354 -417
- package/index.js.map +1 -0
- package/index.ts +582 -0
- package/package.json +6 -4
- package/re_viewer.d.ts +2 -1
- package/re_viewer_bg.js +81 -80
- package/re_viewer_bg.wasm +0 -0
- package/re_viewer_bg.wasm.d.ts +6 -6
- package/tsconfig.json +7 -8
package/index.js
CHANGED
|
@@ -1,450 +1,387 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** @type {(typeof import("./re_viewer.js").WebHandle) | null} */
|
|
4
|
-
let WebHandle = null;
|
|
5
|
-
|
|
6
|
-
/** @returns {Promise<(typeof import("./re_viewer.js").WebHandle)>} */
|
|
1
|
+
let WebHandleConstructor = null;
|
|
7
2
|
async function load() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
if (WebHandleConstructor) {
|
|
4
|
+
return WebHandleConstructor;
|
|
5
|
+
}
|
|
6
|
+
WebHandleConstructor = (await import("./re_viewer.js")).WebHandle;
|
|
7
|
+
return WebHandleConstructor;
|
|
13
8
|
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Used to prevent multiple viewers from being fullscreen at the same time.
|
|
17
|
-
*
|
|
18
|
-
* @type {(() => void) | null}
|
|
19
|
-
*/
|
|
20
9
|
let _minimize_current_fullscreen_viewer = null;
|
|
21
|
-
|
|
22
|
-
/** @returns {string} */
|
|
23
10
|
function randomId() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
const bytes = new Uint8Array(16);
|
|
12
|
+
crypto.getRandomValues(bytes);
|
|
13
|
+
return Array.from(bytes)
|
|
14
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
15
|
+
.join("");
|
|
29
16
|
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @typedef {"top" | "blueprint" | "selection" | "time"} Panel
|
|
33
|
-
*
|
|
34
|
-
* @typedef {"hidden" | "collapsed" | "expanded"} PanelState
|
|
35
|
-
*
|
|
36
|
-
* @typedef {"webgpu" | "webgl"} Backend
|
|
37
|
-
*
|
|
38
|
-
* @typedef {{
|
|
39
|
-
* width: string; height: string;
|
|
40
|
-
* top: string; left: string;
|
|
41
|
-
* bottom: string; right: string;
|
|
42
|
-
* }} CanvasRect
|
|
43
|
-
*
|
|
44
|
-
* @typedef {{
|
|
45
|
-
* canvas: CanvasRect & { position: string; transition: string; };
|
|
46
|
-
* document: { overflow: string };
|
|
47
|
-
* }} CanvasStyle
|
|
48
|
-
*
|
|
49
|
-
* @typedef {{ on: false; saved_style: null; saved_rect: null }} FullscreenOff
|
|
50
|
-
*
|
|
51
|
-
* @typedef {{ on: true; saved_style: CanvasStyle; saved_rect: DOMRect }} FullscreenOn
|
|
52
|
-
*
|
|
53
|
-
* @typedef {(FullscreenOff | FullscreenOn)} FullscreenState
|
|
54
|
-
*
|
|
55
|
-
* @typedef WebViewerOptions
|
|
56
|
-
* @property {string} [manifest_url] Use a different example manifest.
|
|
57
|
-
* @property {Backend} [render_backend] Force the viewer to use a specific rendering backend.
|
|
58
|
-
* @property {boolean} [hide_welcome_screen] Whether to hide the welcome screen in favor of a simpler one.
|
|
59
|
-
* @property {boolean} [allow_fullscreen] Whether to allow the viewer to enter fullscreen mode.
|
|
60
|
-
*
|
|
61
|
-
* @typedef FullscreenOptions
|
|
62
|
-
* @property {() => boolean} get_state
|
|
63
|
-
* @property {() => void} on_toggle
|
|
64
|
-
*/
|
|
65
|
-
|
|
66
17
|
export class WebViewer {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @type {FullscreenState}
|
|
80
|
-
*/
|
|
81
|
-
#fullscreen_state = {
|
|
82
|
-
on: false,
|
|
83
|
-
saved_style: null,
|
|
84
|
-
saved_rect: null,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
#allow_fullscreen = false;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Start the viewer.
|
|
91
|
-
*
|
|
92
|
-
* @param {string | string[] | null} [rrd] URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
93
|
-
* @param {HTMLElement | null} [parent] The element to attach the canvas onto.
|
|
94
|
-
* @param {WebViewerOptions | null} [options] Whether to hide the welcome screen.
|
|
95
|
-
* @returns {Promise<void>}
|
|
96
|
-
*/
|
|
97
|
-
async start(rrd, parent, options) {
|
|
98
|
-
parent ??= document.body;
|
|
99
|
-
options ??= {};
|
|
100
|
-
|
|
101
|
-
this.#allow_fullscreen = options.allow_fullscreen || false;
|
|
102
|
-
|
|
103
|
-
if (this.#state !== "stopped") return;
|
|
104
|
-
this.#state = "starting";
|
|
105
|
-
|
|
106
|
-
this.#canvas = document.createElement("canvas");
|
|
107
|
-
this.#canvas.id = this.#id;
|
|
108
|
-
parent.append(this.#canvas);
|
|
109
|
-
|
|
18
|
+
#id = randomId();
|
|
19
|
+
#handle = null;
|
|
20
|
+
#canvas = null;
|
|
21
|
+
#state = "stopped";
|
|
22
|
+
#fullscreen = false;
|
|
23
|
+
#allow_fullscreen = false;
|
|
24
|
+
constructor() {
|
|
25
|
+
injectStyle();
|
|
26
|
+
setupGlobalEventListeners();
|
|
27
|
+
}
|
|
110
28
|
/**
|
|
111
|
-
*
|
|
112
|
-
* @property {string} [url]
|
|
113
|
-
* @property {string} [manifest_url]
|
|
114
|
-
* @property {Backend} [render_backend]
|
|
115
|
-
* @property {boolean} [hide_welcome_screen]
|
|
116
|
-
* @property {Partial<{[K in Panel]: PanelState}>} [panel_state_overrides]
|
|
117
|
-
* @property {FullscreenOptions} [fullscreen]
|
|
29
|
+
* Start the viewer.
|
|
118
30
|
*
|
|
119
|
-
* @
|
|
120
|
-
* @
|
|
31
|
+
* @param rrd URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
32
|
+
* @param parent The element to attach the canvas onto.
|
|
33
|
+
* @param options Whether to hide the welcome screen.
|
|
121
34
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
35
|
+
async start(rrd, parent, options) {
|
|
36
|
+
parent ??= document.body;
|
|
37
|
+
options ??= {};
|
|
38
|
+
this.#allow_fullscreen = options.allow_fullscreen || false;
|
|
39
|
+
if (this.#state !== "stopped")
|
|
40
|
+
return;
|
|
41
|
+
this.#state = "starting";
|
|
42
|
+
this.#canvas = document.createElement("canvas");
|
|
43
|
+
this.#canvas.id = this.#id;
|
|
44
|
+
parent.append(this.#canvas);
|
|
45
|
+
let WebHandle_class = await load();
|
|
46
|
+
if (this.#state !== "starting")
|
|
47
|
+
return;
|
|
48
|
+
const fullscreen = this.#allow_fullscreen
|
|
49
|
+
? {
|
|
50
|
+
get_state: () => this.#fullscreen,
|
|
51
|
+
on_toggle: () => this.toggle_fullscreen(),
|
|
52
|
+
}
|
|
53
|
+
: undefined;
|
|
54
|
+
this.#handle = new WebHandle_class({ ...options, fullscreen });
|
|
55
|
+
await this.#handle.start(this.#canvas.id);
|
|
56
|
+
if (this.#state !== "starting")
|
|
57
|
+
return;
|
|
58
|
+
if (this.#handle.has_panicked()) {
|
|
59
|
+
throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
|
|
130
60
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (this.#handle.has_panicked()) {
|
|
138
|
-
throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
|
|
61
|
+
this.#state = "ready";
|
|
62
|
+
this.#dispatch_event("ready");
|
|
63
|
+
if (rrd) {
|
|
64
|
+
this.open(rrd);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
139
67
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
68
|
+
#event_map = new Map();
|
|
69
|
+
#dispatch_event(event, value) {
|
|
70
|
+
const callbacks = this.#event_map.get(event);
|
|
71
|
+
if (callbacks) {
|
|
72
|
+
for (const [callback, { once }] of [...callbacks.entries()]) {
|
|
73
|
+
callback(value);
|
|
74
|
+
if (once)
|
|
75
|
+
callbacks.delete(callback);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
144
78
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* Returns `true` if the viewer is ready to connect to data sources.
|
|
151
|
-
*/
|
|
152
|
-
get ready() {
|
|
153
|
-
return this.#state === "ready";
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Open a recording.
|
|
158
|
-
*
|
|
159
|
-
* The viewer must have been started via `WebViewer.start`.
|
|
160
|
-
*
|
|
161
|
-
* @see {WebViewer.start}
|
|
162
|
-
*
|
|
163
|
-
* @param {string | string[]} rrd URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
164
|
-
* @param {{ follow_if_http?: boolean }} options
|
|
165
|
-
* - follow_if_http: Whether Rerun should open the resource in "Following" mode when streaming
|
|
166
|
-
* from an HTTP url. Defaults to `false`. Ignored for non-HTTP URLs.
|
|
167
|
-
*/
|
|
168
|
-
open(rrd, options = {}) {
|
|
169
|
-
if (!this.#handle) {
|
|
170
|
-
throw new Error(`attempted to open \`${rrd}\` in a stopped viewer`);
|
|
79
|
+
on(event, callback) {
|
|
80
|
+
const callbacks = this.#event_map.get(event) ?? new Map();
|
|
81
|
+
callbacks.set(callback, { once: false });
|
|
82
|
+
this.#event_map.set(event, callbacks);
|
|
83
|
+
return () => callbacks.delete(callback);
|
|
171
84
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
85
|
+
once(event, callback) {
|
|
86
|
+
const callbacks = this.#event_map.get(event) ?? new Map();
|
|
87
|
+
callbacks.set(callback, { once: true });
|
|
88
|
+
this.#event_map.set(event, callbacks);
|
|
89
|
+
return () => callbacks.delete(callback);
|
|
178
90
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
*
|
|
188
|
-
* @param {string | string[]} rrd URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
189
|
-
*/
|
|
190
|
-
close(rrd) {
|
|
191
|
-
if (!this.#handle) {
|
|
192
|
-
throw new Error(`attempted to close \`${rrd}\` in a stopped viewer`);
|
|
91
|
+
off(event, callback) {
|
|
92
|
+
const callbacks = this.#event_map.get(event);
|
|
93
|
+
if (callbacks) {
|
|
94
|
+
callbacks.delete(callback);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.warn("Attempted to call `WebViewer.off` with an unregistered callback. Are you using ");
|
|
98
|
+
}
|
|
193
99
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns `true` if the viewer is ready to connect to data sources.
|
|
102
|
+
*/
|
|
103
|
+
get ready() {
|
|
104
|
+
return this.#state === "ready";
|
|
200
105
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Open a recording.
|
|
108
|
+
*
|
|
109
|
+
* The viewer must have been started via `WebViewer.start`.
|
|
110
|
+
*
|
|
111
|
+
* @param rrd URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
112
|
+
* @param options
|
|
113
|
+
* - follow_if_http: Whether Rerun should open the resource in "Following" mode when streaming
|
|
114
|
+
* from an HTTP url. Defaults to `false`. Ignored for non-HTTP URLs.
|
|
115
|
+
*/
|
|
116
|
+
open(rrd, options = {}) {
|
|
117
|
+
if (!this.#handle) {
|
|
118
|
+
throw new Error(`attempted to open \`${rrd}\` in a stopped viewer`);
|
|
119
|
+
}
|
|
120
|
+
const urls = Array.isArray(rrd) ? rrd : [rrd];
|
|
121
|
+
for (const url of urls) {
|
|
122
|
+
this.#handle.add_receiver(url, options.follow_if_http);
|
|
123
|
+
if (this.#handle.has_panicked()) {
|
|
124
|
+
throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
213
127
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
* @param {string} channel_name used to identify the channel.
|
|
233
|
-
*
|
|
234
|
-
* @returns {LogChannel}
|
|
235
|
-
*/
|
|
236
|
-
open_channel(channel_name = "rerun-io/web-viewer") {
|
|
237
|
-
if (!this.#handle) {
|
|
238
|
-
throw new Error(
|
|
239
|
-
`attempted to open channel \"${channel_name}\" in a stopped web viewer`,
|
|
240
|
-
);
|
|
128
|
+
/**
|
|
129
|
+
* Close a recording.
|
|
130
|
+
*
|
|
131
|
+
* The viewer must have been started via `WebViewer.start`.
|
|
132
|
+
*
|
|
133
|
+
* @param rrd URLs to `.rrd` files or WebSocket connections to our SDK.
|
|
134
|
+
*/
|
|
135
|
+
close(rrd) {
|
|
136
|
+
if (!this.#handle) {
|
|
137
|
+
throw new Error(`attempted to close \`${rrd}\` in a stopped viewer`);
|
|
138
|
+
}
|
|
139
|
+
const urls = Array.isArray(rrd) ? rrd : [rrd];
|
|
140
|
+
for (const url of urls) {
|
|
141
|
+
this.#handle.remove_receiver(url);
|
|
142
|
+
if (this.#handle.has_panicked()) {
|
|
143
|
+
throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
241
146
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return new LogChannel(on_send, on_close, get_state);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Force a panel to a specific state.
|
|
266
|
-
*
|
|
267
|
-
* @param {Panel} panel
|
|
268
|
-
* @param {PanelState} state
|
|
269
|
-
*/
|
|
270
|
-
override_panel_state(panel, state) {
|
|
271
|
-
if (!this.#handle) {
|
|
272
|
-
throw new Error(
|
|
273
|
-
`attempted to set ${panel} panel to ${state} in a stopped web viewer`,
|
|
274
|
-
);
|
|
147
|
+
/**
|
|
148
|
+
* Stop the viewer, freeing all associated memory.
|
|
149
|
+
*
|
|
150
|
+
* The same viewer instance may be started multiple times.
|
|
151
|
+
*/
|
|
152
|
+
stop() {
|
|
153
|
+
if (this.#state === "stopped")
|
|
154
|
+
return;
|
|
155
|
+
if (this.#allow_fullscreen && this.#canvas && this.#fullscreen) {
|
|
156
|
+
this.#minimize();
|
|
157
|
+
}
|
|
158
|
+
this.#state = "stopped";
|
|
159
|
+
this.#canvas?.remove();
|
|
160
|
+
this.#handle?.destroy();
|
|
161
|
+
this.#handle?.free();
|
|
162
|
+
this.#canvas = null;
|
|
163
|
+
this.#handle = null;
|
|
164
|
+
this.#fullscreen = false;
|
|
165
|
+
this.#allow_fullscreen = false;
|
|
275
166
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Opens a new channel for sending log messages.
|
|
169
|
+
*
|
|
170
|
+
* The channel can be used to incrementally push `rrd` chunks into the viewer.
|
|
171
|
+
*
|
|
172
|
+
* @param channel_name used to identify the channel.
|
|
173
|
+
*/
|
|
174
|
+
open_channel(channel_name = "rerun-io/web-viewer") {
|
|
175
|
+
if (!this.#handle) {
|
|
176
|
+
throw new Error(`attempted to open channel \"${channel_name}\" in a stopped web viewer`);
|
|
177
|
+
}
|
|
178
|
+
const id = crypto.randomUUID();
|
|
179
|
+
this.#handle.open_channel(id, channel_name);
|
|
180
|
+
const on_send = (/** @type {Uint8Array} */ data) => {
|
|
181
|
+
if (!this.#handle) {
|
|
182
|
+
throw new Error(`attempted to send data through channel \"${channel_name}\" to a stopped web viewer`);
|
|
183
|
+
}
|
|
184
|
+
this.#handle.send_rrd_to_channel(id, data);
|
|
185
|
+
};
|
|
186
|
+
const on_close = () => {
|
|
187
|
+
if (!this.#handle) {
|
|
188
|
+
throw new Error(`attempted to send data through channel \"${channel_name}\" to a stopped web viewer`);
|
|
189
|
+
}
|
|
190
|
+
this.#handle.close_channel(id);
|
|
191
|
+
};
|
|
192
|
+
const get_state = () => this.#state;
|
|
193
|
+
return new LogChannel(on_send, on_close, get_state);
|
|
287
194
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
*
|
|
300
|
-
* When fullscreen mode is toggled off, the style is restored to its previous values.
|
|
301
|
-
*
|
|
302
|
-
* When fullscreen mode is toggled on, any other instance of the viewer on the page
|
|
303
|
-
* which is already in fullscreen mode is toggled off. This means that it doesn't
|
|
304
|
-
* have to be tracked manually.
|
|
305
|
-
*
|
|
306
|
-
* This functionality can also be directly accessed in the viewer:
|
|
307
|
-
* - The maximize/minimize top panel button
|
|
308
|
-
* - The `Toggle fullscreen` UI command (accessible via the command palette, CTRL+P)
|
|
309
|
-
*/
|
|
310
|
-
toggle_fullscreen() {
|
|
311
|
-
if (!this.#allow_fullscreen) return;
|
|
312
|
-
|
|
313
|
-
if (!this.#handle || !this.#canvas) {
|
|
314
|
-
throw new Error(
|
|
315
|
-
`attempted to toggle fullscreen mode in a stopped web viewer`,
|
|
316
|
-
);
|
|
195
|
+
/**
|
|
196
|
+
* Force a panel to a specific state.
|
|
197
|
+
*
|
|
198
|
+
* @param panel
|
|
199
|
+
* @param state
|
|
200
|
+
*/
|
|
201
|
+
override_panel_state(panel, state) {
|
|
202
|
+
if (!this.#handle) {
|
|
203
|
+
throw new Error(`attempted to set ${panel} panel to ${state} in a stopped web viewer`);
|
|
204
|
+
}
|
|
205
|
+
this.#handle.override_panel_state(panel, state);
|
|
317
206
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Toggle panel overrides set via `override_panel_state`.
|
|
209
|
+
*
|
|
210
|
+
* @param value - set to a specific value. Toggles the previous value if not provided.
|
|
211
|
+
*/
|
|
212
|
+
toggle_panel_overrides(value) {
|
|
213
|
+
if (!this.#handle) {
|
|
214
|
+
throw new Error(`attempted to toggle panel overrides in a stopped web viewer`);
|
|
215
|
+
}
|
|
216
|
+
this.#handle.toggle_panel_overrides(value);
|
|
324
217
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Toggle fullscreen mode.
|
|
220
|
+
*
|
|
221
|
+
* This does nothing if `allow_fullscreen` was not set to `true` when starting the viewer.
|
|
222
|
+
*
|
|
223
|
+
* Fullscreen mode works by updating the underlying `<canvas>` element's `style`:
|
|
224
|
+
* - `position` to `fixed`
|
|
225
|
+
* - width/height/top/left to cover the entire viewport
|
|
226
|
+
*
|
|
227
|
+
* When fullscreen mode is toggled off, the style is restored to its previous values.
|
|
228
|
+
*
|
|
229
|
+
* When fullscreen mode is toggled on, any other instance of the viewer on the page
|
|
230
|
+
* which is already in fullscreen mode is toggled off. This means that it doesn't
|
|
231
|
+
* have to be tracked manually.
|
|
232
|
+
*
|
|
233
|
+
* This functionality can also be directly accessed in the viewer:
|
|
234
|
+
* - The maximize/minimize top panel button
|
|
235
|
+
* - The `Toggle fullscreen` UI command (accessible via the command palette, CTRL+P)
|
|
236
|
+
*/
|
|
237
|
+
toggle_fullscreen() {
|
|
238
|
+
if (!this.#allow_fullscreen)
|
|
239
|
+
return;
|
|
240
|
+
if (!this.#handle || !this.#canvas) {
|
|
241
|
+
throw new Error(`attempted to toggle fullscreen mode in a stopped web viewer`);
|
|
242
|
+
}
|
|
243
|
+
if (this.#fullscreen) {
|
|
244
|
+
this.#minimize();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.#maximize();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
#minimize = () => { };
|
|
251
|
+
#maximize = () => {
|
|
252
|
+
_minimize_current_fullscreen_viewer?.();
|
|
253
|
+
const canvas = this.#canvas;
|
|
254
|
+
const rect = canvas.getBoundingClientRect();
|
|
255
|
+
const sync_style_to_rect = () => {
|
|
256
|
+
canvas.style.left = rect.left + "px";
|
|
257
|
+
canvas.style.top = rect.top + "px";
|
|
258
|
+
canvas.style.width = rect.width + "px";
|
|
259
|
+
canvas.style.height = rect.height + "px";
|
|
260
|
+
};
|
|
261
|
+
const undo_style = () => canvas.removeAttribute("style");
|
|
262
|
+
const transition = (callback) => setTimeout(() => requestAnimationFrame(callback), transition_delay_ms);
|
|
263
|
+
canvas.classList.add(classes.fullscreen_base, classes.fullscreen_rect);
|
|
264
|
+
sync_style_to_rect();
|
|
348
265
|
requestAnimationFrame(() => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
canvas.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
right: style.right,
|
|
379
|
-
transition: style.transition,
|
|
380
|
-
},
|
|
381
|
-
document: { overflow: document.body.style.overflow },
|
|
266
|
+
if (!this.#fullscreen)
|
|
267
|
+
return;
|
|
268
|
+
canvas.classList.add(classes.transition);
|
|
269
|
+
transition(() => {
|
|
270
|
+
if (!this.#fullscreen)
|
|
271
|
+
return;
|
|
272
|
+
undo_style();
|
|
273
|
+
document.body.classList.add(classes.hide_scrollbars);
|
|
274
|
+
document.documentElement.classList.add(classes.hide_scrollbars);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
this.#minimize = () => {
|
|
278
|
+
document.body.classList.remove(classes.hide_scrollbars);
|
|
279
|
+
document.documentElement.classList.remove(classes.hide_scrollbars);
|
|
280
|
+
sync_style_to_rect();
|
|
281
|
+
canvas.classList.remove(classes.fullscreen_rect);
|
|
282
|
+
transition(() => {
|
|
283
|
+
if (this.#fullscreen)
|
|
284
|
+
return;
|
|
285
|
+
undo_style();
|
|
286
|
+
canvas.classList.remove(classes.fullscreen_base, classes.transition);
|
|
287
|
+
});
|
|
288
|
+
_minimize_current_fullscreen_viewer = null;
|
|
289
|
+
this.#fullscreen = false;
|
|
290
|
+
this.#dispatch_event("fullscreen", false);
|
|
291
|
+
};
|
|
292
|
+
_minimize_current_fullscreen_viewer = () => this.#minimize();
|
|
293
|
+
this.#fullscreen = true;
|
|
294
|
+
this.#dispatch_event("fullscreen", true);
|
|
382
295
|
};
|
|
383
|
-
const saved_rect = canvas.getBoundingClientRect();
|
|
384
|
-
|
|
385
|
-
style.width = `100%`;
|
|
386
|
-
style.height = `100%`;
|
|
387
|
-
style.top = `0px`;
|
|
388
|
-
style.left = `0px`;
|
|
389
|
-
style.bottom = `0px`;
|
|
390
|
-
style.right = `0px`;
|
|
391
|
-
style.transition = ["width", "height", "top", "left", "bottom", "right"]
|
|
392
|
-
.map((p) => `${p} 0.1s linear`)
|
|
393
|
-
.join(", ");
|
|
394
|
-
document.body.style.overflow = "hidden";
|
|
395
|
-
|
|
396
|
-
this.#fullscreen_state = {
|
|
397
|
-
on: true,
|
|
398
|
-
saved_style,
|
|
399
|
-
saved_rect,
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
_minimize_current_fullscreen_viewer = () => this.toggle_fullscreen();
|
|
403
|
-
};
|
|
404
296
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
297
|
+
class LogChannel {
|
|
298
|
+
#on_send;
|
|
299
|
+
#on_close;
|
|
300
|
+
#get_state;
|
|
301
|
+
#closed = false;
|
|
302
|
+
/**
|
|
303
|
+
* @param on_send
|
|
304
|
+
* @param on_close
|
|
305
|
+
* @param get_state
|
|
306
|
+
*/
|
|
307
|
+
constructor(on_send, on_close, get_state) {
|
|
308
|
+
this.#on_send = on_send;
|
|
309
|
+
this.#on_close = on_close;
|
|
310
|
+
this.#get_state = get_state;
|
|
311
|
+
}
|
|
312
|
+
get ready() {
|
|
313
|
+
return !this.#closed && this.#get_state() === "ready";
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Send an `rrd` containing log messages to the viewer.
|
|
317
|
+
*
|
|
318
|
+
* Does nothing if `!this.ready`.
|
|
319
|
+
*
|
|
320
|
+
* @param rrd_bytes Is an rrd file stored in a byte array, received via some other side channel.
|
|
321
|
+
*/
|
|
322
|
+
send_rrd(rrd_bytes) {
|
|
323
|
+
if (!this.ready)
|
|
324
|
+
return;
|
|
325
|
+
this.#on_send(rrd_bytes);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Close the channel.
|
|
329
|
+
*
|
|
330
|
+
* Does nothing if `!this.ready`.
|
|
331
|
+
*/
|
|
332
|
+
close() {
|
|
333
|
+
if (!this.ready)
|
|
334
|
+
return;
|
|
335
|
+
this.#on_close();
|
|
336
|
+
this.#closed = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const classes = {
|
|
340
|
+
hide_scrollbars: "rerun-viewer-hide-scrollbars",
|
|
341
|
+
fullscreen_base: "rerun-viewer-fullscreen-base",
|
|
342
|
+
fullscreen_rect: "rerun-viewer-fullscreen-rect",
|
|
343
|
+
transition: "rerun-viewer-transition",
|
|
344
|
+
};
|
|
345
|
+
const transition_delay_ms = 100;
|
|
346
|
+
const css = `
|
|
347
|
+
html.${classes.hide_scrollbars},
|
|
348
|
+
body.${classes.hide_scrollbars} {
|
|
349
|
+
scrollbar-gutter: auto !important;
|
|
350
|
+
overflow: hidden !important;
|
|
422
351
|
}
|
|
423
352
|
|
|
424
|
-
|
|
425
|
-
|
|
353
|
+
.${classes.fullscreen_base} {
|
|
354
|
+
position: fixed;
|
|
355
|
+
z-index: 99999;
|
|
426
356
|
}
|
|
427
357
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
*
|
|
431
|
-
* Does nothing if `!this.ready`.
|
|
432
|
-
*
|
|
433
|
-
* @param {Uint8Array} rrd_bytes Is an rrd file stored in a byte array, received via some other side channel.
|
|
434
|
-
*/
|
|
435
|
-
send_rrd(rrd_bytes) {
|
|
436
|
-
if (!this.ready) return;
|
|
437
|
-
this.#on_send(rrd_bytes);
|
|
358
|
+
.${classes.transition} {
|
|
359
|
+
transition: all ${transition_delay_ms / 1000}s linear;
|
|
438
360
|
}
|
|
439
361
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
close() {
|
|
446
|
-
if (!this.ready) return;
|
|
447
|
-
this.#on_close();
|
|
448
|
-
this.#closed = true;
|
|
362
|
+
.${classes.fullscreen_rect} {
|
|
363
|
+
left: 0;
|
|
364
|
+
top: 0;
|
|
365
|
+
width: 100%;
|
|
366
|
+
height: 100%;
|
|
449
367
|
}
|
|
368
|
+
`;
|
|
369
|
+
function injectStyle() {
|
|
370
|
+
const ID = "__rerun_viewer_style";
|
|
371
|
+
if (document.getElementById(ID)) {
|
|
372
|
+
// already injected
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const style = document.createElement("style");
|
|
376
|
+
style.id = ID;
|
|
377
|
+
style.appendChild(document.createTextNode(css));
|
|
378
|
+
document.head.appendChild(style);
|
|
379
|
+
}
|
|
380
|
+
function setupGlobalEventListeners() {
|
|
381
|
+
window.addEventListener("keyup", (e) => {
|
|
382
|
+
if (e.code === "Escape") {
|
|
383
|
+
_minimize_current_fullscreen_viewer?.();
|
|
384
|
+
}
|
|
385
|
+
});
|
|
450
386
|
}
|
|
387
|
+
//# sourceMappingURL=index.js.map
|