@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/index.js CHANGED
@@ -1,450 +1,387 @@
1
- // @ts-check
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
- if (WebHandle) {
9
- return WebHandle;
10
- }
11
- WebHandle = (await import("./re_viewer.js")).WebHandle;
12
- return WebHandle;
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
- const bytes = new Uint8Array(16);
25
- crypto.getRandomValues(bytes);
26
- return Array.from(bytes)
27
- .map((byte) => byte.toString(16).padStart(2, "0"))
28
- .join("");
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
- #id = randomId();
68
-
69
- /** @type {(import("./re_viewer.js").WebHandle) | null} */
70
- #handle = null;
71
-
72
- /** @type {HTMLCanvasElement | null} */
73
- #canvas = null;
74
-
75
- /** @type {'ready' | 'starting' | 'stopped'} */
76
- #state = "stopped";
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
- * @typedef AppOptions
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
- * @typedef {(import("./re_viewer.js").WebHandle)} _WebHandle
120
- * @typedef {{ new(app_options?: AppOptions): _WebHandle }} WebHandleConstructor
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
- let WebHandle_class = /** @type {WebHandleConstructor} */ (await load());
124
- if (this.#state !== "starting") return;
125
-
126
- const fullscreen = this.#allow_fullscreen
127
- ? {
128
- get_state: () => this.#fullscreen_state.on,
129
- on_toggle: () => this.toggle_fullscreen(),
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
- : undefined;
132
-
133
- this.#handle = new WebHandle_class({ ...options, fullscreen });
134
- await this.#handle.start(this.#canvas.id);
135
- if (this.#state !== "starting") return;
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
- this.#state = "ready";
142
- if (rrd) {
143
- this.open(rrd);
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
- return;
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
- const urls = Array.isArray(rrd) ? rrd : [rrd];
173
- for (const url of urls) {
174
- this.#handle.add_receiver(url, options.follow_if_http);
175
- if (this.#handle.has_panicked()) {
176
- throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
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
- * Close a recording.
183
- *
184
- * The viewer must have been started via `WebViewer.start`.
185
- *
186
- * @see {WebViewer.start}
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
- const urls = Array.isArray(rrd) ? rrd : [rrd];
195
- for (const url of urls) {
196
- this.#handle.remove_receiver(url);
197
- if (this.#handle.has_panicked()) {
198
- throw new Error(`Web viewer crashed: ${this.#handle.panic_message()}`);
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
- * Stop the viewer, freeing all associated memory.
205
- *
206
- * The same viewer instance may be started multiple times.
207
- */
208
- stop() {
209
- if (this.#state === "stopped") return;
210
- if (this.#allow_fullscreen && this.#canvas) {
211
- const state = this.#fullscreen_state;
212
- if (state.on) this.#minimize(this.#canvas, state);
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
- this.#state = "stopped";
216
-
217
- this.#canvas?.remove();
218
- this.#handle?.destroy();
219
- this.#handle?.free();
220
-
221
- this.#canvas = null;
222
- this.#handle = null;
223
- this.#fullscreen_state.on = false;
224
- this.#allow_fullscreen = false;
225
- }
226
-
227
- /**
228
- * Opens a new channel for sending log messages.
229
- *
230
- * The channel can be used to incrementally push `rrd` chunks into the viewer.
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
- const id = crypto.randomUUID();
243
- this.#handle.open_channel(id, channel_name);
244
- const on_send = (/** @type {Uint8Array} */ data) => {
245
- if (!this.#handle) {
246
- throw new Error(
247
- `attempted to send data through channel \"${channel_name}\" to a stopped web viewer`,
248
- );
249
- }
250
- this.#handle.send_rrd_to_channel(id, data);
251
- };
252
- const on_close = () => {
253
- if (!this.#handle) {
254
- throw new Error(
255
- `attempted to send data through channel \"${channel_name}\" to a stopped web viewer`,
256
- );
257
- }
258
- this.#handle.close_channel(id);
259
- };
260
- const get_state = () => this.#state;
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
- this.#handle.override_panel_state(panel, state);
277
- }
278
-
279
- /**
280
- * Toggle panel overrides set via `override_panel_state`.
281
- */
282
- toggle_panel_overrides() {
283
- if (!this.#handle) {
284
- throw new Error(
285
- `attempted to toggle panel overrides in a stopped web viewer`,
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
- this.#handle.toggle_panel_overrides();
289
- }
290
-
291
- /**
292
- * Toggle fullscreen mode.
293
- *
294
- * This does nothing if `allow_fullscreen` was not set to `true` when starting the viewer.
295
- *
296
- * Fullscreen mode works by updating the underlying `<canvas>` element's `style`:
297
- * - `position` to `fixed`
298
- * - width/height/top/left to cover the entire viewport
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
- const state = this.#fullscreen_state;
320
- if (state.on) {
321
- this.#minimize(this.#canvas, state);
322
- } else {
323
- this.#maximize(this.#canvas);
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
- #minimize = (
328
- /** @type {HTMLCanvasElement} */ canvas,
329
- /** @type {FullscreenOn} */ { saved_style, saved_rect },
330
- ) => {
331
- this.#fullscreen_state = {
332
- on: false,
333
- saved_style: null,
334
- saved_rect: null,
335
- };
336
-
337
- if (this.#fullscreen_state.on) return;
338
-
339
- canvas.style.width = saved_rect.width + "px";
340
- canvas.style.height = saved_rect.height + "px";
341
- canvas.style.top = saved_rect.top + "px";
342
- canvas.style.left = saved_rect.left + "px";
343
- canvas.style.bottom = saved_rect.bottom + "px";
344
- canvas.style.right = saved_rect.right + "px";
345
-
346
- setTimeout(
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
- for (const key in saved_style.canvas) {
350
- // @ts-expect-error
351
- canvas.style[key] = saved_style.canvas[key];
352
- }
353
- for (const key in saved_style.document) {
354
- // @ts-expect-error
355
- document.body.style[key] = saved_style.document[key];
356
- }
357
- }),
358
- 100,
359
- );
360
-
361
- _minimize_current_fullscreen_viewer = null;
362
- };
363
-
364
- #maximize = (/** @type {HTMLCanvasElement} */ canvas) => {
365
- _minimize_current_fullscreen_viewer?.();
366
-
367
- const style = canvas.style;
368
-
369
- /** @type {CanvasStyle} */
370
- const saved_style = {
371
- canvas: {
372
- position: style.position,
373
- width: style.width,
374
- height: style.height,
375
- top: style.top,
376
- left: style.left,
377
- bottom: style.bottom,
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
- export class LogChannel {
407
- #on_send;
408
- #on_close;
409
- #get_state;
410
- #closed = false;
411
-
412
- /** @internal
413
- *
414
- * @param {(data: Uint8Array) => void} on_send
415
- * @param {() => void} on_close
416
- * @param {() => 'ready' | 'starting' | 'stopped'} get_state
417
- */
418
- constructor(on_send, on_close, get_state) {
419
- this.#on_send = on_send;
420
- this.#on_close = on_close;
421
- this.#get_state = get_state;
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
- get ready() {
425
- return !this.#closed && this.#get_state() === "ready";
353
+ .${classes.fullscreen_base} {
354
+ position: fixed;
355
+ z-index: 99999;
426
356
  }
427
357
 
428
- /**
429
- * Send an `rrd` containing log messages to the viewer.
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
- * Close the channel.
442
- *
443
- * Does nothing if `!this.ready`.
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