@foxglove/extension 2.23.0 → 2.24.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +622 -90
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxglove/extension",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Foxglove Technologies",
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import type { Immutable } from "./immutable";
2
2
 
3
3
  export type { Immutable } from "./immutable";
4
4
 
5
- // Valid types for parameter data (such as rosparams)
5
+ /** Valid types for parameter data (such as rosparams) */
6
6
  export type ParameterValue =
7
7
  | undefined
8
8
  | boolean
@@ -13,7 +13,7 @@ export type ParameterValue =
13
13
  | ParameterValue[]
14
14
  | { [key: string]: ParameterValue };
15
15
 
16
- // Valid types for global variables
16
+ /** Valid types for [variables](https://docs.foxglove.dev/docs/visualization/variables) */
17
17
  export type VariableValue =
18
18
  | undefined
19
19
  | boolean
@@ -24,16 +24,23 @@ export type VariableValue =
24
24
 
25
25
  export type VariableStruct = { [key: string]: VariableValue };
26
26
 
27
- // Valid types for application settings
27
+ /** Valid types for application settings */
28
28
  export type AppSettingValue = string | number | boolean | undefined;
29
29
 
30
- export interface Time {
30
+ /**
31
+ * A timestamp with nanosecond precision.
32
+ *
33
+ * The timestamp is often nanoseconds since the UNIX epoch, but may be
34
+ * relative to another event such as system boot time or simulation start
35
+ * time depending on the context.
36
+ */
37
+ export type Time = {
31
38
  sec: number;
32
39
  nsec: number;
33
- }
40
+ };
34
41
 
35
42
  /**
36
- * A topic is a namespace for specific types of messages
43
+ * A Topic is a named channel of messages.
37
44
  */
38
45
  export type Topic = {
39
46
  /**
@@ -56,11 +63,16 @@ export type Topic = {
56
63
  /**
57
64
  * Lists any additional schema names available for subscribers on the topic. When subscribing to
58
65
  * a topic, the panel can request messages be automatically converted from schemaName into one
59
- * of the convertibleTo schemas using the convertTo option.
66
+ * of the convertibleTo schemas using the {@link Subscription.convertTo} option.
60
67
  */
61
68
  convertibleTo?: readonly string[];
62
69
  };
63
70
 
