Skip to content
8 changes: 8 additions & 0 deletions src/redux/csState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export interface PvState {
readonly: boolean;
}

export type PvDatum = PvState & {
effectivePvName: string;
};

export type PvDataCollection = {
pvData: PvDatum[];
};

export interface FullPvState extends PvState {
initializingPvName: string;
}
Expand Down
1 change: 1 addition & 0 deletions src/types/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type GenericProp =
| boolean
| number
| PV
| { pvName: PV }[]
| Color
| Font
| Border
Expand Down
33 changes: 32 additions & 1 deletion src/ui/hooks/useConnection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { useSubscription } from "./useSubscription";
import { useSelector } from "react-redux";
import { CsState } from "../../redux/csState";
import { CsState, PvDataCollection } from "../../redux/csState";
import { pvStateSelector, PvArrayResults, pvStateComparator } from "./utils";
import { SubscriptionType } from "../../connection/plugin";
import { DType } from "../../types/dtypes";
Expand All @@ -27,6 +27,7 @@ export function useConnection(
let readonly = false;
let value = undefined;
let effectivePvName = "undefined";

if (pvName !== undefined) {
const [pvState, effPvName] = pvResults[pvName];
effectivePvName = effPvName;
Expand All @@ -36,5 +37,35 @@ export function useConnection(
value = pvState.value;
}
}

return [effectivePvName, connected, readonly, value];
}

export const useConnectionMultiplePv = (
id: string,
pvNames: string[],
type?: SubscriptionType
): PvDataCollection => {
const pvNameArray = pvNames.filter(x => !!x);
const typeArray = !type ? [] : [type];

useSubscription(id, pvNameArray, typeArray);

const pvResults = useSelector(
(state: CsState): PvArrayResults => pvStateSelector(pvNameArray, state),
pvStateComparator
);

return {
pvData: pvNameArray.map(pvName => {
const [pvState, effPvName] = pvResults[pvName];

return {
effectivePvName: effPvName,
connected: pvState?.connected ?? false,
readonly: pvState?.readonly ?? false,
value: pvState?.value
};
})
};
};
12 changes: 10 additions & 2 deletions src/ui/widgets/BoolButton/boolButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DType } from "../../../types/dtypes";
import { Color } from "../../../types";
import { ThemeProvider } from "@mui/material";
import { phoebusTheme } from "../../../phoebusTheme";
import { PvDatum } from "../../../redux/csState";

