@rerun-io/web-viewer 0.16.1 → 0.17.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/README.md CHANGED
@@ -41,7 +41,7 @@ viewer.stop();
41
41
  ```
42
42
 
43
43
  The `rrd` in the snippet above should be a URL pointing to either:
44
- - A hosted `.rrd` file, such as <https://app.rerun.io/version/0.16.1/examples/dna.rrd>
44
+ - A hosted `.rrd` file, such as <https://app.rerun.io/version/0.17.0-alpha.2/examples/dna.rrd>
45
45
  - A WebSocket connection to the SDK opened via the [`serve`](https://www.rerun.io/docs/reference/sdk-operating-modes#serve) API
46
46
 
47
47
  If `rrd` is not set, the Viewer will display the same welcome screen as <https://app.rerun.io>.
package/index.d.ts CHANGED
@@ -5,9 +5,9 @@ declare module '@rerun-io/web-viewer' {
5
5
  *
6
6
  * @param rrd URLs to `.rrd` files or WebSocket connections to our SDK.
7
7
  * @param parent The element to attach the canvas onto.
8
- * @param hide_welcome_screen Whether to hide the welcome screen.
8
+ * @param options Whether to hide the welcome screen.
9
9
  * */
10
- start(rrd?: string | string[] | undefined, parent?: HTMLElement | undefined, hide_welcome_screen?: boolean | undefined): Promise<void>;
10
+ start(rrd?: string | string[] | null | undefined, parent?: HTMLElement | null | undefined, options?: WebViewerOptions | null | undefined): Promise<void>;
11
11
  /**
12
12
  * Returns `true` if the viewer is ready to connect to data sources.
13
13
  */
@@ -48,6 +48,35 @@ declare module '@rerun-io/web-viewer' {
48
48
  *
49
49
  * */
50
50
  open_channel(channel_name?: string): LogChannel;
51
+ /**
52
+ * Force a panel to a specific state.
53
+ *
54
+ * */
55
+ override_panel_state(panel: Panel, state: PanelState): void;
56
+ /**
57
+ * Toggle panel overrides set via `override_panel_state`.
58
+ */
59
+ toggle_panel_overrides(): void;
60
+ /**
61
+ * Toggle fullscreen mode.
62
+ *
63
+ * This does nothing if `allow_fullscreen` was not set to `true` when starting the viewer.
64
+ *
65
+ * Fullscreen mode works by updating the underlying `<canvas>` element's `style`:
66
+ * - `position` to `fixed`
67
+ * - width/height/top/left to cover the entire viewport
68
+ *
69
+ * When fullscreen mode is toggled off, the style is restored to its previous values.
70
+ *
71
+ * When fullscreen mode is toggled on, any other instance of the viewer on the page
72
+ * which is already in fullscreen mode is toggled off. This means that it doesn't
73
+ * have to be tracked manually.
74
+ *
75
+ * This functionality can also be directly accessed in the viewer:
76
+ * - The maximize/minimize top panel button
77
+ * - The `Toggle fullscreen` UI command (accessible via the command palette, CTRL+P)
78
+ */
79
+ toggle_fullscreen(): void;
51
80
  #private;
52
81
  }
53
82
  export class LogChannel {
@@ -70,6 +99,59 @@ declare module '@rerun-io/web-viewer' {
70
99
  close(): void;
71
100
  #private;
72
101
  }
102
+ export type Panel = "top" | "blueprint" | "selection" | "time";
103
+ export type PanelState = "hidden" | "collapsed" | "expanded";
104
+ export type Backend = "webgpu" | "webgl";
105
+ export type CanvasRect = {
106
+ width: string;
107
+ height: string;
108
+ top: string;
109
+ left: string;
110
+ bottom: string;
111
+ right: string;
112
+ };
113
+ export type CanvasStyle = {
114
+ canvas: CanvasRect & {
115
+ position: string;
116
+ transition: string;
117
+ };
118
+ document: {
119
+ overflow: string;
120
+ };
121
+ };
122
+ export type FullscreenOff = {
123
+ on: false;
124
+ saved_style: null;
125
+ saved_rect: null;
126
+ };
127
+ export type FullscreenOn = {
128
+ on: true;
129
+ saved_style: CanvasStyle;
130
+ saved_rect: DOMRect;
131
+ };
132
+ export type FullscreenState = (FullscreenOff | FullscreenOn);
133
+ export type WebViewerOptions = {
134
+ /**
135
+ * Use a different example manifest.
136
+ */
137
+ manifest_url?: string | undefined;
138
+ /**
139
+ * Force the viewer to use a specific rendering backend.
140
+ */
141
+ render_backend?: Backend | undefined;
142
+ /**
143
+ * Whether to hide the welcome screen in favor of a simpler one.
144
+ */
145
+ hide_welcome_screen?: boolean | undefined;
146
+ /**
147
+ * Whether to allow the viewer to enter fullscreen mode.
148
+ */
149
+ allow_fullscreen?: boolean | undefined;
150
+ };
151
+ export type FullscreenOptions = {
152
+ get_state: () => boolean;
153
+ on_toggle: () => void;
154
+ };
73
155
  }
74
156
 
75
157
  declare module '@rerun-io/web-viewer/re_viewer.js' {
@@ -119,13 +201,17 @@ declare module '@rerun-io/web-viewer/re_viewer.js' {
119
201
  export class WebHandle {
120
202
  free(): void;
121
203
 
122
- constructor();
204
+ constructor(app_options: any);
123
205
  /**
124
206
  * - `url` is an optional URL to either an .rrd file over http, or a Rerun WebSocket server.
125
207
  * - `manifest_url` is an optional URL to an `examples_manifest.json` file over http.
126
208
  * - `force_wgpu_backend` is an optional string to force a specific backend, either `webgl` or `webgpu`.
127
209
  * */
128
- start(canvas_id: string, url?: string, manifest_url?: string, force_wgpu_backend?: string, hide_welcome_screen?: boolean): Promise<void>;
210
+ start(canvas_id: string): Promise<void>;
211
+
212
+ toggle_panel_overrides(): void;
213
+
214
+ override_panel_state(panel: string, state?: string): void;
129
215
 
130
216
  destroy(): void;
131
217
 
package/index.d.ts.map CHANGED
@@ -4,6 +4,12 @@
4
4
  "names": [
5
5
  "WebViewer",
6
6
  "LogChannel",
7
+ "Panel",
8
+ "PanelState",
9
+ "Backend",
10
+ "FullscreenOff",
11
+ "FullscreenOn",
12
+ "FullscreenState",
7
13
  "set_email",
8
14
  "IntoUnderlyingByteSource",
9
15
  "IntoUnderlyingSink",
@@ -18,5 +24,5 @@
18
24
  null,
19
25
  null
20
26
  ],
21
- "mappings": ";cAuBaA,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqJTC,UAAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBClKPC,SAASA;;eAGZC,wBAAwBA;;;;;;;;;;;;;;eAuBxBC,kBAAkBA;;;;;;;;;;eAmBlBC,oBAAoBA;;;;;;;;eAapBC,SAASA"
27
+ "mappings": ";cAiEaA,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqVTC,UAAUA;;;;;;;;;;;;;;;;;;;;aAvXkCC,KAAKA;aAEZC,UAAUA;aAE3BC,OAAOA;;;;;;;;;;;;;;;;;;aAayBC,aAAaA;;;;;aAEJC,YAAYA;;;;;aAEzCC,eAAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBC1C5CC,SAASA;;eAGZC,wBAAwBA;;;;;;;;;;;;;;eAuBxBC,kBAAkBA;;;;;;;;;;eAmBlBC,oBAAoBA;;;;;;;;eAapBC,SAASA"
22
28
  }
package/index.js CHANGED
@@ -12,6 +12,13 @@ async function load() {
12
12
  return WebHandle;
13
13
  }
14
14
 
15
+ /**
16
+ * Used to prevent multiple viewers from being fullscreen at the same time.
17
+ *
18
+ * @type {(() => void) | null}
19
+ */
20
+ let _minimize_current_fullscreen_viewer = null;
21
+
15
22
  /** @returns {string} */
16
23
  function randomId() {
17
24
  const bytes = new Uint8Array(16);
@@ -21,7 +28,44 @@ function randomId() {
21
28
  .join("");
22
29
  }
23
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
+
24
66
  export class WebViewer {
67
+ #id = randomId();
68
+
25
69
  /** @type {(import("./re_viewer.js").WebHandle) | null} */
26
70
  #handle = null;
27
71
 
@@ -31,33 +75,63 @@ export class WebViewer {
31
75
  /** @type {'ready' | 'starting' | 'stopped'} */
32
76
  #state = "stopped";
33
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
+
34
89
  /**
35
90
  * Start the viewer.
36
91
  *
37
- * @param {string | string[]} [rrd] URLs to `.rrd` files or WebSocket connections to our SDK.
38
- * @param {HTMLElement} [parent] The element to attach the canvas onto.
39
- * @param {boolean} [hide_welcome_screen] Whether to hide the welcome screen.
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.
40
95
  * @returns {Promise<void>}
41
96
  */
42
- async start(rrd, parent = document.body, hide_welcome_screen = false) {
97
+ async start(rrd, parent, options) {
98
+ parent ??= document.body;
99
+ options ??= {};
100
+
101
+ this.#allow_fullscreen = options.allow_fullscreen || false;
102
+
43
103
  if (this.#state !== "stopped") return;
44
104
  this.#state = "starting";
45
105
 
46
106
  this.#canvas = document.createElement("canvas");
47
- this.#canvas.id = randomId();
107
+ this.#canvas.id = this.#id;
48
108
  parent.append(this.#canvas);
49
109
 
50
- let WebHandle_class = await load();
110
+ /**
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]
118
+ *
119
+ * @typedef {(import("./re_viewer.js").WebHandle)} _WebHandle
120
+ * @typedef {{ new(app_options?: AppOptions): _WebHandle }} WebHandleConstructor
121
+ */
122
+
123
+ let WebHandle_class = /** @type {WebHandleConstructor} */ (await load());
51
124
  if (this.#state !== "starting") return;
52
125
 
53
- this.#handle = new WebHandle_class();
54
- await this.#handle.start(
55
- this.#canvas.id,
56
- undefined,
57
- undefined,
58
- undefined,
59
- hide_welcome_screen,
60
- );
126
+ const fullscreen = this.#allow_fullscreen
127
+ ? {
128
+ get_state: () => this.#fullscreen_state.on,
129
+ on_toggle: () => this.toggle_fullscreen(),
130
+ }
131
+ : undefined;
132
+
133
+ this.#handle = new WebHandle_class({ ...options, fullscreen });
134
+ await this.#handle.start(this.#canvas.id);
61
135
  if (this.#state !== "starting") return;
62
136
 
63
137
  if (this.#handle.has_panicked()) {
@@ -65,7 +139,6 @@ export class WebViewer {
65
139
  }
66
140
 
67
141
  this.#state = "ready";
68
-
69
142
  if (rrd) {
70
143
  this.open(rrd);
71
144
  }
@@ -134,6 +207,11 @@ export class WebViewer {
134
207
  */
135
208
  stop() {
136
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);
213
+ }
214
+
137
215
  this.#state = "stopped";
138
216
 
139
217
  this.#canvas?.remove();
@@ -142,6 +220,8 @@ export class WebViewer {
142
220
 
143
221
  this.#canvas = null;
144
222
  this.#handle = null;
223
+ this.#fullscreen_state.on = false;
224
+ this.#allow_fullscreen = false;
145
225
  }
146
226
 
147
227
  /**
@@ -154,20 +234,174 @@ export class WebViewer {
154
234
  * @returns {LogChannel}
155
235
  */
156
236
  open_channel(channel_name = "rerun-io/web-viewer") {
157
- if (!this.#handle) throw new Error("...");
237
+ if (!this.#handle) {
238
+ throw new Error(
239
+ `attempted to open channel \"${channel_name}\" in a stopped web viewer`,
240
+ );
241
+ }
158
242
  const id = crypto.randomUUID();
