diff --git a/esp/src/src-react/components/Metrics.tsx b/esp/src/src-react/components/Metrics.tsx index f31cf7a4f1f..99b25491089 100644 --- a/esp/src/src-react/components/Metrics.tsx +++ b/esp/src/src-react/components/Metrics.tsx @@ -280,6 +280,7 @@ export const Metrics: React.FunctionComponent = ({ const metricGraph = useConst(() => new MetricGraph()); const metricGraphWidget = useConst(() => new MetricGraphWidget() .zoomToFitLimit(1) + .selectionGlowColor("DodgerBlue") .on("selectionChanged", () => { const selection = metricGraphWidget.selection().filter(id => metricGraph.item(id)).map(id => metricGraph.item(id).id); setSelectedMetricsSource("metricGraphWidget"); @@ -592,10 +593,16 @@ export const Metrics: React.FunctionComponent = ({ ]; }, [scopeFilter, onChangeScopeFilter, scopesTable, graphComponent, propsTable, propsTable2]); - const layoutChanged = React.useCallback((layout) => { - setOptions({ ...options, layout }); - saveOptions(); - }, [options, saveOptions, setOptions]); + React.useEffect(() => { + + // Update layout prior to unmount --- + if (dockpanel && options && saveOptions && setOptions) { + return () => { + setOptions({ ...options, layout: dockpanel.getLayout() }); + saveOptions(); + }; + } + }, [dockpanel, options, saveOptions, setOptions]); // Command Bar --- const buttons = React.useMemo((): ICommandBarItemProps[] => [ @@ -678,7 +685,7 @@ export const Metrics: React.FunctionComponent = ({ } main={ - + } diff --git a/esp/src/src-react/components/MetricsOptions.tsx b/esp/src/src-react/components/MetricsOptions.tsx index 167c22051a5..549b644ae2b 100644 --- a/esp/src/src-react/components/MetricsOptions.tsx +++ b/esp/src/src-react/components/MetricsOptions.tsx @@ -21,7 +21,9 @@ export const MetricsOptions: React.FunctionComponent = ({ const [scopeTypes, properties] = useMetricMeta(); const [options, setOptions, save, reset] = useMetricsOptions(); - const closeOptions = () => setShow(false); + const closeOptions = React.useCallback(() => { + setShow(false); + }, [setShow]); const allChecked = scopeTypes.length === options.scopeTypes.length; diff --git a/esp/src/src-react/hooks/metrics.ts b/esp/src/src-react/hooks/metrics.ts index f40dd50734a..c01b1c3adc3 100644 --- a/esp/src/src-react/hooks/metrics.ts +++ b/esp/src/src-react/hooks/metrics.ts @@ -20,6 +20,13 @@ const defaults = { const options = { ...defaults }; +function checkLayout(options: MetricsOptions): boolean { + if (options?.layout && !options?.layout?.["main"]) { + delete options.layout; + } + return !!options?.layout; +} + export interface MetricsOptions { scopeTypes: string[]; properties: string[]; @@ -43,7 +50,9 @@ export function useMetricsOptions(): [MetricsOptions, (opts: MetricsOptions) => }, [refresh]); const save = React.useCallback(() => { - store?.set("MetricOptions", JSON.stringify(options), true); + if (checkLayout(options)) { + store?.set("MetricOptions", JSON.stringify(options), true); + } }, [store]); const reset = React.useCallback((toDefaults: boolean = false) => { @@ -51,7 +60,9 @@ export function useMetricsOptions(): [MetricsOptions, (opts: MetricsOptions) => setOptions({ ...defaults }); } else { store?.get("MetricOptions").then(opts => { - setOptions({ ...defaults, ...JSON.parse(opts) }); + const options = JSON.parse(opts); + checkLayout(options); + setOptions({ ...defaults, ...options }); }); } }, [setOptions, store]); diff --git a/esp/src/src-react/layouts/DockPanel.tsx b/esp/src/src-react/layouts/DockPanel.tsx index 24435d54fd9..dff57463fa7 100644 --- a/esp/src/src-react/layouts/DockPanel.tsx +++ b/esp/src/src-react/layouts/DockPanel.tsx @@ -105,49 +105,72 @@ function isDockPanelComponent(item: DockPanelWidget | DockPanelComponent): item return !!(item as DockPanelComponent).component; } +export interface DockPanelLayout { + main: object; +} + +function validLayout(layout?: any) { + return !!layout?.main; +} + +function formatLayout(layout?: any): DockPanelLayout | undefined { + if (validLayout(layout)) { + return layout; + } + return undefined; +} + export class ResetableDockPanel extends HPCCDockPanel { - protected _origLayout; + protected _origLayout: DockPanelLayout | undefined; + protected _lastLayout: DockPanelLayout | undefined; - render() { - const retVal = super.render(); - if (this._origLayout === undefined) { - this._origLayout = this.layout(); + resetLayout() { + if (this._origLayout) { + this + .layout(this._origLayout) + .lazyRender() + ; } - return retVal; } setLayout(layout: object) { if (this._origLayout === undefined) { - this._origLayout = this.layout(); + this._origLayout = formatLayout(this.layout()); } this.layout(layout); return this; } - resetLayout() { - if (this._origLayout) { - this - .layout(this._origLayout) - .lazyRender() - ; + getLayout() { + return formatLayout(this.layout()) ?? this._lastLayout ?? this._origLayout; + } + + render() { + const retVal = super.render(); + if (this._origLayout === undefined) { + this._origLayout = formatLayout(this.layout()); } + return retVal; + } + + // Events --- + layoutChanged() { + this._lastLayout = this.getLayout(); } } export type DockPanelItems = (DockPanelWidget | DockPanelComponent)[]; interface DockPanelProps { - items?: DockPanelItems, - layout?: object, - layoutChanged: (layout: object) => void, - onDockPanelCreate: (dockpanel: ResetableDockPanel) => void + items?: DockPanelItems; + layout?: object; + onDockPanelCreate?: (dockpanel: ResetableDockPanel) => void; } export const DockPanel: React.FunctionComponent = ({ - items, + items = [], layout, - layoutChanged = layout => { }, onDockPanelCreate }) => { @@ -167,9 +190,11 @@ export const DockPanel: React.FunctionComponent = ({ } }); setIdx(idx); - setTimeout(() => { - onDockPanelCreate(retVal); - }, 0); + if (onDockPanelCreate) { + setTimeout(() => { + onDockPanelCreate(retVal); + }, 0); + } return retVal; }); @@ -181,12 +206,6 @@ export const DockPanel: React.FunctionComponent = ({ } }, [dockPanel, layout]); - React.useEffect(() => { - return () => { - layoutChanged(dockPanel?.layout()); - }; - }, [dockPanel, layoutChanged]); - React.useEffect(() => { items.filter(isDockPanelComponent).forEach(item => { (idx[item.key] as ReactWidget) diff --git a/esp/src/src-react/layouts/HpccJSAdapter.tsx b/esp/src/src-react/layouts/HpccJSAdapter.tsx index 9be9029b3ec..4c9a9c57f8a 100644 --- a/esp/src/src-react/layouts/HpccJSAdapter.tsx +++ b/esp/src/src-react/layouts/HpccJSAdapter.tsx @@ -20,17 +20,15 @@ export const HpccJSComponent: React.FunctionComponent = ({ }) => { const divID = useId("viz-component-"); - React.useEffect(() => { - const w = widget?.target(divID) - .render() - ; - return () => { - w?.target(null); - }; - }, [divID, widget]); + const setDivRef = React.useCallback(node => { + widget?.target(node); + if (node) { + widget?.render(); + } + }, [widget]); React.useEffect(() => { - if (widget.target()) { + if (widget?.target()) { widget.resize({ width, height }); if (debounce) { widget.lazyRender(); @@ -40,9 +38,9 @@ export const HpccJSComponent: React.FunctionComponent = ({ } }, [debounce, height, widget, width]); - return (isNaN(width) || isNaN(height)) ? + return (isNaN(width) || isNaN(height) || width === 0 || height === 0) ? <> : -
+
; }; @@ -66,7 +64,7 @@ export const AutosizeHpccJSComponent: React.FunctionComponent{({ size }) => { const width = size?.width || padding * 2; const height = size?.height || padding * 2; - return