From 31a02893c9656c5e86d5086114dc983a52bd19bd Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Fri, 21 Nov 2025 13:28:08 +0000 Subject: [PATCH 1/3] Patch path for Databrowser .plt files --- src/ui/widgets/EmbeddedDisplay/bobParser.ts | 2 +- src/ui/widgets/EmbeddedDisplay/opiParser.ts | 2 +- src/ui/widgets/EmbeddedDisplay/pltParser.test.ts | 6 +++++- src/ui/widgets/EmbeddedDisplay/pltParser.ts | 15 ++++++++++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/ui/widgets/EmbeddedDisplay/bobParser.ts b/src/ui/widgets/EmbeddedDisplay/bobParser.ts index fac53eb..cea7a6a 100644 --- a/src/ui/widgets/EmbeddedDisplay/bobParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/bobParser.ts @@ -512,7 +512,7 @@ export async function parseBob( traces: (props: ElementCompact) => bobParseTraces(props["traces"]), axes: (props: ElementCompact) => bobParseYAxes(props["y_axes"]), plt: async (props: ElementCompact) => - await parsePlt(props["file"], props._attributes?.type) + await parsePlt(props["file"], filepath, props._attributes?.type) }; const displayWidget = await parseWidget( diff --git a/src/ui/widgets/EmbeddedDisplay/opiParser.ts b/src/ui/widgets/EmbeddedDisplay/opiParser.ts index 2817d40..e5411d2 100644 --- a/src/ui/widgets/EmbeddedDisplay/opiParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/opiParser.ts @@ -775,7 +775,7 @@ function opiPatchRules( return widgetDescription; } -function normalisePath(path: string, parentDir?: string): string { +export function normalisePath(path: string, parentDir?: string): string { let prefix = parentDir ?? ""; while (path.startsWith("../")) { path = path.substr(3); diff --git a/src/ui/widgets/EmbeddedDisplay/pltParser.test.ts b/src/ui/widgets/EmbeddedDisplay/pltParser.test.ts index ab2476d..18d2990 100644 --- a/src/ui/widgets/EmbeddedDisplay/pltParser.test.ts +++ b/src/ui/widgets/EmbeddedDisplay/pltParser.test.ts @@ -97,7 +97,11 @@ describe("plt parser", (): void => { }); const mockFetch = (): Promise => mockFetchPromise; vi.spyOn(globalWithFetch, "fetch").mockImplementation(mockFetch); - const plt = await parsePlt({ _text: "fakefile.plt" }, "databrowser"); + const plt = await parsePlt( + { _text: "fakefile.plt" }, + "fakeDir", + "databrowser" + ); expect(plt.backgroundColor).toEqual(Color.fromRgba(204, 204, 204)); // Check custom props parsed correctly expect(plt.axes.length).toEqual(1); diff --git a/src/ui/widgets/EmbeddedDisplay/pltParser.ts b/src/ui/widgets/EmbeddedDisplay/pltParser.ts index 75b7a62..8467955 100644 --- a/src/ui/widgets/EmbeddedDisplay/pltParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/pltParser.ts @@ -4,7 +4,8 @@ import { XmlDescription, opiParseBoolean, opiParseString, - opiParseNumber + opiParseNumber, + normalisePath } from "./opiParser"; import { parseChildProps, ParserDict } from "./parser"; import { Axis } from "../../../types/axis"; @@ -187,12 +188,16 @@ function pltParseColor(jsonProp: ElementCompact) { */ export async function parsePlt( file: ElementCompact, + parentDir: string, widgetType?: string | number ): Promise { // TO DO - check file ext is plt let props = new Plt(); if (widgetType === "databrowser" && typeof file._text === "string") { - const databrowser: XmlDescription = await fetchPltFile(file._text); + const databrowser: XmlDescription = await fetchPltFile( + file._text, + parentDir + ); // Parse the simple props const [pvlist, pvAxes] = pltParsePvlist(databrowser["pvlist"]); const axes = pltParseAxes(databrowser["axes"], pvAxes); @@ -210,7 +215,11 @@ export async function parsePlt( * @param file * @returns JSON object */ -async function fetchPltFile(file: string) { +async function fetchPltFile(file: string, parentDir: string) { + // Patch filepath if relative path + if (parentDir && !file.startsWith("http")) { + file = normalisePath(file, parentDir); + } const filePromise = await fetch(file); const contents = await filePromise.text(); // Convert it to a "compact format" From 8bfaf7baedc03d9c2477f88fc533e48d0d6c7231 Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Fri, 21 Nov 2025 13:36:54 +0000 Subject: [PATCH 2/3] Add opacity to image widget --- src/ui/widgets/Image/image.test.tsx | 9 +++++++++ src/ui/widgets/Image/image.tsx | 9 ++++++--- .../Symbol/__snapshots__/symbol.test.tsx.snap | 12 ++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/ui/widgets/Image/image.test.tsx b/src/ui/widgets/Image/image.test.tsx index 3630769..041908d 100644 --- a/src/ui/widgets/Image/image.test.tsx +++ b/src/ui/widgets/Image/image.test.tsx @@ -53,5 +53,14 @@ describe("", (): void => { } expect.assertions(1); }); + + test("opacity is applied", (): void => { + render(); + const img = screen.getByRole("img"); + + if ("style" in img) { + expect(img.style).toHaveProperty("opacity", "0.33"); + } + }); }); }); diff --git a/src/ui/widgets/Image/image.tsx b/src/ui/widgets/Image/image.tsx index 115b278..da294d6 100644 --- a/src/ui/widgets/Image/image.tsx +++ b/src/ui/widgets/Image/image.tsx @@ -28,7 +28,8 @@ const ImageProps = { overflow: BoolPropOpt, backgroundColor: ColorPropOpt, transparent: BoolPropOpt, - preserveRatio: BoolPropOpt + preserveRatio: BoolPropOpt, + opacity: FloatPropOpt }; export const ImageComponent = ( @@ -39,7 +40,8 @@ export const ImageComponent = ( flipHorizontal, flipVertical, stretchToFit = false, - preserveRatio = false + preserveRatio = false, + opacity = 1 } = props; const onClick = (event: React.MouseEvent): void => { @@ -84,7 +86,8 @@ export const ImageComponent = ( flipHorizontal ? -1 : 1 }) scaleY(${flipVertical ? -1 : 1})`, objectFit: ratio || !stretchToFit ? "contain" : "fill", - objectPosition: "top left" + objectPosition: "top left", + opacity: opacity }} /> diff --git a/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap b/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap index 45a0c82..10fe116 100644 --- a/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap +++ b/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap @@ -7,7 +7,7 @@ exports[` from .bob file > matches snapshot (using fallback symbol) 1` > @@ -20,7 +20,7 @@ exports[` from .bob file > matches snapshot (with index) 1`] = ` > @@ -33,7 +33,7 @@ exports[` from .bob file > matches snapshot (with rotation) 1`] = ` > @@ -46,7 +46,7 @@ exports[` from .bob file > matches snapshot (without index) 1`] = ` > @@ -59,7 +59,7 @@ exports[` from .opi file > matches snapshot (with rotation) 1`] = ` > @@ -72,7 +72,7 @@ exports[` from .opi file > matches snapshot 1`] = ` >
Date: Mon, 24 Nov 2025 09:36:55 +0000 Subject: [PATCH 3/3] Make LEDs square --- src/ui/widgets/ByteMonitor/byteMonitor.test.tsx | 2 +- src/ui/widgets/ByteMonitor/byteMonitor.tsx | 12 ++++++------ src/ui/widgets/EmbeddedDisplay/bobParser.ts | 2 +- src/ui/widgets/EmbeddedDisplay/opiParser.ts | 1 + src/ui/widgets/LED/led.module.css | 1 - src/ui/widgets/LED/led.test.tsx | 15 ++++++++++----- src/ui/widgets/LED/led.tsx | 11 +++++++---- 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/ui/widgets/ByteMonitor/byteMonitor.test.tsx b/src/ui/widgets/ByteMonitor/byteMonitor.test.tsx index f83b3b9..b5b91d1 100644 --- a/src/ui/widgets/ByteMonitor/byteMonitor.test.tsx +++ b/src/ui/widgets/ByteMonitor/byteMonitor.test.tsx @@ -54,7 +54,7 @@ describe("", (): void => { startBit: 8, numBits: 16, horizontal: false, - squareLed: true, + square: true, bitReverse: true, effect3d: false, onColor: Color.fromRgba(200, 200, 200), diff --git a/src/ui/widgets/ByteMonitor/byteMonitor.tsx b/src/ui/widgets/ByteMonitor/byteMonitor.tsx index 993f70c..2c916d9 100644 --- a/src/ui/widgets/ByteMonitor/byteMonitor.tsx +++ b/src/ui/widgets/ByteMonitor/byteMonitor.tsx @@ -22,7 +22,7 @@ export const ByteMonitorProps = { startBit: IntPropOpt, horizontal: BoolPropOpt, bitReverse: BoolPropOpt, - squareLed: BoolPropOpt, + square: BoolPropOpt, ledBorder: IntPropOpt, ledBorderColor: ColorPropOpt, effect3d: BoolPropOpt @@ -49,8 +49,8 @@ export const ByteMonitorComponent = ( onColor = Color.fromRgba(0, 255, 0), offColor = Color.fromRgba(0, 100, 0), ledBorder = 2, - ledBorderColor = Color.fromRgba(150, 150, 150), // dark grey - squareLed = false, + ledBorderColor = Color.fromRgba(50, 50, 50, 178), // dark grey + square = false, effect3d = false, width = WIDGET_DEFAULT_SIZES["byte_monitor"][0], height = WIDGET_DEFAULT_SIZES["byte_monitor"][1] @@ -73,7 +73,7 @@ export const ByteMonitorComponent = ( height, border, horizontal, - squareLed + square ); const ledArray: Array = []; dataValues.forEach((data: number, idx: number) => { @@ -94,7 +94,7 @@ export const ByteMonitorComponent = ( style["borderColor"] = ledBorderColor.toString(); style["borderWidth"] = `${borderWidth}px`; // Set shape as square or circular - if (squareLed) { + if (square) { style.width = `${bitWidth + borderWidth}px`; style.height = `${bitHeight + borderWidth}px`; } else { @@ -113,7 +113,7 @@ export const ByteMonitorComponent = ( style["backgroundImage"] = `radial-gradient(circle at top left, white, ${ data ? onColor?.toString() : offColor?.toString() })`; - if (!squareLed) { + if (!square) { style["borderColor"] = "transparent"; style["backgroundImage"] += ", radial-gradient(circle at top left, black,white)"; diff --git a/src/ui/widgets/EmbeddedDisplay/bobParser.ts b/src/ui/widgets/EmbeddedDisplay/bobParser.ts index cea7a6a..68faa81 100644 --- a/src/ui/widgets/EmbeddedDisplay/bobParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/bobParser.ts @@ -421,7 +421,7 @@ const BOB_SIMPLE_PARSERS: ParserDict = { imageFile: ["file", opiParseString], points: ["points", bobParsePoints], resize: ["resize", bobParseResizing], - squareLed: ["square", opiParseBoolean], + square: ["square", opiParseBoolean], formatType: ["format", bobParseFormatType], stretchToFit: ["stretch_image", opiParseBoolean], macros: ["macros", opiParseMacros], diff --git a/src/ui/widgets/EmbeddedDisplay/opiParser.ts b/src/ui/widgets/EmbeddedDisplay/opiParser.ts index e5411d2..7ad6eaf 100644 --- a/src/ui/widgets/EmbeddedDisplay/opiParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/opiParser.ts @@ -706,6 +706,7 @@ export const OPI_SIMPLE_PARSERS: ParserDict = { ledBorder: ["led_border", opiParseNumber], ledBorderColor: ["led_border_color", opiParseColor], bitReverse: ["bitReverse", opiParseBoolean], + square: ["square", opiParseBoolean], squareLed: ["square_led", opiParseBoolean], squareButton: ["square_button", opiParseBoolean], effect3d: ["effect_3d", opiParseBoolean], diff --git a/src/ui/widgets/LED/led.module.css b/src/ui/widgets/LED/led.module.css index c8744aa..3f4cff7 100644 --- a/src/ui/widgets/LED/led.module.css +++ b/src/ui/widgets/LED/led.module.css @@ -1,7 +1,6 @@ .Led { width: 11px; height: 11px; - border-radius: 50%; background-color: #00ee00; } diff --git a/src/ui/widgets/LED/led.test.tsx b/src/ui/widgets/LED/led.test.tsx index 9c9a065..83679ca 100644 --- a/src/ui/widgets/LED/led.test.tsx +++ b/src/ui/widgets/LED/led.test.tsx @@ -86,19 +86,24 @@ describe("width property is used", (): void => { // Width in CS-Studio doesn't quite match width in the browser, // so whatever is input has 5 subtracted from it, this makes it // look more like CS-Studio - expect(renderedLed.props.style.width).toBe("5px"); - expect(renderedLed.props.style.height).toBe("15px"); + expect(renderedLed.props.style.width).toBe("10px"); + expect(renderedLed.props.style.height).toBe("20px"); }); }); describe("height property is used", (): void => { it("height changes the size of the LED", (): void => { - const renderedLed = renderLed({ ...DEFAULT_PROPS, height: 10 }); + const renderedLed = renderLed({ + ...DEFAULT_PROPS, + height: 10, + square: true + }); // Width in CS-Studio doesn't quite match width in the browser, // so whatever is input has 5 subtracted from it, this makes it // look more like CS-Studio - expect(renderedLed.props.style.width).toBe("15px"); - expect(renderedLed.props.style.height).toBe("5px"); + expect(renderedLed.props.style.width).toBe("20px"); + expect(renderedLed.props.style.height).toBe("10px"); + expect(renderedLed.props.style.borderRadius).toBe("0%"); }); }); diff --git a/src/ui/widgets/LED/led.tsx b/src/ui/widgets/LED/led.tsx index 63ce00e..0e2f91c 100644 --- a/src/ui/widgets/LED/led.tsx +++ b/src/ui/widgets/LED/led.tsx @@ -25,7 +25,8 @@ export const LedProps = { offColor: ColorPropOpt, lineColor: ColorPropOpt, alarmSensitive: BoolPropOpt, - bit: IntPropOpt + bit: IntPropOpt, + square: BoolPropOpt }; export type LedComponentProps = InferWidgetProps & PVComponent; @@ -45,7 +46,8 @@ export const LedComponent = (props: LedComponentProps): JSX.Element => { width = WIDGET_DEFAULT_SIZES["led"][0], height = WIDGET_DEFAULT_SIZES["led"][1], alarmSensitive = false, - bit = -1 + bit = -1, + square = false } = props; const { value } = getPvValueAndName(pvData); @@ -65,11 +67,12 @@ export const LedComponent = (props: LedComponentProps): JSX.Element => { } style["backgroundColor"] = ledOn ? onColor?.toString() : offColor?.toString(); style["border"] = `2px solid ${lineColor.toString()}`; + style["borderRadius"] = square ? "0%" : "50%"; // make sizes similar to size in CS-Studio, five taken // away from default in css file too - style.width = `${width - 5}px`; - style.height = `${height - 5}px`; + style.width = `${width}px`; + style.height = `${height}px`; let className = classes.Led; if (alarmSensitive) {