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/.eslintrc.yaml b/aos/analysis/foxglove_extension/.eslintrc.yaml
new file mode 100644
index 0000000..10d5ae0
--- /dev/null
+++ b/aos/analysis/foxglove_extension/.eslintrc.yaml
@@ -0,0 +1,27 @@
+root: true
+
+env:
+ browser: true
+ es2020: true
+ node: false
+
+ignorePatterns:
+ - dist
+
+plugins:
+ - jest
+
+extends:
+ - plugin:@foxglove/base
+ - plugin:@foxglove/react
+
+rules:
+ react-hooks/exhaustive-deps:
+ - error
+
+overrides:
+ - files: ["*.ts", "*.tsx"]
+ extends:
+ - plugin:@foxglove/typescript
+ parserOptions:
+ project: ./tsconfig.json
diff --git a/aos/analysis/foxglove_extension/.gitignore b/aos/analysis/foxglove_extension/.gitignore
new file mode 100644
index 0000000..e9b56e6
--- /dev/null
+++ b/aos/analysis/foxglove_extension/.gitignore
@@ -0,0 +1,107 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Build output
+www/js/*
+
+# MacOS
+.DS_Store
diff --git a/aos/analysis/foxglove_extension/.prettierrc.yaml b/aos/analysis/foxglove_extension/.prettierrc.yaml
new file mode 100644
index 0000000..e57cc20
--- /dev/null
+++ b/aos/analysis/foxglove_extension/.prettierrc.yaml
@@ -0,0 +1,5 @@
+arrowParens: always
+printWidth: 100
+trailingComma: "all"
+tabWidth: 2
+semi: true
diff --git a/aos/analysis/foxglove_extension/BUILD.bazel b/aos/analysis/foxglove_extension/BUILD.bazel
new file mode 100644
index 0000000..60f03dc
--- /dev/null
+++ b/aos/analysis/foxglove_extension/BUILD.bazel
@@ -0,0 +1,8 @@
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("//tools/build_rules:foxglove.bzl", "foxglove_extension")
+
+npm_link_all_packages(name = "node_modules")
+
+foxglove_extension(
+ name = "extension",
+)
diff --git a/aos/analysis/foxglove_extension/CHANGELOG.md b/aos/analysis/foxglove_extension/CHANGELOG.md
new file mode 100644
index 0000000..7bff0ee
--- /dev/null
+++ b/aos/analysis/foxglove_extension/CHANGELOG.md
@@ -0,0 +1,5 @@
+# foxglove_extension version history
+
+## 0.0.0
+
+- Alpha testing
diff --git a/aos/analysis/foxglove_extension/LICENSE b/aos/analysis/foxglove_extension/LICENSE
new file mode 100644
index 0000000..03127ea
--- /dev/null
+++ b/aos/analysis/foxglove_extension/LICENSE
@@ -0,0 +1 @@
+Add your extension license here and update the package.json "license" field before publishing. License templates can be found at <https://github.com/licenses/license-templates>.
diff --git a/aos/analysis/foxglove_extension/README.md b/aos/analysis/foxglove_extension/README.md
new file mode 100644
index 0000000..b161e8e
--- /dev/null
+++ b/aos/analysis/foxglove_extension/README.md
@@ -0,0 +1,41 @@
+# foxglove_extension
+
+## _A Foxglove Studio Extension_
+
+[Foxglove Studio](https://github.com/foxglove/studio) allows developers to create extensions, or custom code that is loaded and executed inside the Foxglove Studio application. This can be used to add custom panels. Extensions are authored in TypeScript using the `@foxglove/studio` SDK.
+
+## Develop
+
+Extension development uses the `npm` package manager to install development dependencies and run build scripts.
+
+To install extension dependencies, run `npm` from the root of the extension package.
+
+```sh
+npm install
+```
+
+To build and install the extension into your local Foxglove Studio desktop app, run:
+
+```sh
+npm run local-install
+```
+
+Open the `Foxglove Studio` desktop (or `ctrl-R` to refresh if it is already open). Your extension is installed and available within the app.
+
+## Package
+
+Extensions are packaged into `.foxe` files. These files contain the metadata (package.json) and the build code for the extension.
+
+Before packaging, make sure to set `name`, `publisher`, `version`, and `description` fields in _package.json_. When ready to distribute the extension, run:
+
+```sh
+npm run package
+```
+
+This command will package the extension into a `.foxe` file in the local directory.
+
+## Publish
+
+You can publish the extension for the public marketplace or privately for your organization.
+
+See documentation here: https://foxglove.dev/docs/studio/extensions/publish#packaging-your-extension
diff --git a/aos/analysis/foxglove_extension/package.json b/aos/analysis/foxglove_extension/package.json
new file mode 100644
index 0000000..7678259
--- /dev/null
+++ b/aos/analysis/foxglove_extension/package.json
@@ -0,0 +1,43 @@
+{
+ "dependencies": {
+ "@foxglove/eslint-plugin": "^1",
+ "@foxglove/studio": "^1",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@typescript-eslint/eslint-plugin": "^6",
+ "@typescript-eslint/parser": "^6",
+ "create-foxglove-extension": "^0",
+ "eslint": "^8",
+ "eslint-config-prettier": "^8",
+ "eslint-plugin-es": "^4",
+ "eslint-plugin-filenames": "^1",
+ "eslint-plugin-import": "^2",
+ "eslint-plugin-jest": "^27",
+ "eslint-plugin-prettier": "^5",
+ "eslint-plugin-react": "^7",
+ "eslint-plugin-react-hooks": "^4",
+ "prettier": "^3",
+ "react": "^18",
+ "react-dom": "^18",
+ "ts-loader": "^9",
+ "typescript": "^5"
+ },
+ "description": "",
+ "displayName": "foxglove_extension",
+ "homepage": "",
+ "keywords": [],
+ "license": "UNLICENSED",
+ "main": "./dist/extension.js",
+ "name": "foxglove_extension",
+ "publisher": "unknown",
+ "scripts": {
+ "build": "foxglove-extension build",
+ "foxglove:prepublish": "foxglove-extension build --mode production",
+ "lint": "eslint --report-unused-disable-directives --fix .",
+ "lint:ci": "eslint --report-unused-disable-directives .",
+ "local-install": "foxglove-extension install",
+ "package": "foxglove-extension package",
+ "pretest": "foxglove-extension pretest"
+ },
+ "version": "0.0.0"
+}
\ No newline at end of file
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);
+ };
+}
diff --git a/aos/analysis/foxglove_extension/src/index.ts b/aos/analysis/foxglove_extension/src/index.ts
new file mode 100644
index 0000000..63b17eb
--- /dev/null
+++ b/aos/analysis/foxglove_extension/src/index.ts
@@ -0,0 +1,6 @@
+import { ExtensionContext } from "@foxglove/studio";
+import { initExamplePanel } from "./ExamplePanel";
+
+export function activate(extensionContext: ExtensionContext): void {
+ extensionContext.registerPanel({ name: "example-panel", initPanel: initExamplePanel });
+}
diff --git a/aos/analysis/foxglove_extension/tsconfig.json b/aos/analysis/foxglove_extension/tsconfig.json
new file mode 100644
index 0000000..73995c3
--- /dev/null
+++ b/aos/analysis/foxglove_extension/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "extends": "create-foxglove-extension/tsconfig/tsconfig.json",
+
+ "include": ["./src/**/*"],
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "lib": ["dom", "es2022"],
+
+ // These two settings prevent typescript from emitting .d.ts files we don't need in
+ // the compiled extension.
+ "composite": false,
+ "declaration": false,
+
+ // Additional TypeScript error reporting checks are enabled by default to improve code quality.
+ // Enable/disable these checks as necessary to suit your coding preferences or work with
+ // existing code
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "forceConsistentCasingInFileNames": true
+ }
+}