159
243
  this.#handle.open_channel(id, channel_name);
160
244
  const on_send = (/** @type {Uint8Array} */ data) => {
161
- if (!this.#handle) throw new Error("...");
245
+ if (!this.#handle) {
246
+ throw new Error(
247
+ `attempted to send data through channel \"${channel_name}\" to a stopped web viewer`,
248
+ );
249
+ }
162
250
  this.#handle.send_rrd_to_channel(id, data);
163
251
  };
164
252
  const on_close = () => {
165
- if (!this.#handle) throw new Error("...");
253
+ if (!this.#handle) {
254
+ throw new Error(
255
+ `attempted to send data through channel \"${channel_name}\" to a stopped web viewer`,
256
+ );
257
+ }
166
258
  this.#handle.close_channel(id);
167
259
  };
168
260
  const get_state = () => this.#state;
169
261
  return new LogChannel(on_send, on_close, get_state);
170
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
+ );
275
+ }
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
+ );
287
+ }
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
+ );
317
+ }
318
+
319
+ const state = this.#fullscreen_state;
320
+ if (state.on) {
321
+ this.#minimize(this.#canvas, state);
322
+ } else {
323
+ this.#maximize(this.#canvas);
324
+ }
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
+ () =>
348
+ 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 },
382
+ };
383
+ const saved_rect = canvas.getBoundingClientRect();
384
+
385
+ style.position = "fixed";
386
+ style.width = `100%`;
387
+ style.height = `100%`;
388
+ style.top = `0px`;
389
+ style.left = `0px`;
390
+ style.bottom = `0px`;
391
+ style.right = `0px`;
392
+ style.transition = ["width", "height", "top", "left", "bottom", "right"]
393
+ .map((p) => `${p} 0.1s linear`)
394
+ .join(", ");
395
+ document.body.style.overflow = "hidden";
396
+
397
+ this.#fullscreen_state = {
398
+ on: true,
399
+ saved_style,
400
+ saved_rect,
401
+ };
402
+
403
+ _minimize_current_fullscreen_viewer = () => this.toggle_fullscreen();
404
+ };
171
405
  }
