From b121c3a370c419e76ea86a2a2d1191b192bbaa01 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Wed, 17 Jun 2026 10:12:10 -0400 Subject: [PATCH 1/6] Add logic to strip virtual path from endpoint --- API/Library/HelperMethods.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/API/Library/HelperMethods.cs b/API/Library/HelperMethods.cs index c20cd7d..94cb12d 100644 --- a/API/Library/HelperMethods.cs +++ b/API/Library/HelperMethods.cs @@ -47,6 +47,15 @@ public static string GetEndpoint(this Controller controller, string baseRoute) endPoint = controller.Request.Path.Value; #else endPoint = controller.Request.RequestUri.AbsolutePath; + string virtualPathRoot = controller.RequestContext?.VirtualPathRoot; + + if (!string.IsNullOrEmpty(virtualPathRoot) && virtualPathRoot != "/") + { + virtualPathRoot = virtualPathRoot.TrimEnd('/'); + + if (endPoint.StartsWith(virtualPathRoot, System.StringComparison.OrdinalIgnoreCase)) + endPoint = endPoint.Substring(virtualPathRoot.Length); + } #endif return endPoint.Substring(baseRoute.Length + 1); From f53e12ec95067e09dae18661e2cd0dc6d90b153d Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Wed, 17 Jun 2026 11:17:05 -0400 Subject: [PATCH 2/6] Fix legend and cleanup --- TSX/Widget/OpenSEE.tsx | 224 ++++++++++++++++++++++++++++------------- 1 file changed, 156 insertions(+), 68 deletions(-) diff --git a/TSX/Widget/OpenSEE.tsx b/TSX/Widget/OpenSEE.tsx index bdbafcb..25d0c5f 100644 --- a/TSX/Widget/OpenSEE.tsx +++ b/TSX/Widget/OpenSEE.tsx @@ -24,8 +24,14 @@ import { Line, Plot } from '@gpa-gemstone/react-graph'; import React from 'react'; import { EventWidget } from '../global'; import { Input } from '@gpa-gemstone/react-forms'; +import { useGetContainerPosition } from "@gpa-gemstone/helper-functions"; + +interface ISeries { + label: string, + color: string, + data: [number, number][] +} -interface ISeries { label: string, color: string, data: [number, number][] } interface IPartialOpenseeSettings { Colors: { Va: string, @@ -43,9 +49,33 @@ interface IPartialOpenseeSettings { random: string } } -interface ISetting { OpenSeeUrl: string } -const EventSearchOpenSEE: EventWidget.IWidget = { +const defaultSettings: IPartialOpenseeSettings = { + Colors: { + Va: "#A30000", + Vb: "#0029A3", + Vc: "#007A29", + Vn: "#d3d3d3", + Vab: "#A30000", + Vbc: "#0029A3", + Vca: "#007A29", + Ia: "#FF0000", + Ib: "#0066CC", + Ic: "#33CC33", + Ires: "#d3d3d3", + In: "#d3d3d3", + random: "#4287f5" + } +} + +interface ISetting { + OpenSeeUrl: string +} + +const plotHeight = 250; +const legendLabelPadding = '\u00A0'.repeat(5); + +const EventSearchOpenSEE: EventWidget.IWidget = { Name: 'OpenSEE', DefaultSettings: { OpenSeeUrl: 'http://opensee.demo.gridprotectionalliance.org' }, Settings: (props) => { @@ -56,12 +86,15 @@ const EventSearchOpenSEE: EventWidget.IWidget = { Field={'OpenSeeUrl'} Setter={(record) => props.SetSettings(record)} Valid={() => true} - Label={'OpenSEE URL'} /> + Label={'OpenSEE URL'} + /> }, Widget: (props: EventWidget.IWidgetProps) => { - const divref = React.useRef(null); + const plotRef = React.useRef(null); + const { width } = useGetContainerPosition(plotRef); + const legendWidth = useGetLegendWidth(width); const [VData, setVData] = React.useState([]); const [VLim, setVLim] = React.useState<[number, number]>([0, 100]); @@ -70,11 +103,9 @@ const EventSearchOpenSEE: EventWidget.IWidget = { const [TCEData, setTCEData] = React.useState([]); const [TCELim, setTCELim] = React.useState<[number, number]>([0, 100]); - const [Width, SetWidth] = React.useState(0); - const [openSeeSettings, setOpenSeeSettings] = React.useState(null); + const [openSeeSettings, setOpenSeeSettings] = React.useState(null); React.useEffect(() => { setOpenSeeSettings(loadSettings()) }, []) - React.useLayoutEffect(() => { SetWidth(divref?.current?.offsetWidth ?? 0) }); React.useEffect(() => { const Vhandle = GetData('Voltage', setVData); @@ -91,6 +122,7 @@ const EventSearchOpenSEE: EventWidget.IWidget = { } }, [props.EventID]); + //These three effects below are deriving lims from data we already own, these per react docs should be turned into memo values React.useEffect(() => { let min = 0; let max = 100; @@ -101,7 +133,6 @@ const EventSearchOpenSEE: EventWidget.IWidget = { setVLim([min, max]) }, [VData]); - React.useEffect(() => { let min = 0; let max = 100; @@ -139,25 +170,6 @@ const EventSearchOpenSEE: EventWidget.IWidget = { } function loadSettings(): IPartialOpenseeSettings { - - // ToDO: Move Default OpenSee settigns to gpa-gemstone and use from there. - const defaultSettings: IPartialOpenseeSettings = { - Colors: { - Va: "#A30000", - Vb: "#0029A3", - Vc: "#007A29", - Vn: "#d3d3d3", - Vab: "#A30000", - Vbc: "#0029A3", - Vca: "#007A29", - Ia: "#FF0000", - Ib: "#0066CC", - Ic: "#33CC33", - Ires: "#d3d3d3", - In: "#d3d3d3", - random: "#4287f5" - } - } try { const serializedState = localStorage.getItem('openSee.Settings'); if (serializedState === null) @@ -178,57 +190,133 @@ const EventSearchOpenSEE: EventWidget.IWidget = { } } - function getColor(label) { + function getColor(label: string) { + + const settings = openSeeSettings ?? defaultSettings; - if (label.indexOf('VA') >= 0) return openSeeSettings.Colors.Va; - if (label.indexOf('VB') >= 0) return openSeeSettings.Colors.Vb; - if (label.indexOf('VC') >= 0) return openSeeSettings.Colors.Vc; - if (label.indexOf('VN') >= 0) return openSeeSettings.Colors.Vn; - if (label.indexOf('IA') >= 0) return openSeeSettings.Colors.Ia; - if (label.indexOf('IB') >= 0) return openSeeSettings.Colors.Ib; - if (label.indexOf('IC') >= 0) return openSeeSettings.Colors.Ic; - if (label.indexOf('IR') >= 0) return openSeeSettings.Colors.Ires; + if (label.indexOf('VA') >= 0) return settings.Colors.Va; + if (label.indexOf('VB') >= 0) return settings.Colors.Vb; + if (label.indexOf('VC') >= 0) return settings.Colors.Vc; + if (label.indexOf('VN') >= 0) return settings.Colors.Vn; + if (label.indexOf('IA') >= 0) return settings.Colors.Ia; + if (label.indexOf('IB') >= 0) return settings.Colors.Ib; + if (label.indexOf('IC') >= 0) return settings.Colors.Ic; + if (label.indexOf('IR') >= 0) return settings.Colors.Ires; - return openSeeSettings.Colors.random; + return settings.Colors.random; } return (
- - {VData.map((s, i) => )} - : null} - {IData.length > 0 ? < Plot height={250} width={Width} showBorder={false} - defaultTdomain={ILim} - yDomain={'AutoValue'} - legendWidth={150} - legend={'right'} - Tlabel={'Time'} - Ylabel={'Current (A)'} showMouse={false} zoom={false} pan={false} useMetricFactors={false}> - {IData.map((s, i) => )} - : null} - {TCEData.length > 0 ? < Plot height={250} width={Width} showBorder={false} - defaultTdomain={TCELim} - legendWidth={150} - yDomain={'AutoValue'} - legend={'right'} - Tlabel={'Time'} - Ylabel={'Trip Coil Current (A)'} showMouse={false} zoom={false} pan={false} useMetricFactors={false}> - {TCEData.map((s, i) => )} - : null} +
+
+
+ {VData.length > 0 ? + + {VData.map((s, i) => + + )} + : null} + {IData.length > 0 ? + + {IData.map((s, i) => + + )} + : null} + {TCEData.length > 0 ? + + {TCEData.map((s, i) => + + )} + : null} +
+
) } } -export default EventSearchOpenSEE; \ No newline at end of file +const useGetLegendWidth = (plotWidth: number, percent = .15) => { + const legendWidth = React.useMemo(() => { + const newLegendWidth = plotWidth * (percent ?? .15); + if (newLegendWidth < 100) return 100 + + return Math.ceil(newLegendWidth) + }, [plotWidth, percent]) + + return legendWidth; +} + +export default EventSearchOpenSEE; From dc1ca3f0dbe0bd4833976f9aeea4d30e4f617543 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Wed, 17 Jun 2026 11:22:29 -0400 Subject: [PATCH 3/6] Fix incorrect type --- TSX/global.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TSX/global.tsx b/TSX/global.tsx index 1cbe03d..0645ffb 100644 --- a/TSX/global.tsx +++ b/TSX/global.tsx @@ -35,7 +35,7 @@ export namespace EventWidget { export interface IWidgetProps { Settings: T, EventID: number, - MaxHeight?: number, + MaxHeight: number, DisturbanceID?: number, FaultID?: number, HomePath: string, From 87d4ceeb15cc5ec455ac83459b69c804604cb382 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Wed, 17 Jun 2026 11:31:18 -0400 Subject: [PATCH 4/6] Fix plot sizing --- TSX/Widget/PQICurves.tsx | 79 +++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/TSX/Widget/PQICurves.tsx b/TSX/Widget/PQICurves.tsx index 2d78538..6a6d970 100644 --- a/TSX/Widget/PQICurves.tsx +++ b/TSX/Widget/PQICurves.tsx @@ -24,10 +24,11 @@ import React from 'react'; import { EventWidget } from '../global'; import { Plot, Line } from '@gpa-gemstone/react-graph'; +import { useGetContainerPosition } from "@gpa-gemstone/helper-functions"; interface ICurve { Name: string, - Data: number[][] + Data: number[][] } const baseColors = ["#A30000", "#0029A3", "#007A29", "#d3d3d3", "#edc240", @@ -36,17 +37,17 @@ const baseColors = ["#A30000", "#0029A3", "#007A29", "#d3d3d3", "#edc240", "#737373"] const PQICurves: EventWidget.IWidget<{}> = { - Name: 'PQICurves', + Name: 'PQICurves', DefaultSettings: {}, Settings: () => { return <> }, Widget: (props: EventWidget.IWidgetProps<{}>) => { - const card = React.useRef(null); + const containerRef = React.useRef(null); + const { width, height } = useGetContainerPosition(containerRef); + const [curves, setCurves] = React.useState([]); - const [w, setW] = React.useState(0); const [maxV, setMaxV] = React.useState(1); - React.useLayoutEffect(() => { setW(card?.current?.offsetWidth ?? 0) }); React.useEffect(() => { const handle = $.ajax({ @@ -72,7 +73,7 @@ const PQICurves: EventWidget.IWidget<{}> = { React.useEffect(() => { if (curves.length > 0) - setMaxV(1.1*Math.max(...curves.map(c => Math.max(...c.Data.map(p => p[1]))))) + setMaxV(1.1 * Math.max(...curves.map(c => Math.max(...c.Data.map(p => p[1]))))) }, [curves]); return ( @@ -80,33 +81,45 @@ const PQICurves: EventWidget.IWidget<{}> = {
PQI Impacted Curves:
-
- { }}> - {curves.map((c, i) => )} - +
+
+
+ { }} + > + {curves.map((c, i) => + + )} + +
+
); From ac7f55804be11f1bb16a5c63d419d8bedbcd9ca6 Mon Sep 17 00:00:00 2001 From: Preston Crawford <78245034+prestoncraw@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:15:21 -0400 Subject: [PATCH 5/6] Update TSX/Widget/OpenSEE.tsx Co-authored-by: Erika Wills --- TSX/Widget/OpenSEE.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TSX/Widget/OpenSEE.tsx b/TSX/Widget/OpenSEE.tsx index 25d0c5f..d0a20db 100644 --- a/TSX/Widget/OpenSEE.tsx +++ b/TSX/Widget/OpenSEE.tsx @@ -86,7 +86,7 @@ const EventSearchOpenSEE: EventWidget.IWidget = { Field={'OpenSeeUrl'} Setter={(record) => props.SetSettings(record)} Valid={() => true} - Label={'OpenSEE URL'} + Label={'openSEE URL'} />
From 439e5e4d5f9e473758b19233e177e58d8b5307df Mon Sep 17 00:00:00 2001 From: Preston Crawford <78245034+prestoncraw@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:15:31 -0400 Subject: [PATCH 6/6] Update TSX/Widget/OpenSEE.tsx Co-authored-by: Erika Wills --- TSX/Widget/OpenSEE.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TSX/Widget/OpenSEE.tsx b/TSX/Widget/OpenSEE.tsx index d0a20db..94c9ba2 100644 --- a/TSX/Widget/OpenSEE.tsx +++ b/TSX/Widget/OpenSEE.tsx @@ -210,7 +210,7 @@ const EventSearchOpenSEE: EventWidget.IWidget = {