Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to v1.1.0 #5

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@
},
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
}
}
4 changes: 2 additions & 2 deletions packages/studio-base/src/i18n/en/panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const panels = {
teleopDescription: "Teleoperate a robot over a live connection.",
topicGraph: "Topic Graph",
topicGraphDescription: "Display a graph of active nodes, topics, and services.",
serviceButton: "Custom: Service Button",
serviceButtonDescription: "Button to call std_srvs/Trigger.",
toggleSrvButton: "Custom: Toggle Service Button",
toggleSrvButtonDescription: "Button to call services.",
userScripts: "User Scripts",
userScriptsDescription:
"Write custom data transformations in TypeScript. Previously known as Node Playground.",
Expand Down
3 changes: 2 additions & 1 deletion packages/studio-base/src/panels/Battery/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ body {

.battery__text {
margin-bottom: 0.3rem;
font-size: var(--normal-font-size);
}

.battery__percentage {
Expand All @@ -91,7 +92,7 @@ body {
}

.battery__status i {
font-size: 1.25rem;
font-size: var(--normal-font-size);
}

.battery__pill {
Expand Down
180 changes: 75 additions & 105 deletions packages/studio-base/src/panels/EStop/EStop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import * as _ from "lodash-es";
import { Dispatch, SetStateAction, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useState } from "react";
import { makeStyles } from "tss-react/mui";

import Log from "@foxglove/log";
import { parseMessagePath, MessagePath } from "@foxglove/message-path";
import { MessageEvent, PanelExtensionContext, SettingsTreeAction } from "@foxglove/studio";
import { simpleGetMessagePathDataItems } from "@foxglove/studio-base/components/MessagePathSyntax/simpleGetMessagePathDataItems";
import NotificationModal from "@foxglove/studio-base/components/NotificationModal";
import Stack from "@foxglove/studio-base/components/Stack";
import { Config } from "@foxglove/studio-base/panels/EStop/types";
import ThemeProvider from "@foxglove/studio-base/theme/ThemeProvider";
Expand All @@ -19,18 +19,16 @@ import { defaultConfig, settingsActionReducer, useSettingsTree } from "./setting

import "./styles.css";


const log = Log.getLogger(__dirname);

type Props = {
context: PanelExtensionContext;
};

type EStopState = "go" | "stop" | undefined;
type SrvResponse = { success: boolean; message: string };

type ReqState = {
type SrvState = {
status: "requesting" | "error" | "success";
value: string;
response: SrvResponse | undefined;
};

type State = {
Expand All @@ -47,8 +45,8 @@ type Action =
| { type: "path"; path: string }
| { type: "seek" };

const useStyles = makeStyles<{ state?: string }>()((theme, { state }) => {
const buttonColor = state === "go" ? "#090" : "#900";
const useStyles = makeStyles<{ state: EStopState }>()((theme, { state }) => {
const buttonColor = state === "go" ? "#090" : state === "stop" ? "#900" : "#666";
const augmentedButtonColor = theme.palette.augmentColor({
color: { main: buttonColor },
});
Expand All @@ -65,26 +63,6 @@ const useStyles = makeStyles<{ state?: string }>()((theme, { state }) => {
};
});

function parseInput(value: string): { error?: string; parsedObject?: unknown } {
let parsedObject;
let error = undefined;
try {
const parsedAny: unknown = JSON.parse(value);
if (Array.isArray(parsedAny)) {
error = "Request content must be an object, not an array";
} else if (parsedAny == undefined) {
error = "Request content must be an object, not null";
} else if (typeof parsedAny !== "object") {
error = `Request content must be an object, not ‘${typeof parsedAny}’`;
} else {
parsedObject = parsedAny;
}
} catch (e) {
error = value.length !== 0 ? e.message : "Enter valid request content as JSON";
}
return { error, parsedObject };
}

function getSingleDataItem(results: unknown[]) {
if (results.length <= 1) {
return results[0];
Expand Down Expand Up @@ -181,7 +159,7 @@ function EStopContent(
// 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 [reqState, setReqState] = useState<ReqState | undefined>();
const [srvState, setSrvState] = useState<SrvState | undefined>();
const [eStopAction, setEStopAction] = useState<EStopState>();
const [config, setConfig] = useState<Config>(() => ({
...defaultConfig,
Expand All @@ -206,33 +184,28 @@ function EStopContent(
dispatch({ type: "path", path: config.statusTopicName });
}, [config.statusTopicName]);

useEffect(() => {
context.saveState(config);
context.setDefaultPanelTitle(
config.goServiceName ? `Unspecified` : undefined,
);
}, [config, context]);
const handleRequestCloseNotification = () => {
setSrvState(undefined);
};

useEffect(() => {
context.saveState(config);
context.setDefaultPanelTitle(
config.stopServiceName ? `Unspecified` : undefined,
);
context.setDefaultPanelTitle(`E-Stop`);
}, [config, context]);

useEffect(() => {
context.watch("colorScheme");

context.onRender = (renderReqState, done) => {
context.onRender = (renderSrvState, done) => {
setRenderDone(() => done);
setColorScheme(renderReqState.colorScheme ?? "light");
setColorScheme(renderSrvState.colorScheme ?? "light");

if (renderReqState.didSeek === true) {
if (renderSrvState.didSeek === true) {
dispatch({ type: "seek" });
}

if (renderReqState.currentFrame) {
dispatch({ type: "frame", messages: renderReqState.currentFrame });
if (renderSrvState.currentFrame) {
dispatch({ type: "frame", messages: renderSrvState.currentFrame });
}
};

Expand All @@ -253,19 +226,14 @@ function EStopContent(
};
}, [context, state.parsedPath?.topicName]);

const { error: requestParseError, parsedObject } = useMemo(
() => parseInput(config.requestPayload ?? ""),
[config.requestPayload],
);

const settingsActionHandler = useCallback(
(action: SettingsTreeAction) => {
setConfig((prevConfig) => settingsActionReducer(prevConfig, action));
},
[setConfig],
);

const settingsTree = useSettingsTree(config);
const settingsTree = useSettingsTree(config, state.pathParseError);
useEffect(() => {
context.updatePanelSettingsEditor({
actionHandler: settingsActionHandler,
Expand All @@ -285,43 +253,32 @@ function EStopContent(

const canEStop = Boolean(
context.callService != undefined &&
config.requestPayload &&
config.goServiceName &&
config.stopServiceName &&
eStopAction != undefined &&
parsedObject != undefined &&
requestParseError == undefined &&
reqState?.status !== "requesting",
srvState?.status !== "requesting",
);

const eStopClicked = useCallback(async () => {
if (!context.callService) {
setReqState({ status: "error", value: "The data source does not allow calling services" });
setSrvState({ status: "error", response: undefined });
return;
}

const serviceName = eStopAction === "go" ? config.goServiceName : config.stopServiceName;

if (!serviceName) {
setReqState({ status: "error", value: "Service name is not configured" });
setSrvState({ status: "error", response: undefined });
Comment on lines +264 to +271

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these errors be propagated to the user?

return;
}

try {
setReqState({ status: "requesting", value: `Calling ${serviceName}...` });
const response = await context.callService(serviceName, JSON.parse(config.requestPayload!));
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);
}
}, [context, eStopAction, config.goServiceName, config.stopServiceName, config.requestPayload]);
setSrvState({ status: "requesting", response: undefined });
const response = await context.callService(serviceName, {}) as SrvResponse;
setSrvState({ status: "success", response });

}, [context, eStopAction, config.goServiceName, config.stopServiceName]);

// Setting eStopAction based on state.latestMatchingQueriedData
// Setting eStopAction based on received state
useEffect(() => {
if (state.latestMatchingQueriedData != undefined) {
const data = state.latestMatchingQueriedData as boolean;
Expand All @@ -335,42 +292,55 @@ function EStopContent(
}, [renderDone]);

return (
<Stack flex="auto" gap={1} padding={1.5} position="relative" fullHeight>
<Stack justifyContent="center" alignItems="center" fullWidth fullHeight>
<div className="center">
<Stack
direction="column-reverse"
justifyContent="center"
alignItems="center"
overflow="hidden"
flexGrow={0}
gap={1.5}
>
{statusMessage && (
<Typography variant="caption" noWrap>
{statusMessage}
</Typography>
)}
<span>
<Button
className={classes.button}
variant="contained"
disabled={!canEStop}
onClick={eStopClicked}
data-testid="call-service-button"
style={{
minWidth: "150px",
minHeight: "70px",
fontSize: "1.7rem",
borderRadius: "0.3rem",
}}
>
{eStopAction?.toUpperCase() ?? "Waiting..."}
</Button>
</span>
</Stack>
</div>
<>
<Stack flex="auto" gap={1} padding={1.5} position="relative" fullHeight>
<Stack justifyContent="center" alignItems="center" fullWidth fullHeight>
<div className="center">
<Stack
direction="column-reverse"
justifyContent="center"
alignItems="center"
overflow="hidden"
flexGrow={0}
gap={1.5}
>
{statusMessage && (
<Typography variant="caption" noWrap>
{statusMessage}
</Typography>
)}
<span>
<Button
className={classes.button}
variant="contained"
disabled={!canEStop}
onClick={eStopClicked}
data-testid="call-service-button"
style={{
minWidth: "150px",
minHeight: "150px",
fontSize: "2.2rem",
borderRadius: "50%",
}}
>
{eStopAction?.toUpperCase() ?? "Waiting..."}
</Button>
</span>
</Stack>
</div>
</Stack>
</Stack>
</Stack>
{srvState?.response?.success === false && (
<NotificationModal
onRequestClose={handleRequestCloseNotification}
notification={{
id: "1",
message: "Request Failed",
details: srvState.response.message,
severity: "error",
}}
/>
)}
</>
);
}
Loading
Loading