diff --git a/packages/studio-base/src/PanelAPI/useConfigById.ts b/packages/studio-base/src/PanelAPI/useConfigById.ts index 1ca8818d1e5..137a01a50b1 100644 --- a/packages/studio-base/src/PanelAPI/useConfigById.ts +++ b/packages/studio-base/src/PanelAPI/useConfigById.ts @@ -6,6 +6,10 @@ import * as _ from "lodash-es"; import { useCallback } from "react"; import { DeepPartial } from "ts-essentials"; +import { + getTopicToSchemaNameMap, + useMessagePipeline, +} from "@foxglove/studio-base/components/MessagePipeline"; import { LayoutState, useCurrentLayoutActions, @@ -27,6 +31,7 @@ export default function useConfigById>( ): [Config | undefined, SaveConfig] { const { getCurrentLayoutState, savePanelConfigs } = useCurrentLayoutActions(); const extensionSettings = useExtensionCatalog(getExtensionPanelSettings); + const topicToSchemaNameMap = useMessagePipeline(getTopicToSchemaNameMap); const configSelector = useCallback( (state: DeepPartial) => { @@ -37,13 +42,21 @@ export default function useConfigById>( const topics = Object.keys(stateConfig?.topics ?? {}); const topicsSettings = _.merge( {}, - ...topics.map((topic) => ({ [topic]: extensionSettings[topic]?.defaultConfig })), + ...topics.map((topic) => { + const schemaName = topicToSchemaNameMap[topic]; + if (schemaName == undefined) { + return {}; + } + return { + [topic]: extensionSettings[schemaName]?.defaultConfig, + }; + }), stateConfig?.topics, ); return maybeCast({ ...stateConfig, topics: topicsSettings }); }, - [panelId, extensionSettings], + [panelId, extensionSettings, topicToSchemaNameMap], ); const config = useCurrentLayoutSelector(configSelector); diff --git a/packages/studio-base/src/components/MessagePipeline/index.tsx b/packages/studio-base/src/components/MessagePipeline/index.tsx index 421b71954fc..2856042e172 100644 --- a/packages/studio-base/src/components/MessagePipeline/index.tsx +++ b/packages/studio-base/src/components/MessagePipeline/index.tsx @@ -335,3 +335,8 @@ function createPlayerListener(args: { }, }; } + +export const getTopicToSchemaNameMap = ( + state: MessagePipelineContext, +): Record => + _.mapValues(_.keyBy(state.sortedTopics, "name"), ({ schemaName }) => schemaName); diff --git a/packages/studio-base/src/components/PanelExtensionAdapter/renderState.ts b/packages/studio-base/src/components/PanelExtensionAdapter/renderState.ts index 8f5a26fd3df..b906c766561 100644 --- a/packages/studio-base/src/components/PanelExtensionAdapter/renderState.ts +++ b/packages/studio-base/src/components/PanelExtensionAdapter/renderState.ts @@ -2,6 +2,7 @@ // License, v2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/ +import * as _ from "lodash-es"; import memoizeWeak from "memoize-weak"; import { Writable } from "ts-essentials"; @@ -103,6 +104,11 @@ function initRenderStateBuilder(): BuildRenderStateFn { config, } = input; + const topicToSchemaNameMap = _.mapValues( + _.keyBy(sortedTopics, "name"), + ({ schemaName }) => schemaName, + ); + // Should render indicates whether any fields of render state are updated let shouldRender = false; @@ -207,11 +213,15 @@ function initRenderStateBuilder(): BuildRenderStateFn { if (unconvertedSubscriptionTopics.has(messageEvent.topic)) { postProcessedFrame.push(messageEvent); } - convertMessage( - { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, - topicSchemaConverters, - postProcessedFrame, - ); + + const schemaName = topicToSchemaNameMap[messageEvent.topic]; + if (schemaName) { + convertMessage( + { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, + topicSchemaConverters, + postProcessedFrame, + ); + } lastMessageByTopic.set(messageEvent.topic, messageEvent); } renderState.currentFrame = postProcessedFrame; @@ -221,11 +231,14 @@ function initRenderStateBuilder(): BuildRenderStateFn { // only the new conversions on our most recent message on each topic. const postProcessedFrame: MessageEvent[] = []; for (const messageEvent of lastMessageByTopic.values()) { - convertMessage( - { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, - newConverters, - postProcessedFrame, - ); + const schemaName = topicToSchemaNameMap[messageEvent.topic]; + if (schemaName) { + convertMessage( + { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, + newConverters, + postProcessedFrame, + ); + } } renderState.currentFrame = postProcessedFrame; shouldRender = true; @@ -273,11 +286,15 @@ function initRenderStateBuilder(): BuildRenderStateFn { if (unconvertedSubscriptionTopics.has(messageEvent.topic)) { frames.push(messageEvent); } - convertMessage( - { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, - topicSchemaConverters, - frames, - ); + + const schemaName = topicToSchemaNameMap[messageEvent.topic]; + if (schemaName) { + convertMessage( + { ...messageEvent, topicConfig: config?.topics[messageEvent.topic] }, + topicSchemaConverters, + frames, + ); + } }, ); } diff --git a/packages/studio-base/src/components/PanelSettings/index.tsx b/packages/studio-base/src/components/PanelSettings/index.tsx index 53f0d782847..730f2621cf8 100644 --- a/packages/studio-base/src/components/PanelSettings/index.tsx +++ b/packages/studio-base/src/components/PanelSettings/index.tsx @@ -4,6 +4,7 @@ import { Divider, Typography } from "@mui/material"; import * as _ from "lodash-es"; +import { to } from "mathjs"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useUnmount } from "react-use"; @@ -12,6 +13,10 @@ import { SettingsTree } from "@foxglove/studio"; import { AppSetting } from "@foxglove/studio-base/AppSetting"; import { useConfigById } from "@foxglove/studio-base/PanelAPI"; import EmptyState from "@foxglove/studio-base/components/EmptyState"; +import { + getTopicToSchemaNameMap, + useMessagePipeline, +} from "@foxglove/studio-base/components/MessagePipeline"; import { ActionMenu } from "@foxglove/studio-base/components/PanelSettings/ActionMenu"; import SettingsTreeEditor from "@foxglove/studio-base/components/SettingsTreeEditor"; import { ShareJsonModal } from "@foxglove/studio-base/components/ShareJsonModal"; @@ -151,6 +156,8 @@ export default function PanelSettings({ const extensionSettings = useExtensionCatalog(getExtensionPanelSettings); + const topicToSchemaNameMap = useMessagePipeline(getTopicToSchemaNameMap); + const settingsTree = usePanelStateStore((state) => { if (selectedPanelId) { const set = state.settingsTrees[selectedPanelId]; @@ -159,9 +166,15 @@ export default function PanelSettings({ const topicsConfig = maybeCast<{ topics: Record }>(config)?.topics; const topicsSettings = _.merge( {}, - ...topics.map((topic) => ({ - [topic]: extensionSettings[panelType]?.[topic]?.settings(topicsConfig?.[topic]), - })), + ...topics.map((topic) => { + const schemaName = topicToSchemaNameMap[topic]; + if (schemaName == undefined) { + return {}; + } + return { + [topic]: extensionSettings[panelType]?.[schemaName]?.settings(topicsConfig?.[topic]), + }; + }), ); return { ...set, nodes: _.merge({}, set.nodes, { topics: { children: topicsSettings } }) }; diff --git a/packages/studio-base/src/context/ExtensionCatalogContext.ts b/packages/studio-base/src/context/ExtensionCatalogContext.ts index bfd048e77fe..140fe1e0b6d 100644 --- a/packages/studio-base/src/context/ExtensionCatalogContext.ts +++ b/packages/studio-base/src/context/ExtensionCatalogContext.ts @@ -2,7 +2,6 @@ // License, v2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/ -import * as _ from "lodash-es"; import { createContext } from "react"; import { StoreApi, useStore } from "zustand"; @@ -33,8 +32,11 @@ export type ExtensionCatalog = Immutable<{ installedExtensions: undefined | ExtensionInfo[]; installedPanels: undefined | Record; - installedMessageConverters: undefined | RegisterMessageConverterArgs[]; + installedMessageConverters: + | undefined + | Omit, "panelSettings">[]; installedTopicAliasFunctions: undefined | TopicAliasFunctions; + panelSettings: undefined | Record>>; }>; export const ExtensionCatalogContext = createContext>( @@ -49,8 +51,5 @@ export function useExtensionCatalog(selector: (registry: ExtensionCatalog) => export function getExtensionPanelSettings( reg: ExtensionCatalog, ): Record>> { - return _.merge( - {}, - ...(reg.installedMessageConverters ?? []).map((converter) => converter.panelSettings), - ); + return reg.panelSettings ?? {}; } diff --git a/packages/studio-base/src/providers/ExtensionCatalogProvider.tsx b/packages/studio-base/src/providers/ExtensionCatalogProvider.tsx index f346ae30121..9e936a59bed 100644 --- a/packages/studio-base/src/providers/ExtensionCatalogProvider.tsx +++ b/packages/studio-base/src/providers/ExtensionCatalogProvider.tsx @@ -2,6 +2,7 @@ // License, v2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/ +import * as _ from "lodash-es"; import React, { PropsWithChildren, useEffect, useState } from "react"; import ReactDOM from "react-dom"; import { StoreApi, createStore } from "zustand"; @@ -10,6 +11,7 @@ import Logger from "@foxglove/log"; import { ExtensionContext, ExtensionModule, + PanelSettings, RegisterMessageConverterArgs, TopicAliasFunction, } from "@foxglove/studio"; @@ -32,6 +34,7 @@ type ContributionPoints = { panels: Record; messageConverters: MessageConverter[]; topicAliasFunctions: TopicAliasFunctions; + panelSettings: Record>>; }; function activateExtension( @@ -44,6 +47,8 @@ function activateExtension( const messageConverters: RegisterMessageConverterArgs[] = []; + const panelSettings: Record>> = {}; + const topicAliasFunctions: ContributionPoints["topicAliasFunctions"] = []; log.debug(`Activating extension ${extension.qualifiedName}`); @@ -87,6 +92,12 @@ function activateExtension( ...args, extensionNamespace: extension.namespace, } as MessageConverter); + + const converterSettings = _.mapValues(args.panelSettings, (settings) => ({ + [args.fromSchemaName]: settings, + })); + + _.merge(panelSettings, converterSettings); }, registerTopicAliases: (aliasFunction: TopicAliasFunction) => { @@ -111,6 +122,7 @@ function activateExtension( panels, messageConverters, topicAliasFunctions, + panelSettings, }; } @@ -145,6 +157,7 @@ function createExtensionRegistryStore( panels: {}, messageConverters: [], topicAliasFunctions: [], + panelSettings: {}, }; for (const loader of loaders) { try { @@ -154,6 +167,7 @@ function createExtensionRegistryStore( const unwrappedExtensionSource = await loader.loadExtension(extension.id); const contributionPoints = activateExtension(extension, unwrappedExtensionSource); Object.assign(allContributionPoints.panels, contributionPoints.panels); + _.merge(allContributionPoints.panelSettings, contributionPoints.panelSettings); allContributionPoints.messageConverters.push(...contributionPoints.messageConverters); allContributionPoints.topicAliasFunctions.push( ...contributionPoints.topicAliasFunctions, @@ -174,6 +188,7 @@ function createExtensionRegistryStore( installedPanels: allContributionPoints.panels, installedMessageConverters: allContributionPoints.messageConverters, installedTopicAliasFunctions: allContributionPoints.topicAliasFunctions, + panelSettings: allContributionPoints.panelSettings, }); }, @@ -186,6 +201,8 @@ function createExtensionRegistryStore( installedTopicAliasFunctions: [], + panelSettings: {}, + uninstallExtension: async (namespace: ExtensionNamespace, id: string) => { const namespacedLoader = loaders.find((loader) => loader.namespace === namespace); if (namespacedLoader == undefined) { diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts index 8859fce0e54..b5b9bfe6854 100644 --- a/packages/studio/src/index.ts +++ b/packages/studio/src/index.ts @@ -423,7 +423,7 @@ export type RegisterMessageConverterArgs = { fromSchemaName: string; toSchemaName: string; converter: (msg: Src, event: Immutable>) => unknown; - panelSettings?: Record>>; + panelSettings?: Record>; }; type BaseTopic = { name: string; schemaName?: string };