Add basic foxglove extension for analysis

This patch is the result of running the following:

    $ cd aos/analysis/
    $ ../../tools/foxglove/create-foxglove-extension foxglove_extension

and committing the generated changes.

Future patches will remove the example panel and add actually useful
functionality.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I131be7dab022ed9bfde7cc606d650928c955d0be
diff --git a/aos/analysis/foxglove_extension/src/ExamplePanel.tsx b/aos/analysis/foxglove_extension/src/ExamplePanel.tsx
new file mode 100644
index 0000000..61ccb6a
--- /dev/null
+++ b/aos/analysis/foxglove_extension/src/ExamplePanel.tsx
@@ -0,0 +1,86 @@
+import { Immutable, MessageEvent, PanelExtensionContext, Topic } from "@foxglove/studio";
+import { useEffect, useLayoutEffect, useState } from "react";
+import ReactDOM from "react-dom";
+
+function ExamplePanel({ context }: { context: PanelExtensionContext }): JSX.Element {
+  const [topics, setTopics] = useState<undefined | Immutable<Topic[]>>();
+  const [messages, setMessages] = useState<undefined | Immutable<MessageEvent[]>>();
+
+  const [renderDone, setRenderDone] = useState<(() => void) | undefined>();
+
+  // We use a layout effect to setup render handling for our panel. We also setup some topic subscriptions.
+  useLayoutEffect(() => {
+    // The render handler is run by the broader studio system during playback when your panel
+    // needs to render because the fields it is watching have changed. How you handle rendering depends on your framework.
+    // You can only setup one render handler - usually early on in setting up your panel.
+    //
+    // Without a render handler your panel will never receive updates.
+    //
+    // The render handler could be invoked as often as 60hz during playback if fields are changing often.
+    context.onRender = (renderState, done) => {
+      // render functions receive a _done_ callback. You MUST call this callback to indicate your panel has finished rendering.
+      // Your panel will not receive another render callback until _done_ is called from a prior render. If your panel is not done
+      // rendering before the next render call, studio shows a notification to the user that your panel is delayed.
+      //
+      // Set the done callback into a state variable to trigger a re-render.
+      setRenderDone(() => done);
+
+      // We may have new topics - since we are also watching for messages in the current frame, topics may not have changed
+      // It is up to you to determine the correct action when state has not changed.
+      setTopics(renderState.topics);
+
+      // currentFrame has messages on subscribed topics since the last render call
+      setMessages(renderState.currentFrame);
+    };
+
+    // After adding a render handler, you must indicate which fields from RenderState will trigger updates.
+    // 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.
+
+    // tell the panel context that we care about any update to the _topic_ field of RenderState
+    context.watch("topics");
+
+    // tell the panel context we want messages for the current frame for topics we've subscribed to
+    // This corresponds to the _currentFrame_ field of render state.
+    context.watch("currentFrame");
+
+    // subscribe to some topics, you could do this within other effects, based on input fields, etc
+    // Once you subscribe to topics, currentFrame will contain message events from those topics (assuming there are messages).
+    context.subscribe([{ topic: "/some/topic" }]);
+  }, [context]);
+
+  // invoke the done callback once the render is complete
+  useEffect(() => {
+    renderDone?.();
+  }, [renderDone]);
+
+  return (
+    <div style={{ padding: "1rem" }}>
+      <h2>Welcome to your new extension panel!</h2>
+      <p>
+        Check the{" "}
+        <a href="https://foxglove.dev/docs/studio/extensions/getting-started">documentation</a> for
+        more details on building extension panels for Foxglove Studio.
+      </p>
+      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", rowGap: "0.2rem" }}>
+        <b style={{ borderBottom: "1px solid" }}>Topic</b>
+        <b style={{ borderBottom: "1px solid" }}>Datatype</b>
+        {(topics ?? []).map((topic) => (
+          <>
+            <div key={topic.name}>{topic.name}</div>
+            <div key={topic.datatype}>{topic.datatype}</div>
+          </>
+        ))}
+      </div>
+      <div>{messages?.length}</div>
+    </div>
+  );
+}
+
+export function initExamplePanel(context: PanelExtensionContext): () => void {
+  ReactDOM.render(<ExamplePanel context={context} />, context.panelElement);
+
+  // Return a function to run when the panel is removed
+  return () => {
+    ReactDOM.unmountComponentAtNode(context.panelElement);
+  };
+}