Skip to content

Commit

Permalink
Allow the user to link settings to message converters depending on th…
Browse files Browse the repository at this point in the history
…e panel type and the message type
  • Loading branch information
FraLab09 committed Jun 24, 2024
1 parent 0f83366 commit ed5c4f5
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 20 deletions.
18 changes: 16 additions & 2 deletions packages/studio-base/src/PanelAPI/useConfigById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { useCallback } from "react";
import { DeepPartial } from "ts-essentials";

Expand All @@ -10,6 +11,10 @@ import {
useCurrentLayoutActions,
useCurrentLayoutSelector,
} from "@foxglove/studio-base/context/CurrentLayoutContext";
import {
getExtensionPanelSettings,
useExtensionCatalog,
} from "@foxglove/studio-base/context/ExtensionCatalogContext";
import { SaveConfig } from "@foxglove/studio-base/types/panels";
import { maybeCast } from "@foxglove/studio-base/util/maybeCast";

Expand All @@ -21,15 +26,24 @@ export default function useConfigById<Config extends Record<string, unknown>>(
panelId: string | undefined,
): [Config | undefined, SaveConfig<Config>] {
const { getCurrentLayoutState, savePanelConfigs } = useCurrentLayoutActions();
const extensionSettings = useExtensionCatalog(getExtensionPanelSettings);

const configSelector = useCallback(
(state: DeepPartial<LayoutState>) => {
if (panelId == undefined) {
return undefined;
}
return maybeCast<Config>(state.selectedLayout?.data?.configById?.[panelId]);
const stateConfig = maybeCast<Config>(state.selectedLayout?.data?.configById?.[panelId]);
const topics = Object.keys(stateConfig?.topics ?? {});
const topicsSettings = _.merge(
{},
...topics.map((topic) => ({ [topic]: extensionSettings[topic]?.defaultConfig })),
stateConfig?.topics,
);

return maybeCast<Config>({ ...stateConfig, topics: topicsSettings });
},
[panelId],
[panelId, extensionSettings],
);

const config = useCurrentLayoutSelector(configSelector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { useTheme } from "@mui/material";
import { produce } from "immer";
import { CSSProperties, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { useLatest } from "react-use";
import { v4 as uuid } from "uuid";
Expand All @@ -17,6 +18,7 @@ import {
ParameterValue,
RenderState,
SettingsTree,
SettingsTreeAction,
Subscription,
Time,
VariableValue,
Expand All @@ -31,6 +33,7 @@ import PanelToolbar from "@foxglove/studio-base/components/PanelToolbar";
import { useAppConfiguration } from "@foxglove/studio-base/context/AppConfigurationContext";
import {
ExtensionCatalog,
getExtensionPanelSettings,
useExtensionCatalog,
} from "@foxglove/studio-base/context/ExtensionCatalogContext";
import {
Expand All @@ -51,9 +54,10 @@ import {
} from "@foxglove/studio-base/providers/PanelStateContextProvider";
import { PanelConfig, SaveConfig } from "@foxglove/studio-base/types/panels";
import { assertNever } from "@foxglove/studio-base/util/assertNever";
import { maybeCast } from "@foxglove/studio-base/util/maybeCast";

import { PanelConfigVersionError } from "./PanelConfigVersionError";
import { initRenderStateBuilder } from "./renderState";
import { RenderStateConfig, initRenderStateBuilder } from "./renderState";
import { BuiltinPanelExtensionContext } from "./types";
import { useSharedPanelState } from "./useSharedPanelState";

Expand Down Expand Up @@ -112,7 +116,7 @@ function PanelExtensionAdapter(
//
// We store the config in a ref to avoid re-initializing the panel when the react config
// changes.
const initialState = useLatest(config);
const initialState = useLatest(maybeCast<RenderStateConfig>(config));

const messagePipelineContext = useMessagePipeline(selectContext);

Expand All @@ -121,7 +125,7 @@ function PanelExtensionAdapter(

const { capabilities, profile: dataSourceProfile, presence: playerPresence } = playerState;

const { openSiblingPanel, setMessagePathDropConfig } = usePanelContext();
const { openSiblingPanel, setMessagePathDropConfig, type: panelName } = usePanelContext();

const [panelId] = useState(() => uuid());
const isMounted = useSynchronousMountedState();
Expand Down Expand Up @@ -237,6 +241,7 @@ function PanelExtensionAdapter(
sortedTopics,
subscriptions: localSubscriptions,
watchedFields,
config: initialState.current,
});

if (!renderState) {
Expand Down Expand Up @@ -285,10 +290,13 @@ function PanelExtensionAdapter(
sharedPanelState,
sortedTopics,
watchedFields,
initialState,
]);

const updatePanelSettingsTree = usePanelSettingsTreeUpdate();

const extensionsSettings = useExtensionCatalog(getExtensionPanelSettings);

type PartialPanelExtensionContext = Omit<BuiltinPanelExtensionContext, "panelElement">;
const partialExtensionContext = useMemo<PartialPanelExtensionContext>(() => {
const layout: PanelExtensionContext["layout"] = {
Expand All @@ -310,6 +318,20 @@ function PanelExtensionAdapter(
},
};

const extensionSettingsActionHandler = (action: SettingsTreeAction) => {
const {
payload: { path },
} = action;

saveConfig(
produce<{ topics: Record<string, unknown> }>((draft) => {
if (path[0] === "topics" && path[1] != undefined) {
extensionsSettings[panelName]?.[path[1]]?.handler(action, draft.topics[path[1]]);
}
}),
);
};

return {
initialState: initialState.current,

Expand Down Expand Up @@ -499,7 +521,11 @@ function PanelExtensionAdapter(
if (!isMounted()) {
return;
}
updatePanelSettingsTree(settings);
const actionHandler: typeof settings.actionHandler = (action) => {
settings.actionHandler(action);
extensionSettingsActionHandler(action);
};
updatePanelSettingsTree({ ...settings, actionHandler });
},

setDefaultPanelTitle: (title: string) => {
Expand All @@ -514,22 +540,24 @@ function PanelExtensionAdapter(
},
};
}, [
capabilities,
clearHoverValue,
dataSourceProfile,
getMessagePipelineContext,
initialState,
seekPlayback,
dataSourceProfile,
setSharedPanelState,
capabilities,
isMounted,
openSiblingPanel,
panelId,
saveConfig,
seekPlayback,
setDefaultPanelTitle,
extensionsSettings,
panelName,
getMessagePipelineContext,
setGlobalVariables,
clearHoverValue,
setHoverValue,
setSharedPanelState,
setSubscriptions,
panelId,
updatePanelSettingsTree,
setDefaultPanelTitle,
setMessagePathDropConfig,
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function convertMessage(
message: convertedMessage,
originalMessageEvent: messageEvent,
sizeInBytes: messageEvent.sizeInBytes,
topicConfig: messageEvent.topicConfig,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ import {

const EmptyParameters = new Map<string, ParameterValue>();

export type RenderStateConfig = {
topics: Record<string, unknown>;
};

export type BuilderRenderStateInput = Immutable<{
appSettings: Map<string, AppSettingValue> | undefined;
colorScheme: RenderState["colorScheme"] | undefined;
Expand All @@ -50,6 +54,7 @@ export type BuilderRenderStateInput = Immutable<{
sortedTopics: readonly PlayerTopic[];
subscriptions: Subscription[];
watchedFields: Set<string>;
config: RenderStateConfig | undefined;
}>;

type BuildRenderStateFn = (input: BuilderRenderStateInput) => Immutable<RenderState> | undefined;
Expand Down Expand Up @@ -95,6 +100,7 @@ function initRenderStateBuilder(): BuildRenderStateFn {
sortedTopics,
subscriptions,
watchedFields,
config,
} = input;

// Should render indicates whether any fields of render state are updated
Expand Down Expand Up @@ -201,7 +207,11 @@ function initRenderStateBuilder(): BuildRenderStateFn {
if (unconvertedSubscriptionTopics.has(messageEvent.topic)) {
postProcessedFrame.push(messageEvent);
}
convertMessage(messageEvent, topicSchemaConverters, postProcessedFrame);
convertMessage(
{ ...messageEvent, topicConfig: config?.topics[messageEvent.topic] },
topicSchemaConverters,
postProcessedFrame,
);
lastMessageByTopic.set(messageEvent.topic, messageEvent);
}
renderState.currentFrame = postProcessedFrame;
Expand All @@ -211,7 +221,11 @@ 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, newConverters, postProcessedFrame);
convertMessage(
{ ...messageEvent, topicConfig: config?.topics[messageEvent.topic] },
newConverters,
postProcessedFrame,
);
}
renderState.currentFrame = postProcessedFrame;
shouldRender = true;
Expand Down Expand Up @@ -259,7 +273,11 @@ function initRenderStateBuilder(): BuildRenderStateFn {
if (unconvertedSubscriptionTopics.has(messageEvent.topic)) {
frames.push(messageEvent);
}
convertMessage(messageEvent, topicSchemaConverters, frames);
convertMessage(
{ ...messageEvent, topicConfig: config?.topics[messageEvent.topic] },
topicSchemaConverters,
frames,
);
},
);
}
Expand Down
29 changes: 26 additions & 3 deletions packages/studio-base/src/components/PanelSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { Divider, Typography } from "@mui/material";
import * as _ from "lodash-es";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useUnmount } from "react-use";
Expand All @@ -22,6 +23,10 @@ import {
useCurrentLayoutSelector,
useSelectedPanels,
} from "@foxglove/studio-base/context/CurrentLayoutContext";
import {
getExtensionPanelSettings,
useExtensionCatalog,
} from "@foxglove/studio-base/context/ExtensionCatalogContext";
import { usePanelCatalog } from "@foxglove/studio-base/context/PanelCatalogContext";
import {
PanelStateStore,
Expand All @@ -31,6 +36,7 @@ import { useAppConfigurationValue } from "@foxglove/studio-base/hooks";
import { PanelConfig } from "@foxglove/studio-base/types/panels";
import { TAB_PANEL_TYPE } from "@foxglove/studio-base/util/globalConstants";
import { getPanelTypeFromId } from "@foxglove/studio-base/util/layout";
import { maybeCast } from "@foxglove/studio-base/util/maybeCast";

const singlePanelIdSelector = (state: LayoutState) =>
typeof state.selectedLayout?.data?.layout === "string"
Expand Down Expand Up @@ -143,9 +149,26 @@ export default function PanelSettings({

const [config] = useConfigById(selectedPanelId);

const settingsTree = usePanelStateStore((state) =>
selectedPanelId ? state.settingsTrees[selectedPanelId] : undefined,
);
const extensionSettings = useExtensionCatalog(getExtensionPanelSettings);

const settingsTree = usePanelStateStore((state) => {
if (selectedPanelId) {
const set = state.settingsTrees[selectedPanelId];
if (set && panelType) {
const topics = Object.keys(set.nodes.topics?.children ?? {});
const topicsConfig = maybeCast<{ topics: Record<string, unknown> }>(config)?.topics;
const topicsSettings = _.merge(
{},
...topics.map((topic) => ({
[topic]: extensionSettings[panelType]?.[topic]?.settings(topicsConfig?.[topic]),
})),
);

return { ...set, nodes: _.merge({}, set.nodes, { topics: { children: topicsSettings } }) };
}
}
return undefined;
});

const resetToDefaults = useCallback(() => {
if (selectedPanelId) {
Expand Down
11 changes: 11 additions & 0 deletions packages/studio-base/src/context/ExtensionCatalogContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// 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";

import { useGuaranteedContext } from "@foxglove/hooks";
import {
ExtensionPanelRegistration,
Immutable,
PanelSettings,
RegisterMessageConverterArgs,
} from "@foxglove/studio";
import { TopicAliasFunctions } from "@foxglove/studio-base/players/TopicAliasingPlayer/TopicAliasingPlayer";
Expand Down Expand Up @@ -43,3 +45,12 @@ export function useExtensionCatalog<T>(selector: (registry: ExtensionCatalog) =>
const context = useGuaranteedContext(ExtensionCatalogContext);
return useStore(context, selector);
}

export function getExtensionPanelSettings(
reg: ExtensionCatalog,
): Record<string, Record<string, PanelSettings<unknown>>> {
return _.merge(
{},
...(reg.installedMessageConverters ?? []).map((converter) => converter.panelSettings),
);
}
9 changes: 9 additions & 0 deletions packages/studio/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ export type MessageEvent<T = unknown> = {
* un-converted message event.
*/
originalMessageEvent?: MessageEvent;

topicConfig?: unknown;
};

export interface LayoutActions {
Expand Down Expand Up @@ -411,10 +413,17 @@ export type ExtensionPanelRegistration = {
initPanel: (context: PanelExtensionContext) => void | (() => void);
};

export interface PanelSettings<ExtensionSettings> {
settings: (config?: ExtensionSettings) => SettingsTreeNode;
handler: (action: SettingsTreeAction, config?: ExtensionSettings) => void;
defaultConfig?: ExtensionSettings;
}

export type RegisterMessageConverterArgs<Src> = {
fromSchemaName: string;
toSchemaName: string;
converter: (msg: Src, event: Immutable<MessageEvent<Src>>) => unknown;
panelSettings?: Record<string, Record<string, PanelSettings<unknown>>>;
};

type BaseTopic = { name: string; schemaName?: string };
Expand Down

0 comments on commit ed5c4f5

Please sign in to comment.