diff --git a/packages/studio-base/src/panels/EStop/EStop.tsx b/packages/studio-base/src/panels/EStop/EStop.tsx index 336bd1d933..82e08cb24b 100644 --- a/packages/studio-base/src/panels/EStop/EStop.tsx +++ b/packages/studio-base/src/panels/EStop/EStop.tsx @@ -240,7 +240,7 @@ function EStopContent( [setConfig], ); - const settingsTree = useSettingsTree(config); + const settingsTree = useSettingsTree(config, state.pathParseError); useEffect(() => { context.updatePanelSettingsEditor({ actionHandler: settingsActionHandler, @@ -281,12 +281,12 @@ function EStopContent( try { setReqState({ status: "requesting", value: `Calling ${serviceName}...` }); + setEStopAction(undefined); const response = await context.callService(serviceName, {}); setReqState({ status: "success", value: JSON.stringify(response, (_key, value) => (typeof value === "bigint" ? value.toString() : value), 2) ?? "", }); - setEStopAction(undefined); } catch (err) { setReqState({ status: "error", value: (err as Error).message }); log.error(err); diff --git a/packages/studio-base/src/panels/EStop/settings.ts b/packages/studio-base/src/panels/EStop/settings.ts index b87b0c189a..d1982a3c63 100644 --- a/packages/studio-base/src/panels/EStop/settings.ts +++ b/packages/studio-base/src/panels/EStop/settings.ts @@ -33,7 +33,12 @@ export function settingsActionReducer(prevConfig: Config, action: SettingsTreeAc }); } -export function useSettingsTree(config: Config): SettingsTreeNodes { +const supportedDataTypes = ["bool"]; + +export function useSettingsTree( + config: Config, + pathParseError: string | undefined, +): SettingsTreeNodes { const settings = useMemo( (): SettingsTreeNodes => ({ general: { @@ -52,9 +57,10 @@ export function useSettingsTree(config: Config): SettingsTreeNodes { }, statusTopicName: { label: "EStop status topic", - input: "string", - error: serviceError(config.statusTopicName), + input: "messagepath", value: config.statusTopicName, + error: pathParseError, + validTypes: supportedDataTypes, }, }, }, diff --git a/packages/studio-base/src/panels/ToggleSrvButton/ToggleSrvButton.tsx b/packages/studio-base/src/panels/ToggleSrvButton/ToggleSrvButton.tsx index dd11c42e0c..e24ce904cf 100644 --- a/packages/studio-base/src/panels/ToggleSrvButton/ToggleSrvButton.tsx +++ b/packages/studio-base/src/panels/ToggleSrvButton/ToggleSrvButton.tsx @@ -45,8 +45,8 @@ type Action = | { type: "path"; path: string } | { type: "seek" }; -const useStyles = makeStyles<{ state?: boolean }>()((theme, { state }) => { - const buttonColor = state ? "#090" : "#900"; +const useStyles = makeStyles<{ action?: boolean; config: Config }>()((theme, { action, config }) => { + const buttonColor = action === true ? config.activationColor : config.deactivationColor; const augmentedButtonColor = theme.palette.augmentColor({ color: { main: buttonColor }, }); @@ -140,7 +140,6 @@ function reducer(state: State, action: Action): State { } } -// Wrapper component with ThemeProvider so useStyles in the panel receives the right theme. export function ToggleSrvButton({ context }: Props): JSX.Element { const [colorScheme, setColorScheme] = useState("light"); @@ -155,24 +154,20 @@ function ToggleSrvButtonContent( props: Props & { setColorScheme: Dispatch> }, ): JSX.Element { const { context, setColorScheme } = props; - - // panel extensions must notify when they've completed rendering - // onRender will setRenderDone to a done callback which we can invoke after we've rendered const [renderDone, setRenderDone] = useState<() => void>(() => () => { }); const [srvState, setSrvState] = useState(); - const [config, setConfig] = useState(() => ({ + const [config, setConfig] = useState(() => ({ ...defaultConfig, ...(context.initialState as Partial), })); - const [buttonState, setButtonState] = useState(); - - const { classes } = useStyles({ state: buttonState }); + const [buttonAction, setButtonAction] = useState(undefined); + const { classes } = useStyles({ action: buttonAction, config }); const [state, dispatch] = useReducer( reducer, - { ...config, path: config.stateFieldName }, + { ...config, path: config.statusTopicName }, ({ path }): State => ({ - path: path ?? "", + path, parsedPath: parseMessagePath(path), latestMessage: undefined, latestMatchingQueriedData: undefined, @@ -182,14 +177,13 @@ function ToggleSrvButtonContent( ); useLayoutEffect(() => { - dispatch({ type: "path", path: config.stateFieldName }); - }, [config.stateFieldName]); + dispatch({ type: "path", path: config.statusTopicName }); + }, [config.statusTopicName]); useEffect(() => { context.saveState(config); context.setDefaultPanelTitle( - config.serviceName ? `Unspecified` : undefined, - ); + config.statusTopicName === "" ? undefined : config.statusTopicName); }, [config, context]); useEffect(() => { @@ -202,10 +196,20 @@ function ToggleSrvButtonContent( useEffect(() => { context.watch("colorScheme"); - context.onRender = (renderSrvState, done) => { + context.onRender = (renderState, done) => { setRenderDone(() => done); - setColorScheme(renderSrvState.colorScheme ?? "light"); + setColorScheme(renderState.colorScheme ?? "light"); + + if (renderState.didSeek === true) { + dispatch({ type: "seek" }); + } + + if (renderState.currentFrame) { + dispatch({ type: "frame", messages: renderState.currentFrame }); + } }; + context.watch("currentFrame"); + context.watch("didSeek"); return () => { context.onRender = undefined; @@ -228,7 +232,7 @@ function ToggleSrvButtonContent( [setConfig], ); - const settingsTree = useSettingsTree(config); + const settingsTree = useSettingsTree(config, state.pathParseError); useEffect(() => { context.updatePanelSettingsEditor({ actionHandler: settingsActionHandler, @@ -249,8 +253,8 @@ function ToggleSrvButtonContent( const canToggleSrvButton = Boolean( context.callService != undefined && config.serviceName && - config.stateFieldName && - buttonState != undefined && + config.statusTopicName && + buttonAction != undefined && srvState?.status !== "requesting", ); @@ -262,24 +266,24 @@ function ToggleSrvButtonContent( try { setSrvState({ status: "requesting", value: `Calling ${config.serviceName}...` }); - const requestPayload = { data: !buttonState }; - const response = await context.callService(config.serviceName!, requestPayload) as { success?: boolean }; + const requestPayload = { data: buttonAction ?? true }; + setButtonAction(undefined); + const response = await context.callService(config.serviceName, requestPayload) as { success?: boolean }; setSrvState({ status: "success", value: JSON.stringify(response, (_key, value) => (typeof value === "bigint" ? value.toString() : value), 2) ?? "", }); - setButtonState(undefined); } catch (err) { setSrvState({ status: "error", value: (err as Error).message }); log.error(err); } - }, [context, buttonState, config.serviceName]); + }, [context, buttonAction, config.serviceName]); - // Setting buttonState based on state.latestMatchingQueriedData + // Setting buttonAction based on state.latestMatchingQueriedData useEffect(() => { - if (state.latestMatchingQueriedData != undefined) { - const data = state.latestMatchingQueriedData as boolean; - setButtonState(data); + const data = state.latestMatchingQueriedData; + if (typeof data === "boolean") { + setButtonAction(!data); } }, [state.latestMatchingQueriedData]); @@ -319,7 +323,7 @@ function ToggleSrvButtonContent( borderRadius: "0.3rem", }} > - {buttonState ? config.buttonActive : config.buttonDisable} + {buttonAction === true ? config.activationText : buttonAction === false ? config.deactivationText : "Unknown"} diff --git a/packages/studio-base/src/panels/ToggleSrvButton/index.stories.tsx b/packages/studio-base/src/panels/ToggleSrvButton/index.stories.tsx index d393990947..d7c34ee6fb 100644 --- a/packages/studio-base/src/panels/ToggleSrvButton/index.stories.tsx +++ b/packages/studio-base/src/panels/ToggleSrvButton/index.stories.tsx @@ -16,7 +16,6 @@ import { Config } from "./types"; const successResponseJson = JSON.stringify({ success: true }, undefined, 2); const baseConfig: Config = { serviceName: "/set_bool", - requestPayload: `{\n "data": true\n}`, }; const getFixture = ({ allowToggleSrvButton }: { allowToggleSrvButton: boolean }): Fixture => { @@ -104,9 +103,8 @@ export const ToggleSrvButtonEnabledWithCustomButtonSettings: StoryObj = { overrideConfig={{ ...baseConfig, buttonColor: "#ffbf49", - buttonTooltip: "I am a button tooltip", - buttonActive: "Activate that funky service", - buttonDisable: "Disable that funky service", + activationText: "Activate that funky service", + deactivationText: "Disable that funky service", }} /> ); @@ -122,19 +120,10 @@ export const ToggleSrvButtonEnabledWithCustomButtonSettings: StoryObj = { parameters: { panelSetup: { fixture: getFixture({ allowToggleSrvButton: true }) } }, }; -const validJSON = `{\n "a": 1,\n "b": 2,\n "c": 3\n}`; export const WithValidJSON: StoryObj = { render: () => { - return ; - }, -}; - -const invalidJSON = `{\n "a": 1,\n 'b: 2,\n "c": 3\n}`; - -export const WithInvalidJSON: StoryObj = { - render: () => { - return ; + return ; }, }; diff --git a/packages/studio-base/src/panels/ToggleSrvButton/settings.ts b/packages/studio-base/src/panels/ToggleSrvButton/settings.ts index f2715b1525..f8a56c89f2 100644 --- a/packages/studio-base/src/panels/ToggleSrvButton/settings.ts +++ b/packages/studio-base/src/panels/ToggleSrvButton/settings.ts @@ -13,9 +13,11 @@ import { Config } from "./types"; export const defaultConfig: Config = { serviceName: "", - stateFieldName: "", - buttonActive: "Activate", - buttonDisable: "Deactivate", + statusTopicName: "", + activationText: "Activate", + activationColor: "#090", + deactivationText: "Deactivate", + deactivationColor: "#900", }; function serviceError(serviceName?: string) { @@ -34,7 +36,12 @@ export function settingsActionReducer(prevConfig: Config, action: SettingsTreeAc }); } -export function useSettingsTree(config: Config): SettingsTreeNodes { +const supportedDataTypes = ["bool"]; + +export function useSettingsTree( + config: Config, + pathParseError: string | undefined, +): SettingsTreeNodes { const settings = useMemo( (): SettingsTreeNodes => ({ general: { @@ -43,31 +50,42 @@ export function useSettingsTree(config: Config): SettingsTreeNodes { label: "Service name", input: "string", error: serviceError(config.serviceName), - value: config.serviceName ?? "", + value: config.serviceName, }, - stateFieldName: { - label: "Topic State Field Name", - input: "string", - value: config.stateFieldName, + statusTopicName: { + label: "Current State Data", + input: "messagepath", + value: config.statusTopicName, + error: pathParseError, + validTypes: supportedDataTypes, }, }, }, button: { label: "Button", fields: { - buttonActive: { + activationText: { label: "Activation Message", input: "string", - value: config.buttonActive, + value: config.activationText, placeholder: "Activate", }, - buttonDisable: { + activationColor: { + label: "Activation Color", + input: "rgb", + value: config.activationColor, + }, + deactivationText: { label: "Deactivation Message", input: "string", - value: config.buttonDisable, + value: config.deactivationText, placeholder: "Deactivate", }, - buttonTooltip: { label: "Tooltip", input: "string", value: config.buttonTooltip }, + deactivationColor: { + label: "Deactivation Color", + input: "rgb", + value: config.deactivationColor, + }, }, }, }), diff --git a/packages/studio-base/src/panels/ToggleSrvButton/types.ts b/packages/studio-base/src/panels/ToggleSrvButton/types.ts index 37d3b7024c..6a661e9b42 100644 --- a/packages/studio-base/src/panels/ToggleSrvButton/types.ts +++ b/packages/studio-base/src/panels/ToggleSrvButton/types.ts @@ -4,8 +4,9 @@ export type Config = { serviceName: string; - stateFieldName: string; - buttonActive: string; - buttonDisable: string; - buttonTooltip?: string; + statusTopicName: string; + activationText: string; + activationColor: string; + deactivationText: string; + deactivationColor: string; };