diff --git a/src/index.ts b/src/index.ts index a8526f1a..824bc1d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ export * from "./redux"; export * from "./misc"; export * from "./connection"; -export * from "./ui/components"; export * from "./ui/widgets"; export * from "./ui/hooks"; export * from "./types"; diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts deleted file mode 100644 index 46719ea0..00000000 --- a/src/ui/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { InputComponent } from "./input/input"; diff --git a/src/ui/components/input/input.tsx b/src/ui/components/input/input.tsx deleted file mode 100644 index abbeee07..00000000 --- a/src/ui/components/input/input.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { CSSProperties, useState } from "react"; - -export const InputComponent = (props: { - value: string; - onEnter: (value: string) => void; - readonly?: boolean; - style?: CSSProperties; - className?: string; -}): JSX.Element => { - const [inputValue, setInputValue] = useState(""); - const [editing, setEditing] = useState(false); - function onKeyDown(event: React.KeyboardEvent): void { - if (event.key === "Enter") { - props.onEnter(event.currentTarget.value); - setInputValue(""); - setEditing(false); - event.currentTarget.blur(); - } - } - function onChange(event: React.ChangeEvent): void { - setInputValue(event.currentTarget.value); - } - function onClick(event: React.MouseEvent): void { - /* When focus gained allow editing. */ - if (!props.readonly && !editing) { - setInputValue(""); - setEditing(true); - } - } - function onBlur(event: React.ChangeEvent): void { - setEditing(false); - } - - if (!editing && inputValue !== props.value) { - setInputValue(props.value); - } - - return ( - - ); -}; diff --git a/src/ui/widgets/EmbeddedDisplay/bobParser.ts b/src/ui/widgets/EmbeddedDisplay/bobParser.ts index 4526beb0..83e6c9dc 100644 --- a/src/ui/widgets/EmbeddedDisplay/bobParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/bobParser.ts @@ -380,7 +380,8 @@ export function parseBob( rotation: ["rotation", bobParseNumber], styleOpt: ["style", bobParseNumber], lineColor: ["line_color", opiParseColor], - rotationStep: ["rotation_step", bobParseNumber] + rotationStep: ["rotation_step", bobParseNumber], + multiLine: ["multi_line", opiParseBoolean] }; const complexParsers = { diff --git a/src/ui/widgets/Input/__snapshots__/input.test.tsx.snap b/src/ui/widgets/Input/__snapshots__/input.test.tsx.snap new file mode 100644 index 00000000..8c4ef680 --- /dev/null +++ b/src/ui/widgets/Input/__snapshots__/input.test.tsx.snap @@ -0,0 +1,35 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > renders an input 1`] = ` + +
+
+ + +
+
+
+`; diff --git a/src/ui/widgets/Input/input.module.css b/src/ui/widgets/Input/input.module.css deleted file mode 100644 index d103adf9..00000000 --- a/src/ui/widgets/Input/input.module.css +++ /dev/null @@ -1,14 +0,0 @@ -.Input { - height: 100%; - width: 100%; - min-width: 0; - display: flex; - text-align: center; - color: blue; - box-sizing: border-box; - border-width: 1px; -} - -.Input.readonly { - cursor: not-allowed; -} diff --git a/src/ui/widgets/Input/input.test.tsx b/src/ui/widgets/Input/input.test.tsx index cd50d4bf..6e6dbeaa 100644 --- a/src/ui/widgets/Input/input.test.tsx +++ b/src/ui/widgets/Input/input.test.tsx @@ -5,7 +5,6 @@ import { DAlarm } from "../../../types/dtypes"; import { dstring } from "../../../testResources"; let input: JSX.Element; - beforeEach((): void => { input = ( { }); describe("", (): void => { it("renders an input", (): void => { - const { getByDisplayValue } = render(input); - const renderedInput = getByDisplayValue("hello"); - expect(renderedInput).toBeInTheDocument(); - expect(renderedInput).toHaveStyle("color: var(--warning)"); - expect(renderedInput.className).toContain("readonly"); + const { asFragment } = render(input); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/src/ui/widgets/Input/input.tsx b/src/ui/widgets/Input/input.tsx index d11d2b90..8cdc0a2c 100644 --- a/src/ui/widgets/Input/input.tsx +++ b/src/ui/widgets/Input/input.tsx @@ -1,8 +1,7 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; -import classes from "./input.module.css"; import { writePv } from "../../hooks/useSubscription"; -import { commonCss, Widget } from "../widget"; +import { Widget } from "../widget"; import { PVInputComponent, PVWidgetPropType } from "../widgetProps"; import { registerWidget } from "../register"; import { @@ -10,93 +9,159 @@ import { FontPropOpt, ChoicePropOpt, ColorPropOpt, - BoolPropOpt + BoolPropOpt, + BorderPropOpt, + StringPropOpt } from "../propTypes"; -import { Font } from "../../../types/font"; -import { Color } from "../../../types/color"; import { AlarmQuality, DType } from "../../../types/dtypes"; -import { InputComponent } from "../../components/input/input"; +import { TextField as MuiTextField, styled } from "@mui/material"; +import { diamondTheme } from "../../../diamondTheme"; -export interface InputProps { - pvName: string; - value: string; - readonly: boolean; - foregroundColor?: Color; - backgroundColor?: Color; - transparent: boolean; - alarm: AlarmQuality; - alarmSensitive: boolean; - onKeyDown: (event: React.KeyboardEvent) => void; - onChange: (event: React.ChangeEvent) => void; - onBlur: (event: React.ChangeEvent) => void; - onClick: (event: React.MouseEvent) => void; - font?: Font; - textAlign?: "left" | "center" | "right"; -} +const InputComponentProps = { + pvName: StringPropOpt, + font: FontPropOpt, + foregroundColor: ColorPropOpt, + backgroundColor: ColorPropOpt, + transparent: BoolPropOpt, + alarmSensitive: BoolPropOpt, + enabled: BoolPropOpt, + textAlign: ChoicePropOpt(["left", "center", "right"]), + textAlignV: ChoicePropOpt(["top", "center", "bottom"]), + border: BorderPropOpt, + multiLine: BoolPropOpt +}; -export const SmartInputComponent = ( - props: PVInputComponent & { - font?: Font; - foregroundColor?: Color; - backgroundColor?: Color; - transparent?: boolean; - alarmSensitive?: boolean; - textAlign?: "left" | "center" | "right"; +const TextField = styled(MuiTextField)({ + "&.MuiFormControl-root": { + height: "100%", + width: "100%", + display: "block" + }, + "& .MuiInputBase-root": { + height: "100%", + width: "100%" + }, + "& .MuiOutlinedInput-root": { + "&:hover fieldset": { + borderWidth: "1px", + borderColor: "#1976D2" + }, + "&.Mui-focused fieldset": { + borderWidth: "2px", + borderColor: "#1976D2" + }, + "&.Mui-disabled": { + cursor: "not-allowed", + pointerEvents: "all !important" + } } +}); + +export const SmartInputComponent = ( + props: PVInputComponent & InferWidgetProps ): JSX.Element => { + const { + enabled = true, + transparent = false, + textAlign = "left", + textAlignV = "center", + value = null, + multiLine = false + } = props; + + const font = props.font?.css() ?? diamondTheme.typography; + const alarmQuality = props.value?.getAlarm().quality ?? AlarmQuality.VALID; - let allClasses = classes.Input; - const style = commonCss(props); - if (props.textAlign) { - style.textAlign = props.textAlign; - } - style.color = props.foregroundColor?.toString(); - style.backgroundColor = props.backgroundColor?.toString(); - // Transparent prop overrides backgroundColor. - if (props.transparent) { - style["backgroundColor"] = "transparent"; - } - if (props.readonly) { - allClasses += ` ${classes.readonly}`; + const foregroundColor = props.alarmSensitive + ? function () { + switch (alarmQuality) { + case AlarmQuality.UNDEFINED: + case AlarmQuality.INVALID: + case AlarmQuality.CHANGING: + return "var(--invalid)"; + case AlarmQuality.WARNING: + return "var(--alarm)"; + case AlarmQuality.ALARM: + return "var(--alarm)"; + case AlarmQuality.VALID: + return ( + props.foregroundColor?.toString() ?? + diamondTheme.palette.primary.contrastText + ); + } + } + : (props.foregroundColor?.toString() ?? + diamondTheme.palette.primary.contrastText); + + let alignmentV = "center"; + if (textAlignV === "top") { + alignmentV = "start"; + } else if (textAlignV === "bottom") { + alignmentV = "end"; } - if (props.alarmSensitive) { - switch (alarmQuality) { - case AlarmQuality.UNDEFINED: - case AlarmQuality.INVALID: - case AlarmQuality.CHANGING: - style.color = "var(--invalid)"; - break; - case AlarmQuality.WARNING: - style.color = "var(--alarm)"; - break; - case AlarmQuality.ALARM: - style.color = "var(--alarm)"; - break; + + const backgroundColor = transparent + ? "transparent" + : (props.backgroundColor?.toString() ?? "#80FFFF"); + + const [inputValue, setInputValue] = useState(value?.getStringValue() ?? ""); + + useEffect(() => { + if (value) { + setInputValue(value.getStringValue() ?? ""); } - } - function onEnter(value: string): void { - writePv(props.pvName, new DType({ stringValue: value })); - } + }, [value]); + + const onKeyPress = (event: React.KeyboardEvent) => { + if (multiLine) { + if (event.key === "Enter" && event.ctrlKey) { + writePv(props.pvName, new DType({ stringValue: inputValue })); + event.currentTarget.blur(); + } + } else { + if (event.key === "Enter") { + writePv(props.pvName, new DType({ stringValue: inputValue })); + event.currentTarget.blur(); + } + } + }; return ( - setInputValue(event.target.value)} + sx={{ + "& .MuiInputBase-input": { + textAlign: textAlign, + padding: "4px", + fontFamily: font + }, + "& .MuiInputBase-root": { + alignItems: alignmentV, + color: foregroundColor, + backgroundColor: backgroundColor + }, + "& fieldset": { + borderWidth: props.border?.width ?? "0px", + borderColor: props.border?.color.toString() ?? "#0000003B" + } + }} /> ); }; const InputWidgetProps = { - ...PVWidgetPropType, - font: FontPropOpt, - foregroundColor: ColorPropOpt, - backgroundColor: ColorPropOpt, - transparent: BoolPropOpt, - alarmSensitive: BoolPropOpt, - textAlign: ChoicePropOpt(["left", "center", "right"]) + ...InputComponentProps, + ...PVWidgetPropType }; export const Input = ( diff --git a/src/ui/widgets/SlideControl/slideControl.test.tsx b/src/ui/widgets/SlideControl/slideControl.test.tsx index e6cc4625..2482b2a2 100644 --- a/src/ui/widgets/SlideControl/slideControl.test.tsx +++ b/src/ui/widgets/SlideControl/slideControl.test.tsx @@ -9,7 +9,7 @@ test("slideControl", () => { value={ddouble(5)} connected={true} readonly={false} - pvName="dummy" + pvName="pv" max={10} min={0} >