172
406
 
173
407
  export class LogChannel {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rerun-io/web-viewer",
3
- "version": "0.16.1",
3
+ "version": "0.17.0-alpha.2",
4
4
  "description": "Embed the Rerun web viewer in your app",
5
5
  "licenses": [
6
6
  {
@@ -12,9 +12,10 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "build:wasm": "cargo run -p re_dev_tools -- build-web-viewer --release -g --module -o rerun_js/web-viewer",
15
- "build:wasm:debug": "cargo run -p re_dev_tools -- build-web-viewer --module -o rerun_js/web-viewer",
16
15
  "build:types": "tsc --noEmit && dts-buddy",
17
- "build": "npm run build:wasm && npm run build:types"
16
+ "build": "npm run build:wasm && npm run build:types",
17
+ "build:wasm:debug": "cargo run -p re_dev_tools -- build-web-viewer --debug --module -o rerun_js/web-viewer",
18
+ "build:debug": "npm run build:wasm:debug && npm run build:types"
18
19
  },
19
20
  "repository": {
20
21
  "type": "git",
package/re_viewer.d.ts CHANGED
@@ -69,20 +69,25 @@ export class IntoUnderlyingSource {
69
69
  export class WebHandle {
70
70
  free(): void;
71
71
  /**
72
+ * @param {any} app_options
72
73
  */
73
- constructor();
74
+ constructor(app_options: any);
74
75
  /**
75
76
  * - `url` is an optional URL to either an .rrd file over http, or a Rerun WebSocket server.
76
77
  * - `manifest_url` is an optional URL to an `examples_manifest.json` file over http.
77
78
  * - `force_wgpu_backend` is an optional string to force a specific backend, either `webgl` or `webgpu`.
78
79
  * @param {string} canvas_id
79
- * @param {string | undefined} [url]
80
- * @param {string | undefined} [manifest_url]
81
- * @param {string | undefined} [force_wgpu_backend]
82
- * @param {boolean | undefined} [hide_welcome_screen]
83
80
  * @returns {Promise<void>}
84
81
  */
85
- start(canvas_id: string, url?: string, manifest_url?: string, force_wgpu_backend?: string, hide_welcome_screen?: boolean): Promise<void>;
82
+ start(canvas_id: string): Promise<void>;
83
+ /**
84
+ */
85
+ toggle_panel_overrides(): void;
86
+ /**
87
+ * @param {string} panel
88
+ * @param {string | undefined} [state]
89
+ */
90
+ override_panel_state(panel: string, state?: string): void;
86
91
  /**
87
92
  */
88
93
  destroy(): void;