71
+ /**
72
+ * A single subscription passed to {@link PanelExtensionContext.subscribe}.
73
+ *
74
+ * @category Custom panels
75
+ */
64
76
  export type Subscription = {
65
77
  topic: string;
66
78
 
@@ -77,12 +89,16 @@ export type Subscription = {
77
89
  * messages for the topic. The default behavior is to only load messages for the current frame.
78
90
  *
79
91
  * **Only** topics with `preload: true` are available in the `allFrames` render state.
92
+ *
93
+ * @deprecated Please use {@link PanelExtensionContext.subscribeMessageRange} instead.
80
94
  */
81
95
  preload?: boolean;
82
96
  };
83
97
 
84
98
  /**
85
- * A message event frames message data with the topic and receive time
99
+ * A MessageEvent represents a single message along with metadata about the message.
100
+ *
101
+ * Remember to import MessageEvent from `@foxglove/extension`. This is not the same as the DOM [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) class.
86
102
  */
87
103
  export type MessageEvent<T = unknown> = {
88
104
  /** The topic name this message was received on, i.e. "/some/topic" */
@@ -117,14 +133,39 @@ export type MessageEvent<T = unknown> = {
117
133
 
118
134
  /**
119
135
  * When subscribing to a topic using the `convertTo` option, the message event `message`
120
- * contains the converted message and the originalMessageEvent field contains the original
136
+ * contains the converted message and the `originalMessageEvent` field contains the original
121
137
  * un-converted message event.
122
138
  */
123
139
  originalMessageEvent?: MessageEvent;
124
140
  };
125
141
 
142
+ /**
143
+ * Actions the panel may perform related to the user's current layout via {@link PanelExtensionContext.layout | context.layout}.
144
+ *
145
+ * @category Custom panels
146
+ */
126
147
  export interface LayoutActions {
127
- /** Open a new panel or update an existing panel in the layout. */
148
+ /**
149
+ * Use `context.layout.addPanel` to add a panel adjacent to the current panel in the layout.
150
+ *
151
+ * The value of `position` must be set to `"sibling"`.
152
+ *
153
+ * The value of `type` can refer to a panel from a custom extension as `extensionname.panelname`,
154
+ * where `extensionname` is the extension name from `package.json` and `panelname` is the name
155
+ * provided when the extension registers a panel.
156
+ *
157
+ * `getState` is set to a function that returns the state (also known as panel settings) for the
158
+ * new panel, or return `undefined` to use the new panel's default settings.
159
+ *
160
+ * ```ts
161
+ * // Add new panel
162
+ * context.layout.addPanel({
163
+ * position: "sibling",
164
+ * type: "MyExtension.MyPanel",
165
+ * getState: () => ({}),
166
+ * });
167
+ * ```
168
+ */
128
169
  addPanel(params: {
129
170
  /**
130
171
  * Where to position the panel. Currently, only "sibling" is supported which indicates the
@@ -156,6 +197,19 @@ export interface LayoutActions {
156
197
  }): void;
157
198
  }
158
199
 
200
+ /**
201
+ * RenderState is the information passed to your panel's
202
+ * {@link PanelExtensionContext.onRender | onRender} function.
203
+ *
204
+ * To receive updates for a particular part of RenderState, you must first call
205
+ * {@link PanelExtensionContext.watch | watch} with the field name. For example, call
206
+ * `watch("currentTime")` to receive updates for `currentTime`.
207
+ *
208
+ * If a field is missing from RenderState, either the value has not changed since the last call to
209
+ * `onRender`, or you did not `watch` the field.
210
+ *
211
+ * @category Custom panels
212
+ */
159
213
  export type RenderState = {
160
214
  /**
161
215
  * The latest messages for the current render frame. These are new messages since the last render frame.
@@ -171,6 +225,8 @@ export type RenderState = {
171
225
 
172
226
  /**
173
227
  * All available messages. Best-effort list of all available messages.
228
+ *
229
+ * @deprecated Please use {@link PanelExtensionContext.subscribeMessageRange} instead.
174
230
  */
175
231
  allFrames?: MessageEvent[];
176
232
 
@@ -231,11 +287,16 @@ export type RenderState = {
231
287
  /** The color scheme currently in use throughout the app. */
232
288
  colorScheme?: "dark" | "light";
233
289
 
234
- /** Application settings. This will only contain subscribed application setting key/values */
290
+ /** Application settings. This will only contain keys/values that were subscribed to using {@link @PanelExtensionContext.subscribeAppSettings} */
235
291
  appSettings?: Map<string, AppSettingValue>;
236
292
  };
237
293
 
238
- type SubscribeMessageRangeArgs = {
294
+ /**
295
+ * This type represents the arguments you pass to {@link PanelExtensionContext.subscribeMessageRange}.
296
+ *
297
+ * @category Custom panels
298
+ */
299
+ export type SubscribeMessageRangeArgs = {
239
300
  /**
240
301
  * Topic to be subscribed to.
241
302
  */
@@ -251,7 +312,7 @@ type SubscribeMessageRangeArgs = {
251
312
  */
252
313
  convertTo?: string;
253
314
  /**
254
- * `onReset` is a function that receives an async iterable when there is message data available on
315
+ * `onNewRangeIterator` is a function that receives an async iterable when there is message data available on
255
316
  * the subscription.
256
317
  *
257
318
  * To read messages, your function should iterate through the provided async iterable. Each item
@@ -260,25 +321,47 @@ type SubscribeMessageRangeArgs = {
260
321
  * finish.
261
322
  *
262
323
  * ```typescript
263
- * async function onReset(batchIterator) {
324
+ * async function onNewRangeIterator(batchIterator) {
264
325
  * for await (const batch of batchIterator) {
265
326
  * //...
266
327
  * }
267
328
  * }
268
329
  * ```
269
330
  *
270
- * `onReset` is called again when the upstream topic data changes. I.E subscribing to a
331
+ * `onNewRangeIterator` is called again when the upstream topic data changes. I.E subscribing to a
271
332
  * user-script output topic and the user script changes, or subscribing to an aliased topic and
272
333
  * the alias changes. When topic data changes, the previous iterator will end, and its data is no
273
- * longer valid. When `onReset` is called, you should discard previously received data.
334
+ * longer valid. When `onNewRangeIterator` is called, you should discard previously received data.
274
335
  *
275
- * If your `onReset` function throws an error, the iterator will end and you will not receive any
276
- * more messages until `onReset` is called again. Your error will appear in the problems sidebar
336
+ * If your `onNewRangeIterator` function throws an error, the iterator will end and you will not receive any
337
+ * more messages until `onNewRangeIterator` is called again. Your error will appear in the problems sidebar
277
338
  * for user visibility.
278
339
  */
279
- onReset: (batchIterator: AsyncIterable<Immutable<MessageEvent[]>>) => Promise<void>;
340
+ onNewRangeIterator: (batchIterator: AsyncIterable<Immutable<MessageEvent[]>>) => Promise<void>;
341
+
342
+ /**
343
+ * @deprecated This method has been renamed. Use `onNewRangeIterator`.
344
+ */
345
+ onReset?: (batchIterator: AsyncIterable<Immutable<MessageEvent[]>>) => Promise<void>;
280
346
  };
281
347
 
348
+ /**
349
+ * The `PanelExtensionContext` exposes properties and methods for writing a custom panel. The
350
+ * context has methods to subscribe for messages, receive updates, configure your panel's settings,
351
+ * and render your panel to the UI.
352
+ *
353
+ * The {@link ExtensionPanelRegistration.initPanel | initPanel} function used in
354
+ * {@link ExtensionContext.registerPanel | registerPanel} accepts a {@link PanelExtensionContext}
355
+ * argument. This argument contains properties and methods for accessing panel data and rendering UI
356
+ * updates. The `initPanel` function also returns an optional cleanup function to run when the
357
+ * extension `panelElement` unmounts.
358
+ *
359
+ * See the [Creating a custom
360
+ * panel](https://docs.foxglove.dev/docs/visualization/extensions/guides/create-custom-panel) guide
361
+ * for more details.
362
+ *
363
+ * @category Custom panels
364
+ */
282
365
  export type PanelExtensionContext = {
283
366
  /**
284
367
  * The root element for the panel. Add your panel elements as children under this element.
@@ -290,7 +373,7 @@ export type PanelExtensionContext = {
290
373
  */
291
374
  readonly initialState: unknown;
292
375
 
293
- /** Actions the panel may perform related to the user's current layout. */
376
+ /** Actions the panel may perform related to the user's current layout. See {@link LayoutActions} for details. */
294
377
  readonly layout: LayoutActions;
295
378
 
296
379
  /**
@@ -304,103 +387,285 @@ export type PanelExtensionContext = {
304
387
  /**
305
388
  * Subscribe to updates on this field within the render state. Render will only be invoked when
306
389
  * this field changes.
390
+ *
391
+ * Use `context.watch` to indicate which fields in {@link RenderState} (e.g. `currentFrame`,
392
+ * `currentTime`, `previewTime`, `parameters`, `topics`) should trigger panel re-renders when
393
+ * their contained values change.
394
+ *
395
+ * ```ts
396
+ * context.watch("topics");
397
+ * context.watch("currentFrame");
398
+ * context.watch("parameters");
399
+ * context.watch("currentTime");
400
+ * ```
401
+ */
402
+ watch(field: keyof RenderState): void;
403
+
404
+ /**
405
+ * Subscribe to updates on this field within the render state. Render will only be invoked when
406
+ * this field changes.
407
+ *
408
+ * @deprecated Calling `watch` with `allFrames` is deprecated. Use {@link PanelExtensionContext.subscribeMessageRange} instead.
307
409
  */
308
- watch: (field: keyof RenderState) => void;
410
+ watch(field: "allFrames"): void;
309
411
 
310
412
  /**
311
- * Save arbitrary object as persisted panel state. This state is persisted for the panel
312
- * within a layout.
413
+ * Use `context.saveState` to save an arbitrary object as persisted panel state (also known as
414
+ * panel settings) in the current layout. You can view the current panel state using
415
+ * [Import/export settings](https://docs.foxglove.dev/docs/visualization/panels/introduction#importexport-settings).
416
+ *
417
+ * ```ts
418
+ * context.initialState = undefined; // your panel's initial state
313
419
  *
314
- * The state value should be JSON serializable.
420
+ * context.saveState({ myNum: 2, myBool: false, myStr: "abc" });
421
+ * ```
422
+ *
423
+ * @param state The state to save. This value should be JSON serializable.
315
424
  */
316
- saveState: (state: Partial<unknown>) => void;
425
+ saveState(state: Partial<unknown>): void;
317
426
 
318
427
  /**
319
- * Set the value of parameter name to value.
428
+ * Use `context.setParameter` to set a parameter `name` to any valid `value` (i.e. primitives, dates, `Uint8Array`s, and arrays or objects containing these values).
429
+ *
430
+ * ```ts
431
+ * context.setParameter("/param1", "value1");
432
+ * ```
320
433
  *
321
434
  * @param name The name of the parameter to set.
322
435
  * @param value The new value of the parameter.
323
436
  */
324
- setParameter: (name: string, value: ParameterValue) => void;
437
+ setParameter(name: string, value: ParameterValue): void;
325
438
 
326
439
  /**
327
440
  * Set the transient state shared by panels of the same type as the caller of this function.
328
441
  * This will not be persisted in the layout.
329
442
  */
330
- setSharedPanelState: (state: undefined | Record<string, unknown>) => void;
443
+ setSharedPanelState(state: undefined | Record<string, unknown>): void;
331
444
 
332
445
  /**
333
- * Set the value of variable name to value.
446
+ * Use `context.setVariable` to set a
447
+ * [variable](https://docs.foxglove.dev/docs/visualization/variables) `name` to any valid variable
448
+ * `value`.
449
+ *
450
+ * ```ts
451
+ * context.setVariable("myVar", 55);
452
+ *
453
+ * context.onRender = (renderState: RenderState, done) => {
454
+ * // Read variable values from the renderState
455
+ * const variableValues = renderState.variables;
456
+ * const myVarValue = variableValues.myVar;
457
+ *
458
+ * // Call done when you've rendered all the UI for this renderState. If your UI framework delays rendering, call done when rendering has actaully happened.
459
+ * done();
460
+ * };
461
+ * ```
334
462
  *
335
463
  * @param name The name of the variable to set.
336
464
  * @param value The new value of the variable.
337
465
  */
338
- setVariable: (name: string, value: VariableValue) => void;
466
+ setVariable(name: string, value: VariableValue): void;
339
467
 
340
468
  /**
341
469
  * Set the active preview time. Setting the preview time to undefined clears the preview time.
342
470
  */
343
- setPreviewTime: (time: number | undefined) => void;
471
+ setPreviewTime(time: number | undefined): void;
344
472
 
345
473
  /**
346
474
  * Seek playback to the given time. Behaves as if the user had clicked the playback bar
347
475
  * to seek.
348
476
  *
349
477
  * Clients can pass a number or alternatively a Time object for greater precision.
350
- */
351
- seekPlayback?: (time: number | Time) => void;
352
-
353
- /**
354
- * Subscribe to an array of topic names.
355
- *
356
- * Subscribe will update the current subscriptions to the list of topic names. Passing an empty
357
- * array will unsubscribe from all topics.
358
- *
359
- * Calling subscribe with an empty array of topics is analagous to unsubscribeAll.
360
478
  *
361
- * @deprecated Use `subscribe` with an array of Subscription objects instead.
479
+ * This property may be `undefined` if the current data source does not support seeking.
362
480
  */
363
- subscribe(topics: string[]): void;
481
+ seekPlayback?(time: number | Time): void;
364
482
 
365
483
  /**
366
- * Subscribe to an array of topics with additional options for each subscription.
484
+ * Use `context.subscribe` to indicate the topics your panel wants to receive messages for. The
485
+ * messages are provided during render in {@link RenderState.currentFrame}.
367
486
  *
368
- * Subscribe will update the current subscriptions to the new list of Subscriptions and
487
+ * @remarks
488
+ *
489
+ * This method will update the current subscriptions to the new list of Subscriptions and
369
490
  * unsubscribe from any previously subscribed topics no longer in the Subscription list. Passing
370
491
  * an empty array will unsubscribe from all topics.
371
492
  *
372
- * Calling subscribe with an empty array is analagous to unsubscribeAll.
493
+ * ```ts
494
+ * context.subscribe([{ topic: "/some/topic" }, { topic: "/another/topic" }]);
495
+ * ```
496
+ *
497
+ * `context.subscribe([])` will unsubscribe from all topics, and is equivalent to
498
+ * `unsubscribeAll`.
499
+ *
500
+ * #### Range loading
501
+ *
502
+ * Most panels display data from the current frame; examples of built-in panels that display the
503
+ * current frame are 3D, Image, and Raw Message, however some panels can display data for multiple
504
+ * messages or even the entire dataset duration (Plot, Map, State Transitions).
505
+ *
506
+ * Subscriptions will provide only the messages for the current frame. If your panel would like to
507
+ * process all the available messages on a topic, use
508
+ * {@link @PanelExtensionContext.subscribeMessageRange | subscribeMessageRange} instead.
509
+ *
510
+ * > NOTE: Message range loading is done on a best-effort basis. If your range-loaded messages
511
+ * > exceed available memory limits for the browser or desktop app, then the data may not
512
+ * > represent the full dataset range. Range loading results in more data transfer and memory use
513
+ * > and is recommended only for panels which require access to the entire dataset.
514
+ *
515
+ * #### Message converters
516
+ *
517
+ * Message converters can convert messages from one schema to another – for example, a user might
518
+ * convert custom GPS message into
519
+ * [`foxglove.LocationFix`](https://docs.foxglove.dev/docs/visualization/message-schemas/location-fix)
520
+ * messages for visualization in the [Map
521
+ * panel](https://docs.foxglove.dev/docs/visualization/panels/map). Users may have one or more
522
+ * message converters registered.
523
+ *
524
+ * If your panel expects messages with specific schema names, you can leverage registered message
525
+ * converters to convert from one schema to another.
526
+ *
527
+ * Specify the `convertTo` option to enable message conversion on a topic. When conversion is
528
+ * enabled for a subscription, the {@link MessageEvent}s will contain `message` entries with the
529
+ * converted message rather than the original message on the topic. The original message is
530
+ * available in the `originalMessageEvent` field in the message event.
531
+ *
532
+ * ```ts
533
+ * context.subscribe([{ topic: "/some/topic", convertTo: "foxglove.LocationFix" }]);
534
+ * ```
535
+ *
536
+ * The {@link Topic.convertibleTo | convertibleTo} field within {@link RenderState.topics} will
537
+ * contain the names of schemas you can convert this topic into.
373
538
  */
374
539
  subscribe(subscriptions: Subscription[]): void;
375
540
 
541
+ /**
542
+ * @deprecated Use `subscribe` with an array of Subscription objects instead.
543
+ */
544
+ subscribe(topics: string[]): void;
545
+
376
546
  /**
377
547
  * Unsubscribe from all topics.
378
548
  *
379
- * Note: This is analagous to calling subscribe([]) with an empty array of topics.
549
+ * Note: This is analagous to calling `subscribe([])` with an empty array of topics.
380
550
  */
381
551
  unsubscribeAll(): void;
382
552
 
383
553
  /**
384
- * Subscribe to any changes in application settings for an array of setting names.
554
+ * Subscribe to any changes in application settings for an array of setting keys.
555
+ *
556
+ * The keys and their corresponding values are not currently documented and are subject to change.
385
557
  */
386
558
  subscribeAppSettings(settings: string[]): void;
387
559
 
388
560
  /**
389
- * Indicate intent to publish messages on a specific topic.
561
+ * Use `context.advertise` to indicate an intent to publish a specific datatype on a topic. A
562
+ * panel must call `context.advertise` before being able to publish on the topic
563
+ * (`context.publish`). Options are specific to the data source - some make use of options; others
564
+ * do not.
565
+ *
566
+ * This property may be `undefined` if the current data source does not support publishing.
567
+ *
568
+ * ```ts
569
+ * context.advertise("/my_image_topic", "sensor_msgs/Image");
570
+ * ```
571
+ *
572
+ * `options` are specific to each data source - see documentation below for supported data
573
+ * sources.
390
574
  *
391
575
  * @param topic The topic on which the extension will publish messages.
392
576
  * @param schemaName The name of the schema that the published messages will conform to.
393
577
  * @param options Options passed to the current data source for additional configuration.
578
+ *
579
+ * @remarks
580
+ *
581
+ * #### [Native (ROS 1)](https://docs.foxglove.dev/docs/connecting-to-data/frameworks/ros1#native)
582
+ *
583
+ * | field | type | description |
584
+ * | --------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
585
+ * | datatypes | `Map<string, T>` | JavaScript map of datatype names and [`MessageDefinition`](https://github.com/foxglove/message-definition/blob/main/src/types.ts#L185) definitions for a given topic |
586
+ *
587
+ * Common datatype definitions are available in the `@foxglove/rosmsg-msgs-common` package.
588
+ *
589
+ * ```ts
590
+ * import { ros1 } from "@foxglove/rosmsg-msgs-common";
591
+ *
592
+ * context.advertise?.(currentTopic, "sensor_msgs/Joy", {
593
+ * datatypes: new Map([
594
+ * ["std_msgs/Header", ros1["std_msgs/Header"]],
595
+ * ["std_msgs/Float32", ros1["std_msgs/Float32"]],
596
+ * ["std_msgs/Int32", ros1["std_msgs/Int32"]],
597
+ * ["sensor_msgs/Joy", ros1["sensor_msgs/Joy"]],
598
+ * ]),
599
+ * });
600
+ * ```
601
+ *
602
+ * #### [Foxglove WebSocket](https://docs.foxglove.dev/docs/connecting-to-data/frameworks/custom#foxglove-websocket)
603
+ *
604
+ * `options` depend on the server implementation.
605
+ *
606
+ * ##### [Foxglove Bridge](https://docs.foxglove.dev/docs/connecting-to-data/ros-foxglove-bridge)
607
+ *
608
+ * When using the Foxglove Bridge with ROS data, use `context.dataSourceProfile` to determine the
609
+ * ROS version.
610
+ *
611
+ * For ROS 1 data, pass datatypes the same way you would for a [native ROS 1](#native-ros-1)
612
+ * connection.
613
+ *
614
+ * For ROS 2 data, pass datatypes using the following fields:
615
+ *
616
+ * | field | type | description |
617
+ * | --------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
618
+ * | datatypes | `Map<string, T>` | JavaScript map of datatype names and [`MessageDefinition`](https://github.com/foxglove/message-definition/blob/main/src/types.ts#L185) definitions for a given topic |
619
+ *
620
+ * Common datatype definitions are available in the `@foxglove/rosmsg-msgs-common` package.
621
+ *
622
+ * ```ts
623
+ * import { ros2humble as ros2 } from "@foxglove/rosmsg-msgs-common";
624
+ * // For Galactic and lower, use:
625
+ * // import { ros2galactic as ros2 } from "@foxglove/rosmsg-msgs-common";
626
+ * ```
627
+ *
628
+ * ```ts
629
+ * context.advertise?.(currentTopic, "sensor_msgs/Joy", {
630
+ * datatypes: new Map([
631
+ * ["std_msgs/Header", ros2["std_msgs/Header"]],
632
+ * ["std_msgs/Float32", ros2["std_msgs/Float32"]],
633
+ * ["std_msgs/Int32", ros2["std_msgs/Int32"]],
634
+ * ["sensor_msgs/Joy", ros2["sensor_msgs/Joy"]],
635
+ * ]),
636
+ * });
637
+ * ```
638
+ *
639
+ * #### Rosbridge ([ROS 1](https://docs.foxglove.dev/docs/connecting-to-data/frameworks/ros1#rosbridge), [ROS 2](https://docs.foxglove.dev/docs/connecting-to-data/frameworks/ros2#rosbridge))
640
+ *
641
+ * No `options` required. Simply publish a JSON message with fields conforming to the advertised
642
+ * datatype, and the bridge node will serialize it according to the datatype.
394
643
  */
395
644
  advertise?(topic: string, schemaName: string, options?: Record<string, unknown>): void;
396
645
 
397
646
  /**
398
647
  * Indicate that you no longer want to advertise on this topic.
648
+ *
649
+ * ```ts
650
+ * context.unadvertise("/my_image_topic");
651
+ * ```
652
+ *
653
+ * This property may be `undefined` if the current data source does not support publishing.
399
654
  */
400
655
  unadvertise?(topic: string): void;
401
656
 
402
657
  /**
403
- * Publish a message on a given topic. You must first advertise on the topic before publishing.
658
+ * Use `context.publish` to publish a message on a previously advertised topic. (You must first
659
+ * call {@link PanelExtensionContext.advertise | advertise} to advertise the topic before
660
+ * publishing.) If the topic is not advertised or otherwise malformed, the function will throw an
661
+ * error.
662
+ *
663
+ * ```ts
664
+ * context.advertise("/my_color_topic", "std_msgs/ColorRGBA");
665
+ * context.publish("/my_color_topic", { r: 0, g: 1, b: 0, a: 1 });
666
+ * ```
667
+ *
668
+ * This property may be `undefined` if the current data source does not support publishing.
404
669
  *
405
670
  * @param topic The name of the topic to publish the message on
406
671
  * @param message The message to publish
@@ -408,7 +673,13 @@ export type PanelExtensionContext = {
408
673
  publish?(topic: string, message: unknown): void;
409
674
 
410
675
  /**
411
- * Call a service.
676
+ * Use `context.callService` to make a service call to the specified `service` with a request payload.
677
+ *
678
+ * ```ts
679
+ * context.callService("my_service", { foo: "bar" });
680
+ * ```
681
+ *
682
+ * This property may be `undefined` if the current data source does not support services.
412
683
  *
413
684
  * @param service The name of the service to call
414
685
  * @param request The request payload for the service call
@@ -417,68 +688,215 @@ export type PanelExtensionContext = {
417
688
  callService?(service: string, request: unknown): Promise<unknown>;
418
689
 
419
690
  /**
420
- * Process render events for the panel. Each render event receives a render state and a done callback.
421
- * Render events occur frequently (60hz, 30hz, etc).
691
+ * Set this property to a function during your panel's
692
+ * {@link ExtensionPanelRegistration.initPanel | initialization}.
693
+ *
694
+ * Foxglove will run `context.onRender` whenever your panel needs to re-render during playback.
695
+ * The function accepts `renderState` and a `done` callback as its arguments. Render events occur
696
+ * frequently (60hz, 30hz, etc).
422
697
  *
423
- * The done callback should be called once the panel has rendered the render state.
698
+ * **Note**: Your `onRender` function **must** call `done` after rendering to indicate that the
699
+ * panel is ready to render the next set of data. The exact placement of this `done` invocation
700
+ * will vary between frameworks and different extensions' logic.
701
+ *
702
+ * ```ts
703
+ * context.onRender = (renderState, done) => {
704
+ * // Render your UI updates with fields from RenderState
705
+ *
706
+ * // Call done when you've rendered all the UI for this renderState.
707
+ * // If your UI framework delays rendering, call done when rendering has actually happened.
708
+ * done();
709
+ * };
710
+ * ```
424
711
  */
425
712
  onRender?: (renderState: Immutable<RenderState>, done: () => void) => void;
426
713
 
427
714
  /**
428
- * Updates the panel's settings editor. Call this every time you want to update
429
- * the representation of the panel settings in the editor.
715
+ * Call the `updatePanelSettingsEditor` method on your panel's {@link PanelExtensionContext}
716
+ * instance to define or update its settings.
717
+ *
718
+ * ```ts
719
+ * const panelSettings: SettingsTree = {
720
+ * nodes: { ... },
721
+ * actionHandler: (action: SettingsTreeAction) => { ... }
722
+ * };
723
+ *
724
+ * context.updatePanelSettingsEditor(panelSettings);
725
+ * ```
726
+ *
727
+ * The `settings` argument must be a valid {@link SettingsTree} and include 2 mandatory properties
728
+ * – `nodes` and `actionHandler`:
729
+ *
730
+ * - `nodes` - Hierarchical structure where each node can contain input fields, display fields, or
731
+ * even other nodes
732
+ * - `actionHandler` - Function that is invoked when the user interacts with the settings UI;
733
+ * contains logic to process the interactions and update the panel or settings tree
734
+ *
735
+ * It can also include the following optional properties:
736
+ *
737
+ * - `enableFilter` – Whether the settings should show the filter control
738
+ * - `focusedPath` – Node to scroll to (transient one-time effect)
739
+ *
740
+ * The example tree below has a `title` text input field inside a `General` section along with an
741
+ * `actionHandler` to respond to updates for the `title` field.
742
+ *
743
+ * ```ts
744
+ * const panelSettings: SettingsTree = {
745
+ * nodes: {
746
+ * general: {
747
+ * label: "General",
748
+ * fields: {
749
+ * title: "{
750
+ * label: "Title",
751
+ * input: "string",
752
+ * // `panelTitle` refers to a value in your extension panel's config
753
+ * value: panelTitle,
754
+ * },
755
+ * },
756
+ * },
757
+ * },
758
+ * actionHandler: (action: SettingsTreeAction) => {
759
+ * switch (action.action) {
760
+ * case "perform-node-action":
761
+ * // Handle user-defined actions for nodes in the settings tree
762
+ * break;
763
+ * case "update":
764
+ * if (action.payload.path[0] === "general" && action.payload.path[1] === "title") {
765
+ * // Read action.payload.value for the new panel title value
766
+ * panelTitle = action.payload.value;
767
+ *
768
+ * // Update your panel's state accordingly
769
+ * }
770
+ * break;
771
+ * }
772
+ * },
773
+ * }
774
+ *
775
+ * context.updatePanelSettingsEditor(panelSettings);
776
+ * ```
777
+ *
778
+ * #### `SettingsTreeAction`
779
+ *
780
+ * A {@link SettingsTreeAction} describes how the settings UI should update when a user interacts
781
+ * with its fields.
782
+ *
783
+ * Each `SettingsTreeAction` has a `payload` with a `path` to the settings field to update (e.g.
784
+ * `["general", "title"]`).
785
+ *
786
+ * The `update` action corresponds to a user setting a new value for a field (e.g. "My new
787
+ * title").
788
+ *
789
+ * #### Special node properties
790
+ *
791
+ * There are two special {@link SettingsTreeNode} properties, `label` and `visibility`. The value
792
+ * you specify for `label` will control the label displayed in the settings editor. If you set the
793
+ * `renamable` node property to `true`, the user can edit the node `label` – you will receive a
794
+ * `SettingsTreeAction` of `update` with a path ending in `label`.
795
+ *
796
+ * In addition, if you specify a boolean value for `visibility` of the node then the settings
797
+ * editor will provide a button to toggle the visibility of the node and you will receive an
798
+ * `update` action with `visibility` as the final element in the path.
799
+ *
800
+ * For an example of how to use these special properties, check out the [panel settings example
801
+ * extension](https://github.com/foxglove/create-foxglove-extension/tree/main/examples/panel-settings).
802
+ *
803
+ * #### Input types
804
+ *
805
+ * In addition to the `string` input type in the example above, the panel API provides a wide
806
+ * array of types for your extension panel input fields.
807
+ *
808
+ * Each input type has different properties that you can configure:
809
+ *
810
+ * - `autocomplete`
811
+ * - `boolean`
812
+ * - `rgb`
813
+ * - `rgba`
814
+ * - `gradient`
815
+ * - `messagepath`
816
+ * - `select`
817
+ * - `string`
818
+ * - `toggle`
819
+ * - `vec3`
820
+ * - `vec2`
430
821
  */
431
822
  updatePanelSettingsEditor(settings: Immutable<SettingsTree>): void;
432
823
 
433
824
  /**
434
- * Updates the panel's default title. Users can always override the default title by editing it
435
- * manually. A value of `undefined` will display the panel's name in the title bar.
825
+ * Use `context.setDefaultPanelTitle` to override a panel's default title. Users can always
826
+ * override the default title by editing it manually. If no override or default title is set, the
827
+ * panel will simply display its type (e.g. "Image").
828
+ *
829
+ * ```ts
830
+ * // Override the default panel title
831
+ * context.setDefaultPanelTitle(`Plot of ${config.topicName}`);
832
+ * ```
436
833
  */
437
834
  setDefaultPanelTitle(defaultTitle: string | undefined): void;
438
835
  /**
439
- * NOTE: UNSTABLE API SUBJECT TO CHANGE
440
- *
441
836
  * Subscribe to receive the entire time range of messages for a given topic for the current data source.
442
837
  *
443
- * See `SubscribeMessageRangeArgs` for more information on behavior.
838
+ * See {@link SubscribeMessageRangeArgs} for more information on behavior.
444
839
  *
445
- * Note: This will not read messages for live sources, like foxglove bridge, rosbridge, or ROS1
446
- * native. For those messages you will still need to use `context.subscribe()` and
447
- * watch("currentFrame").
840
+ * Note: This will not read messages for live sources, like foxglove_bridge, rosbridge, or ROS 1
841
+ * native connections. For those messages you will still need to use `context.subscribe()` and
842
+ * `watch("currentFrame")`.
448
843
  *
449
844
  * @returns A function that will unsubscribe from the topic, cancel the active async iterator,
450
- * and prevent `onReset` from being called again.
845
+ * and prevent {@link SubscribeMessageRangeArgs.onNewRangeIterator | onNewRangeIterator} from being called again.
846
+ */
847
+ subscribeMessageRange?: (args: SubscribeMessageRangeArgs) => () => void;
848
+
849
+ /**
850
+ * @deprecated Renamed to `subscribeMessageRange`. Please use that method instead.
451
851
  */
452
852
  UNSTABLE_subscribeMessageRange?: (args: SubscribeMessageRangeArgs) => () => void;
453
853
  };
454
854
 
855
+ /**
856
+ * This type represents the arguments you pass to {@link ExtensionContext.registerPanel}.
857
+ *
858
+ * @category Custom panels
859
+ */
455
860
  export type ExtensionPanelRegistration = {
456
- // Unique name of the panel within your extension
457
- //
458
- // NOTE: Panel names within your extension must be unique. The panel name identifies this panel
459
- // within a layout. Changing the panel name will cause layouts using the old name unable to load
460
- // your panel.
861
+ /**
862
+ * Unique name of the panel within your extension
863
+ *
864
+ * NOTE: Panel names within your extension must be unique. The panel name identifies this panel
865
+ * within a layout. Changing the panel name will cause layouts using the old name unable to load
866
+ * your panel.
867
+ */
461
868
  name: string;
462
869
 
463
870
  /**
464
871
  * This function is invoked when your panel is initialized
465
- * @return: (optional) A function which is called when the panel is removed or replaced. Typically intended for cleanup logic to gracefully teardown your panel.
872
+ *
873
+ * @returns (optional) A function which will be called when the panel is removed or replaced.
874
+ * Perform any cleanup logic here to gracefully tear down your panel.
466
875
  */
467
876
  initPanel: (context: PanelExtensionContext) => void | (() => void);
468
877
  };
469
878
 
879
+ /**
880
+ * This type represents the arguments you pass to {@link ExtensionContext.registerMessageConverter}.
881
+ *
882
+ * @category Message converters
883
+ */
470
884
  export type RegisterMessageConverterArgs<Src> = {
471
885
  fromSchemaName: string;
472
886
  toSchemaName: string;
473
887
  converter: (msg: Src, event: Immutable<MessageEvent<Src>>) => unknown;
474
888
  };
475
889
 
476
- type BaseTopic = { name: string; schemaName?: string };
477
- type TopicAlias = { name: string; sourceTopicName: string };
890
+ /** @category Topic aliases */
891
+ export type BaseTopic = { name: string; schemaName?: string };
892
+ /** @category Topic aliases */
893
+ export type TopicAlias = { name: string; sourceTopicName: string };
478
894
 
479
895
  /**
480
- * An AliasFunction takes a list of data source topics and variables and outputs
481
- * a list of aliased topics.
896
+ * A TopicAliasFunction takes a list of data source topics and variables and outputs
897
+ * a list of aliased topics. Register this function using {@link ExtensionContext.registerTopicAliases}.
898
+ *
899
+ * @category Topic aliases
482
900
  */
483
901
  export type TopicAliasFunction = (
484
902
  args: Immutable<{
@@ -487,41 +905,116 @@ export type TopicAliasFunction = (
487
905
  }>,
488
906
  ) => TopicAlias[];
489
907
 
908
+ /**
909
+ * The {@link ExtensionModule.activate | activate} function's first argument is an
910
+ * `ExtensionContext` — this context allows you to extend Foxglove for your custom workflows.
911
+ *
912
+ * ```typescript
913
+ * export function activate(extensionContext: ExtensionContext) {
914
+ * // ... call methods on the extensionContext to extend Foxglove
915
+ * }
916
+ * ```
917
+ *
918
+ * @category Entry point
919
+ */
490
920
  export interface ExtensionContext {
491
- /** The current _mode_ of the application. */
921
+ /**
922
+ * @deprecated This field is no longer used.
923
+ * @hidden
924
+ */
492
925
  readonly mode: "production" | "development" | "test";
493
926
 
927
+ /**
928
+ * `registerPanel` adds a new panel to the Foxglove interface. To register a panel you provide a
929
+ * `name` and an `initPanel` function.
930
+ *
931
+ * The `initPanel` function accepts a {@link PanelExtensionContext} argument, which contains
932
+ * properties and methods for accessing panel data and rendering UI updates. It also returns an
933
+ * optional cleanup function to run when the extension `panelElement` unmounts.
934
+ *
935
+ * See the [Creating a custom
936
+ * panel](https://docs.foxglove.dev/docs/visualization/extensions/guides/create-custom-panel)
937
+ * guide for more details.
938
+ */
494
939
  registerPanel(params: ExtensionPanelRegistration): void;
495
940
 
496
941
  /**
497
- * Register a function to convert messages from one schema to another.
942
+ * `registerMessageConverter` registers a function to convert messages from one schema to another.
943
+ *
944
+ * Message converters allow you to leverage Foxglove's built-in visualization panels by
945
+ * transforming messages to adhere to Foxglove-supported schemas — for example, you can convert
946
+ * your custom GPS messages to
947
+ * [`foxglove.LocationFix`](https://docs.foxglove.dev/docs/visualization/message-schemas/location-fix)
948
+ * messages for visualization in the [Map
949
+ * panel](https://docs.foxglove.dev/docs/visualization/panels/map).
498
950
  *
499
- * A converter function is invoked when a panel subscribes to a topic with the `convertTo` option.
500
- * The return value of the converter function is the converted message and is provided to the
501
- * panel.
951
+ * Whenever a panel subscribes to a topic with the
952
+ * [`convertTo`](https://docs.foxglove.dev/docs/visualization/extensions/api/panel#message-converters)
953
+ * option, the converter function runs on the original message and outputs the converted message,
954
+ * which it then provides it to the panel. If the function returns `undefined`, the output is
955
+ * ignored, and no message is provided to the panel. This is useful if you want to selectively
956
+ * output converted messages depending on the input messages' contents.
502
957
  *
503
- * If the converter function invocation returns _undefined_, the output of the converter for that
504
- * message is ignored and no message is provided to the panel. This is useful in instances where
505
- * you might want to selectively output a converted schema depending on the input message.
958
+ * See the [Creating a message
959
+ * converter](https://docs.foxglove.dev/docs/visualization/extensions/guides/create-message-converter)
960
+ * guide for more details.
961
+
506
962
  */
507
963
  registerMessageConverter<Src>(args: RegisterMessageConverterArgs<Src>): void;
508
964
 
509
965
  /**
510
- * Registers a new alias function with the extension context. The function will be
511
- * called every time there is a new set of topics and variables and returns an array of
512
- * topic aliases.
966
+ * `registerTopicAliases` registers a function to compute topic aliases. The provided alias
967
+ * function should accept an argument with two fields `topics` with the data source's original
968
+ * topics and `globalVariables` with the current layout's variables – and return a list of aliased
969
+ * topics.
970
+ *
971
+ * Your alias function runs whenever there are changes to the data source topics or variables. Any
972
+ * aliases it returns are added to the data source topics (replacing any previously returned
973
+ * aliases) and available for subscribing or use within message paths as if they were real topics.
513
974
  */
514
975
  registerTopicAliases(aliasFunction: TopicAliasFunction): void;
515
976
  }
516
977
 
978
+ /**
979
+ * @inline
980
+ * @hidden
981
+ */
517
982
  export type ExtensionActivate = (extensionContext: ExtensionContext) => void;
518
983
 
519
- // ExtensionModule describes the interface your extension entry level module must export
520
- // as its default export
984
+ /**
985
+ * ExtensionModule describes the interface your extension module must export. This typically corresponds to your `index.ts` file.
986
+ *
987
+ * You may use either a `default` export or named export syntax:
988
+ *
989
+ * ```typescript
990
+ * export function activate(context: ExtensionContext) {
991
+ * // ... call methods on the extensionContext to extend Foxglove
992
+ * }
993
+ * ```
994
+ *
995
+ * ```typescript
996
+ * function activate(context: ExtensionContext) {
997
+ * // ... call methods on the extensionContext to extend Foxglove
998
+ * }
999
+ * export default { activate };
1000
+ * ```
1001
+ *
1002
+ * @category Entry point
1003
+ */
521
1004
  export interface ExtensionModule {
1005
+ /**
1006
+ * This function will be called when your extension is loaded. In this function, you can register
1007
+ * your custom panels or other types of extension features.
1008
+ */
522
1009
  activate: ExtensionActivate;
523
1010
  }
524
1011
 
1012
+ /**
1013
+ * Icons that can be displayed in the settings tree. Most icon names come from the Material Design
1014
+ * icon set, but the exact icons displayed may change in the future.
1015
+ *
1016
+ * @category Custom panels
1017
+ */
525
1018
  export type SettingsIcon =
526
1019
  | "Add"
527
1020
  | "Addchart"
@@ -566,6 +1059,8 @@ export type SettingsIcon =
566
1059
  /**
567
1060
  * A settings tree field specifies the input type and the value of a field
568
1061
  * in the settings editor.
1062
+ *
1063
+ * @category Custom panels
569
1064
  */
570
1065
  export type SettingsTreeFieldValue =
571
1066
  | {
@@ -678,6 +1173,12 @@ export type SettingsTreeFieldValue =
678
1173
  min?: number;
679
1174
  };
680
1175
 
1176
+ /**
1177
+ * A settings tree field specifies the input type and the value of a field
1178
+ * in the settings editor.
1179
+ *
1180
+ * @category Custom panels
1181
+ */
681
1182
  export type SettingsTreeField = SettingsTreeFieldValue & {
682
1183
  /**
683
1184
  * True if the field is disabled.
@@ -705,10 +1206,21 @@ export type SettingsTreeField = SettingsTreeFieldValue & {
705
1206
  error?: string;
706
1207
  };
707
1208
 
1209
+ /**
1210
+ * @category Custom panels
1211
+ */
708
1212
  export type SettingsTreeFields = Record<string, undefined | SettingsTreeField>;
709
1213
 
1214
+ /**
1215
+ * @category Custom panels
1216
+ */
710
1217
  export type SettingsTreeChildren = Record<string, undefined | SettingsTreeNode>;
711
1218
 
1219
+ /**
1220
+ * An action included in the action menu for a settings node.
1221
+ *
1222
+ * @category Custom panels
1223
+ */
712
1224
  export type SettingsTreeNodeActionItem = {
713
1225
  type: "action";
714
1226
 
@@ -735,13 +1247,23 @@ export type SettingsTreeNodeActionItem = {
735
1247
  display?: "menu" | "inline";
736
1248
  };
737
1249
 
1250
+ /**
1251
+ * @category Custom panels
1252
+ */
738
1253
  export type SettingsTreeNodeActionDivider = { type: "divider" };
739
1254
 
740
1255
  /**
741
1256
  * An action included in the action menu for a settings node.
1257
+ *
1258
+ * @category Custom panels
742
1259
  */
743
1260
  export type SettingsTreeNodeAction = SettingsTreeNodeActionItem | SettingsTreeNodeActionDivider;
744
1261
 
1262
+ /**
1263
+ * A node represents a single item or group of items in the settings tree.
1264
+ *
1265
+ * @category Custom panels
1266
+ */
745
1267
  export type SettingsTreeNode = {
746
1268
  /**
747
1269
  * An array of actions that can be performed on this node.
@@ -816,6 +1338,8 @@ type DistributivePick<T, K extends keyof T> = T extends unknown ? Pick<T, K> : n
816
1338
  /**
817
1339
  * Represents actions that can be dispatched to source of the SettingsTree to implement
818
1340
  * edits and updates.
1341
+ *
1342
+ * @category Custom panels
819
1343
  */
820
1344
  export type SettingsTreeAction =
821
1345
  | {
@@ -830,6 +1354,10 @@ export type SettingsTreeAction =
830
1354
  payload: { id: string; path: readonly string[] };
831
1355
  };
832
1356
 
1357
+ /**
1358
+ * @inline
1359
+ * @hidden
1360
+ */
833
1361
  export type SettingsTreeNodes = Record<string, undefined | SettingsTreeNode>;
834
1362
 
835
1363
  /**
@@ -841,6 +1369,7 @@ export type SettingsTreeNodes = Record<string, undefined | SettingsTreeNode>;
841
1369
  *
842
1370
  * For example, for the following tree:
843
1371
  *
1372
+ * ```json
844
1373
  * root: {
845
1374
  * children: {
846
1375
  * a: {
@@ -858,12 +1387,15 @@ export type SettingsTreeNodes = Record<string, undefined | SettingsTreeNode>;
858
1387
  * },
859
1388
  * },
860
1389
  * },
1390
+ * ```
861
1391
  *
862
- * the path to the node at b would be ["a", "b"] and the path to the toggleMe
863
- * field would be ["a", "b", "toggleMe"]. These paths are used in the
1392
+ * the path to the node at b would be `["a", "b"]` and the path to the toggleMe
1393
+ * field would be `["a", "b", "toggleMe"]`. These paths are used in the
864
1394
  * actionHandler, which responds to updates to values in the tree, and also in
865
1395
  * the focusedPath, which is used to focus the editor UI at a particular node
866
1396
  * in the tree.
1397
+ *
1398
+ * @category Custom panels
867
1399
  */
868
1400
  export type SettingsTree = {
869
1401
  /**