-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #54 from dls-controls/add-choice-widget
Add choice widget
- Loading branch information
Showing
7 changed files
with
264 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
.ChoiceButton { | ||
align-items: center; | ||
justify-content: center; | ||
text-overflow: ellipsis; | ||
border-radius: 3px; | ||
margin-right: 1px; | ||
margin-bottom: 1px; | ||
border: 1px solid gray; | ||
word-wrap: break-word; | ||
overflow: hidden; | ||
display: inline-block; | ||
} | ||
|
||
.ChoiceButton:hover { | ||
background-image: linear-gradient(rgb(255 255 255 0.4) 0 0); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React from "react"; | ||
import { ChoiceButtonComponent } from "./choiceButton"; | ||
import { render } from "@testing-library/react"; | ||
import { DDisplay, DType } from "../../../types/dtypes"; | ||
import { Color } from "../../../types"; | ||
|
||
const ChoiceButtonRenderer = (choiceButtonProps: any): JSX.Element => { | ||
return <ChoiceButtonComponent {...choiceButtonProps} />; | ||
}; | ||
|
||
describe("<BoolButton />", (): void => { | ||
test("it renders ChoiceButton with default props", (): void => { | ||
const choiceButtonProps = { | ||
value: null | ||
}; | ||
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps)); | ||
const buttons = getAllByRole("button") as Array<HTMLButtonElement>; | ||
|
||
expect(buttons[0].textContent).toEqual("Item 1"); | ||
expect(buttons[1].textContent).toEqual("Item 2"); | ||
expect(buttons[0].style.height).toEqual("43px"); | ||
expect(buttons[1].style.width).toEqual("46px"); | ||
expect(buttons[0].style.backgroundColor).toEqual("rgb(210, 210, 210)"); | ||
expect(buttons[1].style.fontSize).toEqual("1.4rem"); | ||
}); | ||
|
||
test("pass props to widget", (): void => { | ||
const choiceButtonProps = { | ||
value: new DType({ doubleValue: 0 }), | ||
width: 60, | ||
height: 140, | ||
items: ["Choice", "Option", "Setting", "Custom"], | ||
horizontal: false, | ||
backgroundColor: Color.fromRgba(20, 20, 200), | ||
selectedColor: Color.fromRgba(10, 60, 40), | ||
itemsFromPv: false, | ||
enabled: false | ||
}; | ||
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps)); | ||
const buttons = getAllByRole("button") as Array<HTMLButtonElement>; | ||
|
||
expect(buttons.length).toEqual(4); | ||
// First button is selected therefore different color and box shadow | ||
expect(buttons[0].style.boxShadow).toEqual( | ||
"inset 0px 23px 35px 0px rgba(0,0,0,0.3)" | ||
); | ||
expect(buttons[0].style.backgroundColor).toEqual("rgb(10, 60, 40)"); | ||
expect(buttons[2].textContent).toEqual("Setting"); | ||
expect(buttons[3].style.cursor).toEqual("not-allowed"); | ||
expect(buttons[3].style.height).toEqual("31px"); | ||
expect(buttons[3].style.backgroundColor).toEqual("rgb(20, 20, 200)"); | ||
}); | ||
|
||
test("pass props to widget, using itemsFromPv", (): void => { | ||
const choiceButtonProps = { | ||
value: new DType( | ||
{ doubleValue: 0 }, | ||
undefined, | ||
undefined, | ||
new DDisplay({ choices: ["hi", "Hello"] }) | ||
), | ||
items: ["one", "two", "three"], | ||
horizontal: false, | ||
itemsFromPv: true, | ||
enabled: true | ||
}; | ||
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps)); | ||
const buttons = getAllByRole("button") as Array<HTMLButtonElement>; | ||
|
||
expect(buttons.length).toEqual(2); | ||
// First button is selected therefore different color and box shadow | ||
expect(buttons[0].textContent).toEqual("hi"); | ||
expect(buttons[1].textContent).toEqual("Hello"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import React, { CSSProperties, useEffect, useState } from "react"; | ||
|
||
import { Widget } from "../widget"; | ||
import { PVComponent, PVWidgetPropType } from "../widgetProps"; | ||
import { registerWidget } from "../register"; | ||
import { | ||
BoolPropOpt, | ||
ColorPropOpt, | ||
FontPropOpt, | ||
InferWidgetProps, | ||
IntPropOpt, | ||
StringArrayPropOpt, | ||
StringPropOpt | ||
} from "../propTypes"; | ||
import { DType } from "../../../types/dtypes"; | ||
import classes from "./choiceButton.module.css"; | ||
import { Color } from "../../../types/color"; | ||
import { writePv } from "../../hooks/useSubscription"; | ||
import { Font } from "../../../types/font"; | ||
|
||
const ChoiceButtonProps = { | ||
pvName: StringPropOpt, | ||
height: IntPropOpt, | ||
width: IntPropOpt, | ||
items: StringArrayPropOpt, | ||
selectedColor: ColorPropOpt, | ||
itemsFromPv: BoolPropOpt, | ||
foregroundColor: ColorPropOpt, | ||
backgroundColor: ColorPropOpt, | ||
horizontal: BoolPropOpt, | ||
enabled: BoolPropOpt, | ||
itemsfromPv: BoolPropOpt, | ||
font: FontPropOpt | ||
}; | ||
|
||
export type ChoiceButtonComponentProps = InferWidgetProps< | ||
typeof ChoiceButtonProps | ||
> & | ||
PVComponent; | ||
|
||
export const ChoiceButtonComponent = ( | ||
props: ChoiceButtonComponentProps | ||
): JSX.Element => { | ||
const { | ||
width = 100, | ||
height = 43, | ||
value = null, | ||
enabled = true, | ||
itemsFromPv = true, | ||
pvName, | ||
items = ["Item 1", "Item 2"], | ||
horizontal = true, | ||
backgroundColor = Color.fromRgba(210, 210, 210), | ||
foregroundColor = Color.BLACK, | ||
selectedColor = Color.fromRgba(200, 200, 200), | ||
font = new Font(14) | ||
} = props; | ||
const [selected, setSelected] = useState(value?.getDoubleValue()); | ||
|
||
// Use items from file, unless itemsFRomPv set | ||
let options = items; | ||
if (itemsFromPv && value?.display.choices) options = value?.display.choices; | ||
|
||
// This is necessary in order to set the initial label value | ||
// after connection to PV established, as setState cannot be | ||
// established inside a conditional, or called in the main body | ||
// of the component as it causes too many re-renders error | ||
useEffect(() => { | ||
if (value) { | ||
setSelected(value.getDoubleValue()); | ||
} | ||
}, [value]); | ||
|
||
// Number of buttons to create | ||
const numButtons = options.length || 1; | ||
// Determine width and height of buttons if horizontal or vertically placed | ||
const buttonHeight = horizontal ? height : height / numButtons - 4; | ||
const buttonWidth = horizontal ? width / numButtons - 4 : width; | ||
|
||
const style: CSSProperties = { | ||
height: buttonHeight, | ||
width: buttonWidth, | ||
textAlignLast: "center", | ||
cursor: enabled ? "default" : "not-allowed", | ||
color: foregroundColor?.toString(), | ||
...font.css() | ||
}; | ||
|
||
function handleClick(index: number) { | ||
// Write to PV | ||
if (pvName) { | ||
writePv(pvName, new DType({ doubleValue: index })); | ||
} | ||
} | ||
|
||
// Iterate over items to create buttons | ||
const elements: Array<JSX.Element> = []; | ||
options.forEach((item: string | null | undefined, idx: number) => { | ||
if (typeof item === "string") { | ||
elements.push( | ||
<button | ||
className={classes.ChoiceButton} | ||
disabled={enabled ? false : true} | ||
onClick={() => handleClick(idx)} | ||
style={{ | ||
...style, | ||
backgroundColor: | ||
selected === idx | ||
? selectedColor.toString() | ||
: backgroundColor.toString(), | ||
boxShadow: | ||
selected === idx | ||
? `inset 0px ${Math.round(height / 6)}px ${Math.round( | ||
height / 4 | ||
)}px 0px rgba(0,0,0,0.3)` | ||
: "none" | ||
}} | ||
key={item} | ||
> | ||
{item} | ||
</button> | ||
); | ||
} | ||
}); | ||
|
||
return ( | ||
<div | ||
style={{ display: "flex", flexDirection: horizontal ? "row" : "column" }} | ||
> | ||
{elements} | ||
</div> | ||
); | ||
}; | ||
|
||
const ChoiceButtonWidgetProps = { | ||
...ChoiceButtonProps, | ||
...PVWidgetPropType | ||
}; | ||
|
||
export const ChoiceButton = ( | ||
props: InferWidgetProps<typeof ChoiceButtonWidgetProps> | ||
): JSX.Element => <Widget baseWidget={ChoiceButtonComponent} {...props} />; | ||
|
||
registerWidget(ChoiceButton, ChoiceButtonWidgetProps, "choicebutton"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters