Philipp Schrader | aa19132 | 2024-04-28 20:23:52 -0700 | [diff] [blame^] | 1 | import { Immutable, MessageEvent, PanelExtensionContext, Topic } from "@foxglove/studio"; |
| 2 | import { useEffect, useLayoutEffect, useState } from "react"; |
| 3 | import ReactDOM from "react-dom"; |
| 4 | |
| 5 | function 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 | |
| 79 | export 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 | } |