const BoolButtonRenderer = (boolButtonProps: any): JSX.Element => {
return (
Expand All @@ -14,8 +15,15 @@ const BoolButtonRenderer = (boolButtonProps: any): JSX.Element => {
);
};

const TEST_PVDATUM = {
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: new DType({ doubleValue: 1 })
} as Partial<PvDatum> as PvDatum;

const TEST_PROPS = {
value: new DType({ doubleValue: 1 }),
pvData: [TEST_PVDATUM],
width: 45,
height: 20,
onColor: Color.fromRgba(0, 235, 10),
Expand Down Expand Up @@ -116,7 +124,7 @@ describe("<BoolButton />", (): void => {
test("on click change led colour if no text ", async (): Promise<void> => {
const boolButtonProps = {
...TEST_PROPS,
value: new DType({ doubleValue: 0 }),
pvData: [{ ...TEST_PVDATUM, value: new DType({ doubleValue: 0 }) }],
onLabel: "",
offLabel: "",
showLed: true
Expand Down
5 changes: 3 additions & 2 deletions src/ui/widgets/BoolButton/boolButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { writePv } from "../../hooks/useSubscription";
import { DType } from "../../../types/dtypes";
import { WIDGET_DEFAULT_SIZES } from "../EmbeddedDisplay/bobParser";
import { Button as MuiButton, styled, useTheme } from "@mui/material";
import { getPvValueAndName } from "../utils";

// For HTML button, these are the sizes of the buffer on
// width and height. Must take into account when allocating
Expand Down Expand Up @@ -88,8 +89,7 @@ export const BoolButtonComponent = (
height = WIDGET_DEFAULT_SIZES["bool_button"][1],
foregroundColor = theme.palette.primary.contrastText,
backgroundColor = theme.palette.primary.main,
pvName,
value,
pvData,
onState = 1,
offState = 0,
onColor = Color.fromRgba(0, 255, 0),
Expand All @@ -100,6 +100,7 @@ export const BoolButtonComponent = (
labelsFromPv = false,
enabled = true
} = props;
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);

const font = props.font?.css() ?? theme.typography;

Expand Down
19 changes: 17 additions & 2 deletions src/ui/widgets/ByteMonitor/byteMonitor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { Color } from "../../../types";
import renderer, { ReactTestRendererJSON } from "react-test-renderer";
import { DType } from "../../../types/dtypes";
import { PvDatum } from "../../../redux/csState";

const ByteMonitorRenderer = (byteMonitorProps: any): ReactTestRendererJSON => {
return renderer
Expand All @@ -17,7 +18,14 @@ const ByteMonitorRenderer = (byteMonitorProps: any): ReactTestRendererJSON => {
describe("<ByteMonitorComponent />", (): void => {
test("default properties are added to bytemonitor component", (): void => {
const byteMonitorProps = {
value: new DType({ doubleValue: 15 }),
pvData: [
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: new DType({ doubleValue: 15 })
} as Partial<PvDatum> as PvDatum
],
height: 40,
width: 40
};
Expand All @@ -33,7 +41,14 @@ describe("<ByteMonitorComponent />", (): void => {
});
test("overwrite bytemonitor default values", (): void => {
const byteMonitorProps = {
value: new DType({ doubleValue: 2 }),
pvData: [
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: new DType({ doubleValue: 2 })
} as Partial<PvDatum> as PvDatum
],
height: 50,
width: 50,
startBit: 8,
Expand Down
4 changes: 3 additions & 1 deletion src/ui/widgets/ByteMonitor/byteMonitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { registerWidget } from "../register";
import classes from "./byteMonitor.module.css";
import { Color } from "../../../types/color";
import { WIDGET_DEFAULT_SIZES } from "../EmbeddedDisplay/bobParser";
import { getPvValueAndName } from "../utils";

export const ByteMonitorProps = {
width: IntPropOpt,
Expand Down Expand Up @@ -41,7 +42,7 @@ export const ByteMonitorComponent = (
props: ByteMonitorComponentProps
): JSX.Element => {
const {
value,
pvData,
startBit = 0,
horizontal = true,
bitReverse = false,
Expand All @@ -54,6 +55,7 @@ export const ByteMonitorComponent = (
width = WIDGET_DEFAULT_SIZES["byte_monitor"][0],
height = WIDGET_DEFAULT_SIZES["byte_monitor"][1]
} = props;
const { value } = getPvValueAndName(pvData);

// Check for a value, otherwise set to 0
const doubleValue = value?.getDoubleValue() || 0;
Expand Down
4 changes: 3 additions & 1 deletion src/ui/widgets/Checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "@mui/material";
import { writePv } from "../../hooks/useSubscription";
import { DType } from "../../../types";
import { getPvValueAndName } from "../utils";

export const CheckboxProps = {
label: StringPropOpt,
Expand Down Expand Up @@ -65,7 +66,8 @@ export const CheckboxComponent = (
props: CheckboxComponentProps
): JSX.Element => {
const theme = useTheme();
const { enabled = true, label = "Label", value, pvName } = props;
const { enabled = true, label = "Label", pvData } = props;
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);
const checked = Boolean(value?.getDoubleValue());

const handleChange = (event: any): void => {
Expand Down
40 changes: 31 additions & 9 deletions src/ui/widgets/ChoiceButton/choiceButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DDisplay, DType } from "../../../types/dtypes";
import { Color, Font } from "../../../types";
import { ThemeProvider } from "@mui/material";
import { phoebusTheme } from "../../../phoebusTheme";
import { PvDatum } from "../../../redux/csState";

const ChoiceButtonRenderer = (choiceButtonProps: any): JSX.Element => {
return (
Expand All @@ -17,7 +18,14 @@ const ChoiceButtonRenderer = (choiceButtonProps: any): JSX.Element => {
describe("<ChoiceButton />", (): void => {
test("it renders ChoiceButton with default props", (): void => {
const choiceButtonProps = {
value: null
pvData: [
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: undefined
} as Partial<PvDatum> as PvDatum
]
};
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps));
const buttons = getAllByRole("button") as Array<HTMLButtonElement>;
Expand All @@ -37,7 +45,14 @@ describe("<ChoiceButton />", (): void => {

test("pass props to widget", (): void => {
const choiceButtonProps = {
value: new DType({ doubleValue: 0 }),
pvData: [
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: new DType({ doubleValue: 0 })
} as Partial<PvDatum> as PvDatum
],
width: 60,
height: 140,
font: new Font(12),
Expand Down Expand Up @@ -68,12 +83,19 @@ describe("<ChoiceButton />", (): void => {

test("pass props to widget, using itemsFromPv", (): void => {
const choiceButtonProps = {
value: new DType(
{ doubleValue: 0 },
undefined,
undefined,
new DDisplay({ choices: ["hi", "Hello"] })
),
pvData: [
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: new DType(
{ doubleValue: 0 },
undefined,
undefined,
new DDisplay({ choices: ["hi", "Hello"] })
)
} as Partial<PvDatum> as PvDatum
],
items: ["one", "two", "three"],
horizontal: false,
itemsFromPv: true,
Expand All @@ -90,7 +112,7 @@ describe("<ChoiceButton />", (): void => {

test("selecting a button", (): void => {
const choiceButtonProps = {
value: null
pvData: []
};
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps));
const buttons = getAllByRole("button") as Array<HTMLButtonElement>;
Expand Down
6 changes: 4 additions & 2 deletions src/ui/widgets/ChoiceButton/choiceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useTheme
} from "@mui/material";
import { Color } from "../../../types";
import { getPvValueAndName } from "../utils";

const ChoiceButtonProps = {
pvName: StringPropOpt,
Expand Down Expand Up @@ -71,16 +72,17 @@ export const ChoiceButtonComponent = (
const {
width = 100,
height = 43,
value = null,
pvData,
enabled = true,
itemsFromPv = true,
pvName,
items = ["Item 1", "Item 2"],
horizontal = true,
foregroundColor = theme.palette.primary.contrastText,
backgroundColor = theme.palette.primary.main,
selectedColor = Color.fromRgba(200, 200, 200)
} = props;
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);

const font = props.font?.css() ?? theme.typography;
const [selected, setSelected] = useState(value?.getDoubleValue());

Expand Down
2 changes: 1 addition & 1 deletion src/ui/widgets/EmbeddedDisplay/bobParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("bob widget parser", (): void => {
it("parses a readback widget", (): void => {
const widget = parseBob(readbackString, "xxx", PREFIX)
.children?.[0] as WidgetDescription;
expect(widget.pvName).toEqual(PV.parse("xxx://abc"));
expect(widget.pvMetadataList[0].pvName).toEqual(PV.parse("xxx://abc"));
});

const noXString = `
Expand Down
8 changes: 5 additions & 3 deletions src/ui/widgets/EmbeddedDisplay/bobParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ function bobParseTraces(props: any): Trace[] {
const traces: Trace[] = [];
let parsedProps = {};
if (props) {
// If only once trace, we are passed an object instead
// If only one trace, we are passed an object instead
// of an array
if (props.trace.length > 1) {
props.trace.forEach((trace: any) => {
Expand Down Expand Up @@ -496,9 +496,11 @@ export function parseBob(

const simpleParsers: ParserDict = {
...BOB_SIMPLE_PARSERS,
pvName: [
pvMetadataList: [
"pv_name",
(pvName: ElementCompact): PV => opiParsePvName(pvName, defaultProtocol)
(pvName: ElementCompact): { pvName: PV }[] => [
{ pvName: opiParsePvName(pvName, defaultProtocol) }
]
],
actions: [
"actions",
Expand Down
4 changes: 3 additions & 1 deletion src/ui/widgets/EmbeddedDisplay/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ export function genericParser(
// plot widgets as the PV. This is a placeholder until support for
// multiple PVs per widget is implemented
if (newProps.hasOwnProperty("traces")) {
newProps.pvName = PV.parse(newProps.traces[0].yPv);
newProps.pvMetadataList = newProps.traces?.map((trace: any) => ({
pvName: PV.parse(trace.yPv)
}));
}

return newProps;
Expand Down
13 changes: 9 additions & 4 deletions src/ui/widgets/Input/input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import { SmartInputComponent } from "./input";
import { render } from "@testing-library/react";
import { DAlarm } from "../../../types/dtypes";
import { dstring } from "../../../testResources";
import { PvDatum } from "../../../redux/csState";

let input: JSX.Element;
beforeEach((): void => {
input = (
<SmartInputComponent
pvName="pv"
value={dstring("hello", DAlarm.MINOR)}
connected={true}
readonly={true}
pvData={[
{
effectivePvName: "TEST:PV",
connected: true,
readonly: true,
value: dstring("hello", DAlarm.MINOR)
} as Partial<PvDatum> as PvDatum
]}
alarmSensitive={true}
/>
);
Expand Down
Loading
Loading