blob: 61ccb6af4143c93699b30f09f1c844f952eb6152 [file] [log] [blame]
Philipp Schraderaa191322024-04-28 20:23:52 -07001import { Immutable, MessageEvent, PanelExtensionContext, Topic } from "@foxglove/studio";
2import { useEffect, useLayoutEffect, useState } from "react";
3import ReactDOM from "react-dom";
4
5function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Element {
6 const [topics, setTopics] = useState<undefined | Immutable<Topic[]>>();
7 const [messages, setMessages] = useState<undefined | Immutable<MessageEvent[]>>();
8
9 const [renderDone, setRenderDone] = useState<(() => void) | undefined>();
10
11 // We use a layout effect to setup render handling for our panel. We also setup some topic subscriptions.
12 useLayoutEffect(() => {
13 // The render handler is run by the broader studio system during playback when your panel
14 // needs to render because the fields it is watching have changed. How you handle rendering depends on your framework.
15 // You can only setup one render handler - usually early on in setting up your panel.
16 //
17 // Without a render handler your panel will never receive updates.
18 //
19 // The render handler could be invoked as often as 60hz during playback if fields are changing often.
20 context.onRender = (renderState, done) => {
21 // render functions receive a _done_ callback. You MUST call this callback to indicate your panel has finished rendering.
22 // Your panel will not receive another render callback until _done_ is called from a prior render. If your panel is not done
23 // rendering before the next render call, studio shows a notification to the user that your panel is delayed.
24 //
25 // Set the done callback into a state variable to trigger a re-render.
26 setRenderDone(() => done);
27
28 // We may have new topics - since we are also watching for messages in the current frame, topics may not have changed
29 // It is up to you to determine the correct action when state has not changed.
30 setTopics(renderState.topics);
31
32 // currentFrame has messages on subscribed topics since the last render call
33 setMessages(renderState.currentFrame);
34 };
35
36 // After adding a render handler, you must indicate which fields from RenderState will trigger updates.
37 // If you do not watch any fields then your panel will never render since the panel context will assume you do not want any updates.
38
39 // tell the panel context that we care about any update to the _topic_ field of RenderState
40 context.watch("topics");
41
42 // tell the panel context we want messages for the current frame for topics we've subscribed to
43 // This corresponds to the _currentFrame_ field of render state.
44 context.watch("currentFrame");
45
46 // subscribe to some topics, you could do this within other effects, based on input fields, etc
47 // Once you subscribe to topics, currentFrame will contain message events from those topics (assuming there are messages).
48 context.subscribe([{ topic: "/some/topic" }]);
49 }, [context]);
50
51 // invoke the done callback once the render is complete
52 useEffect(() => {
53 renderDone?.();
54 }, [renderDone]);
55
56 return (
57 <div style={{ padding: "1rem" }}>
58 <h2>Welcome to your new extension panel!</h2>
59 <p>
60 Check the{" "}
61 <a href="https://foxglove.dev/docs/studio/extensions/getting-started">documentation</a> for
62 more details on building extension panels for Foxglove Studio.
63 </p>
64 <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", rowGap: "0.2rem" }}>
65 <b style={{ borderBottom: "1px solid" }}>Topic</b>
66 <b style={{ borderBottom: "1px solid" }}>Datatype</b>
67 {(topics ?? []).map((topic) => (
68 <>
69 <div key={topic.name}>{topic.name}</div>
70 <div key={topic.datatype}>{topic.datatype}</div>
71 </>
72 ))}
73 </div>
74 <div>{messages?.length}</div>
75 </div>
76 );
77}
78
79export function initExamplePanel(context: PanelExtensionContext): () => void {
80 ReactDOM.render(<ExamplePanel context={context} />, context.panelElement);
81
82 // Return a function to run when the panel is removed
83 return () => {
84 ReactDOM.unmountComponentAtNode(context.panelElement);
85 };
86}