From 5253615b2e80b448a817022342331735c8668dce Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Mon, 10 Nov 2025 15:28:32 +0000 Subject: [PATCH 1/4] Add path patching for symbols --- src/ui/widgets/EmbeddedDisplay/opiParser.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/widgets/EmbeddedDisplay/opiParser.ts b/src/ui/widgets/EmbeddedDisplay/opiParser.ts index 88bf212..e0de7c5 100644 --- a/src/ui/widgets/EmbeddedDisplay/opiParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/opiParser.ts @@ -824,6 +824,14 @@ function opiPatchPaths( } } } + + // symbols: list of string file paths + if ( widgetDescription["symbols"] && parentDir) { + widgetDescription["symbols"] = widgetDescription["symbols"].map((symbol: string) => { + if (symbol.startsWith("http")) return symbol; + return normalisePath(symbol, parentDir); + }) + } } function opiPatchActions(widgetDescription: WidgetDescription): void { From b3faec6f0dd4ae31b8cf8e39c069d9f15f333979 Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Tue, 11 Nov 2025 09:50:46 +0000 Subject: [PATCH 2/4] Add relative path patching for symbols and correct sizing --- src/ui/widgets/EmbeddedDisplay/opiParser.ts | 12 +++++++----- src/ui/widgets/Image/image.tsx | 12 +++++++++--- .../Symbol/__snapshots__/symbol.test.tsx.snap | 12 ++++++------ src/ui/widgets/Symbol/symbol.tsx | 6 ++++-- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/ui/widgets/EmbeddedDisplay/opiParser.ts b/src/ui/widgets/EmbeddedDisplay/opiParser.ts index e0de7c5..97af2a0 100644 --- a/src/ui/widgets/EmbeddedDisplay/opiParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/opiParser.ts @@ -826,11 +826,13 @@ function opiPatchPaths( } // symbols: list of string file paths - if ( widgetDescription["symbols"] && parentDir) { - widgetDescription["symbols"] = widgetDescription["symbols"].map((symbol: string) => { - if (symbol.startsWith("http")) return symbol; - return normalisePath(symbol, parentDir); - }) + if (widgetDescription["symbols"] && parentDir) { + widgetDescription["symbols"] = widgetDescription["symbols"].map( + (symbol: string) => { + if (symbol.startsWith("http")) return symbol; + return normalisePath(symbol, parentDir); + } + ); } } diff --git a/src/ui/widgets/Image/image.tsx b/src/ui/widgets/Image/image.tsx index 022276d..8c56cab 100644 --- a/src/ui/widgets/Image/image.tsx +++ b/src/ui/widgets/Image/image.tsx @@ -27,7 +27,8 @@ const ImageProps = { onClick: FuncPropOpt, overflow: BoolPropOpt, backgroundColor: ColorPropOpt, - transparent: BoolPropOpt + transparent: BoolPropOpt, + preserveRatio: BoolPropOpt }; export const ImageComponent = ( @@ -37,7 +38,8 @@ export const ImageComponent = ( rotation = 0, flipHorizontal, flipVertical, - stretchToFit = false + stretchToFit = false, + preserveRatio = false } = props; const onClick = (event: React.MouseEvent): void => { @@ -67,6 +69,10 @@ export const ImageComponent = ( imageFileName = imageFileName + new Date().getTime(); } + // In Phoebus, the aspect ratio of svgs is always maintained even when + // stretchToFit is true. Also accept preserveRatio property passed from + // Symbol widget + const ratio = imageFileName.includes(".svg") ? true : preserveRatio; return (
diff --git a/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap b/src/ui/widgets/Symbol/__snapshots__/symbol.test.tsx.snap index e54edde..0878264 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`] = ` >
& @@ -76,6 +77,7 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => { backgroundColor = "white", showBooleanLabel = false, enabled = true, + preserveRatio = true, pvData } = props; const { value } = getPvValueAndName(pvData); @@ -158,7 +160,7 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => { transparent imageFile={imageFile} onClick={onClick} - stretchToFit={false} + stretchToFit={preserveRatio ? false : true} overflow={true} /> {isBob ? ( From 37010820f7fbb9e04d7aea0628399ae63e5526a8 Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Wed, 12 Nov 2025 08:52:25 +0000 Subject: [PATCH 3/4] Tidy up object fit --- src/ui/widgets/Image/image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/widgets/Image/image.tsx b/src/ui/widgets/Image/image.tsx index 8c56cab..115b278 100644 --- a/src/ui/widgets/Image/image.tsx +++ b/src/ui/widgets/Image/image.tsx @@ -83,7 +83,7 @@ export const ImageComponent = ( transform: `rotate(${rotation}deg) scaleX(${ flipHorizontal ? -1 : 1 }) scaleY(${flipVertical ? -1 : 1})`, - objectFit: ratio ? "contain" : stretchToFit ? "fill" : "contain", + objectFit: ratio || !stretchToFit ? "contain" : "fill", objectPosition: "top left" }} /> From f05670b50349622452a8462bf4ce0464a0e48a79 Mon Sep 17 00:00:00 2001 From: Abigail Alexander Date: Wed, 12 Nov 2025 09:05:42 +0000 Subject: [PATCH 4/4] Make path functions return object instead of mutate --- src/ui/widgets/EmbeddedDisplay/opiParser.ts | 13 ++++++++++--- src/ui/widgets/EmbeddedDisplay/parser.ts | 9 ++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ui/widgets/EmbeddedDisplay/opiParser.ts b/src/ui/widgets/EmbeddedDisplay/opiParser.ts index 97af2a0..2817d40 100644 --- a/src/ui/widgets/EmbeddedDisplay/opiParser.ts +++ b/src/ui/widgets/EmbeddedDisplay/opiParser.ts @@ -751,7 +751,9 @@ export const OPI_COMPLEX_PARSERS: ComplexParserDict = { points: opiParsePoints }; -function opiPatchRules(widgetDescription: WidgetDescription): void { +function opiPatchRules( + widgetDescription: WidgetDescription +): WidgetDescription { /* Re-index simple parsers so we can find the correct one for the opi prop. */ const opiPropParsers: ParserDict = {}; @@ -770,6 +772,7 @@ function opiPatchRules(widgetDescription: WidgetDescription): void { }); } }); + return widgetDescription; } function normalisePath(path: string, parentDir?: string): string { @@ -784,7 +787,7 @@ function normalisePath(path: string, parentDir?: string): string { function opiPatchPaths( widgetDescription: WidgetDescription, parentDir?: string -): void { +): WidgetDescription { log.debug(`opiPatchPaths ${parentDir}`); // file: OpiFile type if ( @@ -834,9 +837,12 @@ function opiPatchPaths( } ); } + return widgetDescription; } -function opiPatchActions(widgetDescription: WidgetDescription): void { +function opiPatchActions( + widgetDescription: WidgetDescription +): WidgetDescription { if ( widgetDescription.type === "actionbutton" && widgetDescription.text && @@ -859,6 +865,7 @@ function opiPatchActions(widgetDescription: WidgetDescription): void { }; } } + return widgetDescription; } export const OPI_PATCHERS: PatchFunction[] = [ diff --git a/src/ui/widgets/EmbeddedDisplay/parser.ts b/src/ui/widgets/EmbeddedDisplay/parser.ts index ff938f3..19fcf77 100644 --- a/src/ui/widgets/EmbeddedDisplay/parser.ts +++ b/src/ui/widgets/EmbeddedDisplay/parser.ts @@ -46,7 +46,10 @@ export type ComplexParserDict = { [key: string]: (value: any) => GenericProp | Promise; }; -export type PatchFunction = (props: WidgetDescription, path?: string) => void; +export type PatchFunction = ( + props: WidgetDescription, + path?: string +) => WidgetDescription; /* Take an object representing a widget and return our widget description. */ export async function genericParser( @@ -153,7 +156,7 @@ export async function parseWidget( filepath?: string ): Promise { const targetWidget = getTargetWidget(props); - const widgetDescription = await genericParser( + let widgetDescription = await genericParser( props, targetWidget, simpleParsers, @@ -162,7 +165,7 @@ export async function parseWidget( ); // Execute patch functions. for (const patcher of patchFunctions) { - patcher(widgetDescription, filepath); + widgetDescription = patcher(widgetDescription, filepath); } /* Child widgets */ const childWidgets = toArray(props[childrenName]);