", (): void => {
+describe("
from .opi file", (): void => {
test("label is not shown if showLabel is false", (): void => {
const symbolProps = {
showBooleanLabel: false,
@@ -40,4 +42,120 @@ describe("
", (): void => {
expect(asFragment()).toMatchSnapshot();
});
+
+ test("matches snapshot (with rotation)", (): void => {
+ const symbolProps = {
+ showBooleanLabel: false,
+ imageFile: "img 1.gif",
+ value: fakeValue,
+ rotation: 45
+ };
+
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+});
+
+describe("
from .bob file", (): void => {
+ test("index is not shown if showIndex is false", (): void => {
+ const symbolProps = {
+ symbols: ["img 1.gif"],
+ value: new DType({ stringValue: "0" })
+ };
+
+ render(
);
+
+ expect(screen.queryByText("0")).not.toBeInTheDocument();
+ });
+
+ test("index is added", (): void => {
+ const symbolProps = {
+ showIndex: true,
+ symbols: ["img 1.gif", "img 2.png"],
+ value: stringValue
+ };
+ render(
);
+
+ expect(screen.getByText("1")).toBeInTheDocument();
+ });
+
+ test("use initialIndex if no props value provided", (): void => {
+ const symbolProps = {
+ showIndex: true,
+ initialIndex: 2,
+ symbols: ["img 1.gif", "img 2.png", "img 3.svg"],
+ value: undefined
+ };
+
+ render(
);
+
+ expect(screen.getByText("2")).toBeInTheDocument();
+ });
+
+ test("use arrayIndex to find index if value is an array", (): void => {
+ const symbolProps = {
+ arrayIndex: 0,
+ showIndex: true,
+ symbols: ["img 1.gif", "img 2.png", "img 3.svg"],
+ value: arrayValue
+ };
+ render(
);
+
+ expect(screen.getByText("2")).toBeInTheDocument();
+ });
+
+ test("matches snapshot (without index)", (): void => {
+ const symbolProps = {
+ symbols: ["img 1.gif"],
+ value: new DType({ stringValue: "0" })
+ };
+
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test("matches snapshot (with index)", (): void => {
+ const symbolProps = {
+ symbols: ["img 1.gif", "img 2.png", "img 3.svg"],
+ value: new DType({ stringValue: "2" })
+ };
+
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test("matches snapshot (using fallback symbol)", (): void => {
+ const symbolProps = {
+ symbols: ["img 1.gif"],
+ value: new DType({ doubleValue: 1 })
+ };
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ test("matches snapshot (with rotation)", (): void => {
+ const symbolProps = {
+ symbols: ["img 1.gif"],
+ value: new DType({ stringValue: "0" }),
+ rotation: 45
+ };
+
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
});
diff --git a/src/ui/widgets/Symbol/symbol.tsx b/src/ui/widgets/Symbol/symbol.tsx
index 403b21de..ef94c242 100644
--- a/src/ui/widgets/Symbol/symbol.tsx
+++ b/src/ui/widgets/Symbol/symbol.tsx
@@ -9,10 +9,10 @@ import {
ColorPropOpt,
FloatPropOpt,
BorderPropOpt,
- StringProp,
ChoicePropOpt,
FontPropOpt,
- ActionsPropType
+ ActionsPropType,
+ StringArrayPropOpt
} from "../propTypes";
import { registerWidget } from "../register";
import { ImageComponent } from "../Image/image";
@@ -22,9 +22,11 @@ import { executeActions, WidgetActions } from "../widgetActions";
import { MacroContext } from "../../../types/macros";
import { ExitFileContext, FileContext } from "../../../misc/fileContext";
import { DType } from "../../../types/dtypes";
+import classes from "./symbol.module.css";
const SymbolProps = {
- imageFile: StringProp,
+ imageFile: StringPropOpt,
+ symbols: StringArrayPropOpt,
alt: StringPropOpt,
backgroundColor: ColorPropOpt,
showBooleanLabel: BoolPropOpt,
@@ -46,7 +48,13 @@ const SymbolProps = {
visible: BoolPropOpt,
stretchToFit: BoolPropOpt,
actions: ActionsPropType,
- font: FontPropOpt
+ font: FontPropOpt,
+ initialIndex: FloatPropOpt,
+ showIndex: BoolPropOpt,
+ arrayIndex: FloatPropOpt,
+ enabled: BoolPropOpt,
+ fallbackSymbol: StringPropOpt,
+ transparent: BoolPropOpt
};
export type SymbolComponentProps = InferWidgetProps
&
@@ -58,13 +66,31 @@ export type SymbolComponentProps = InferWidgetProps &
* @param props
*/
export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => {
+ const {
+ showIndex = false,
+ arrayIndex = 0,
+ initialIndex = 0,
+ fallbackSymbol = "https://cs-web-symbol.diamond.ac.uk/catalogue/default.svg",
+ transparent = true,
+ backgroundColor = "white",
+ showBooleanLabel = false,
+ enabled = true
+ } = props;
const style = commonCss(props as any);
+ // If symbols and not imagefile, we're in a bob file
+ const isBob = props.symbols ? true : false;
+ const symbols = props.symbols ? props.symbols : [];
+
+ // Convert our value to an index, or use the initialIndex
+ const index = convertValueToIndex(props.value, initialIndex, arrayIndex);
- let imageFile = props.imageFile;
const regex = / [0-9]\./;
+ let imageFile = isBob ? symbols[index] : props.imageFile;
+ // If no provided image file
+ if (!imageFile) imageFile = fallbackSymbol;
const intValue = DType.coerceDouble(props.value);
- if (!isNaN(intValue)) {
- imageFile = props.imageFile.replace(regex, ` ${intValue.toFixed(0)}.`);
+ if (!isNaN(intValue) && !isBob) {
+ imageFile = imageFile.replace(regex, ` ${intValue.toFixed(0)}.`);
}
let alignItems = "center";
@@ -104,7 +130,7 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => {
const exitContext = useContext(ExitFileContext);
const parentMacros = useContext(MacroContext).macros;
function onClick(event: React.MouseEvent): void {
- if (props.actions !== undefined) {
+ if (props.actions !== undefined && enabled) {
executeActions(
props.actions as WidgetActions,
files,
@@ -114,27 +140,36 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => {
}
}
+ // Define label appearance
+ let labelDiv;
+ if (isBob) labelDiv = generateIndexLabel(index, showIndex);
+
// Note: I would've preferred to define the onClick on div that wraps
// both sub-components, but replacing the fragment with a div, with the way
// the image component is written causes many images to be of the incorrect size
return (
<>
-
- {props.showBooleanLabel && (
+
+ {isBob ? (
+ labelDiv
+ ) : showBooleanLabel ? (
<>
@@ -146,11 +181,53 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => {
>
+ ) : (
+ <>>
)}
>
);
};
+/**
+ * Return a div element describing how the label should look
+ */
+function generateIndexLabel(index: number, showIndex: boolean): JSX.Element {
+ if (!showIndex) return <>>;
+ // Create span
+ return (
+
+
+ {index}
+
+
+ );
+}
+
+/**
+ * Convert the input value into an index for symbols
+ * @param value
+ */
+function convertValueToIndex(
+ value: DType | undefined,
+ initialIndex: number,
+ arrayIndex: number
+): number {
+ // If no value, use initialIndex
+ if (value === undefined) return initialIndex;
+ // First we check if we have a string
+ const isArray = value.getArrayValue()?.length !== undefined ? true : false;
+ if (isArray) {
+ // If is array, get index
+ const arrayValue = DType.coerceArray(value);
+ const idx = Number(arrayValue[arrayIndex]);
+ return Math.floor(idx);
+ } else {
+ const intValue = DType.coerceDouble(value);
+ if (!isNaN(intValue)) return Math.floor(intValue);
+ }
+ return initialIndex;
+}
+
const SymbolWidgetProps = {
...SymbolProps,
...PVWidgetPropType