diff --git a/packages/chart/src/Chart.tsx b/packages/chart/src/Chart.tsx index 5a403f9a02..af6af5914a 100644 --- a/packages/chart/src/Chart.tsx +++ b/packages/chart/src/Chart.tsx @@ -49,6 +49,7 @@ interface ChartProps { settings: FormatterSettings; isActive: boolean; Plotly: typeof Plotly; + containerRef?: React.RefObject; onDisconnect: () => void; onReconnect: () => void; onUpdate: (obj: { isLoading: boolean }) => void; @@ -145,7 +146,7 @@ export class Chart extends Component { this.PlotComponent = createPlotlyComponent(props.Plotly); this.plot = React.createRef(); - this.plotWrapper = React.createRef(); + this.plotWrapper = props.containerRef ?? React.createRef(); this.columnFormats = []; this.dateTimeFormatterOptions = {}; this.decimalFormatOptions = {}; diff --git a/packages/dashboard-core-plugins/src/ChartPlugin.tsx b/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx similarity index 94% rename from packages/dashboard-core-plugins/src/ChartPlugin.tsx rename to packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx index d335d54191..5dfbe3974f 100644 --- a/packages/dashboard-core-plugins/src/ChartPlugin.tsx +++ b/packages/dashboard-core-plugins/src/ChartPanelPlugin.tsx @@ -14,7 +14,7 @@ import type { } from '@deephaven/jsapi-types'; import { IrisGridUtils } from '@deephaven/iris-grid'; import { getTimeZone, store } from '@deephaven/redux'; -import { type WidgetComponentProps } from '@deephaven/plugin'; +import { WidgetPanelProps } from '@deephaven/plugin'; import { ChartPanelMetadata, GLChartPanelState, @@ -113,8 +113,8 @@ async function createChartModel( ); } -export const ChartPlugin = forwardRef( - (props: WidgetComponentProps, ref: React.Ref) => { +export const ChartPanelPlugin = forwardRef( + (props: WidgetPanelProps, ref: React.Ref) => { const dh = useApi(); const chartTheme = useChartTheme(); const connection = useConnection(); @@ -160,6 +160,6 @@ export const ChartPlugin = forwardRef( } ); -ChartPlugin.displayName = 'ChartPlugin'; +ChartPanelPlugin.displayName = 'ChartPanelPlugin'; -export default ChartPlugin; +export default ChartPanelPlugin; diff --git a/packages/dashboard-core-plugins/src/ChartPluginConfig.ts b/packages/dashboard-core-plugins/src/ChartPluginConfig.ts index db712f8809..e63cb9ee87 100644 --- a/packages/dashboard-core-plugins/src/ChartPluginConfig.ts +++ b/packages/dashboard-core-plugins/src/ChartPluginConfig.ts @@ -1,13 +1,14 @@ import { PluginType, type WidgetPlugin } from '@deephaven/plugin'; import { vsGraph } from '@deephaven/icons'; -import ChartPlugin from './ChartPlugin'; +import { ChartWidgetPlugin } from './ChartWidgetPlugin'; +import { ChartPanelPlugin } from './ChartPanelPlugin'; const ChartPluginConfig: WidgetPlugin = { name: 'ChartPanel', title: 'Chart', type: PluginType.WIDGET_PLUGIN, - component: ChartPlugin, - panelComponent: ChartPlugin, + component: ChartWidgetPlugin, + panelComponent: ChartPanelPlugin, supportedTypes: 'Figure', icon: vsGraph, }; diff --git a/packages/dashboard-core-plugins/src/ChartWidgetPlugin.tsx b/packages/dashboard-core-plugins/src/ChartWidgetPlugin.tsx new file mode 100644 index 0000000000..f55b86b071 --- /dev/null +++ b/packages/dashboard-core-plugins/src/ChartWidgetPlugin.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { useApi } from '@deephaven/jsapi-bootstrap'; +import { + Chart, + ChartModel, + ChartModelFactory, + useChartTheme, +} from '@deephaven/chart'; +import type { Figure } from '@deephaven/jsapi-types'; +import { type WidgetComponentProps } from '@deephaven/plugin'; + +export function ChartWidgetPlugin( + props: WidgetComponentProps +): JSX.Element | null { + const dh = useApi(); + const chartTheme = useChartTheme(); + const [model, setModel] = useState(); + + const { fetch } = props; + + useEffect(() => { + let cancelled = false; + async function init() { + const figure = (await fetch()) as unknown as Figure; + const newModel = await ChartModelFactory.makeModel( + dh, + undefined, + figure, + chartTheme + ); + + if (!cancelled) { + setModel(newModel); + } + } + + init(); + + return () => { + cancelled = true; + }; + }, [dh, fetch, chartTheme]); + + return model ? : null; +} + +export default ChartWidgetPlugin; diff --git a/packages/dashboard-core-plugins/src/GridPlugin.tsx b/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx similarity index 69% rename from packages/dashboard-core-plugins/src/GridPlugin.tsx rename to packages/dashboard-core-plugins/src/GridPanelPlugin.tsx index 72601da32a..75666bd064 100644 --- a/packages/dashboard-core-plugins/src/GridPlugin.tsx +++ b/packages/dashboard-core-plugins/src/GridPanelPlugin.tsx @@ -1,13 +1,13 @@ import { forwardRef } from 'react'; -import { type WidgetComponentProps } from '@deephaven/plugin'; +import { WidgetPanelProps } from '@deephaven/plugin'; import { type Table } from '@deephaven/jsapi-types'; import useHydrateGrid from './useHydrateGrid'; import ConnectedIrisGridPanel, { type IrisGridPanel, } from './panels/IrisGridPanel'; -export const GridPlugin = forwardRef( - (props: WidgetComponentProps, ref: React.Ref) => { +export const GridPanelPlugin = forwardRef( + (props: WidgetPanelProps, ref: React.Ref) => { const { localDashboardId, fetch } = props; const hydratedProps = useHydrateGrid( fetch as unknown as () => Promise, @@ -19,6 +19,6 @@ export const GridPlugin = forwardRef( } ); -GridPlugin.displayName = 'GridPlugin'; +GridPanelPlugin.displayName = 'GridPanelPlugin'; -export default GridPlugin; +export default GridPanelPlugin; diff --git a/packages/dashboard-core-plugins/src/GridPluginConfig.ts b/packages/dashboard-core-plugins/src/GridPluginConfig.ts index 9b17fbc708..6e18aebe1b 100644 --- a/packages/dashboard-core-plugins/src/GridPluginConfig.ts +++ b/packages/dashboard-core-plugins/src/GridPluginConfig.ts @@ -1,13 +1,14 @@ import { PluginType, type WidgetPlugin } from '@deephaven/plugin'; import { dhTable } from '@deephaven/icons'; -import GridPlugin from './GridPlugin'; +import { GridWidgetPlugin } from './GridWidgetPlugin'; +import { GridPanelPlugin } from './GridPanelPlugin'; const GridPluginConfig: WidgetPlugin = { name: 'IrisGridPanel', title: 'Table', type: PluginType.WIDGET_PLUGIN, - component: GridPlugin, - panelComponent: GridPlugin, + component: GridWidgetPlugin, + panelComponent: GridPanelPlugin, supportedTypes: ['Table', 'TreeTable', 'HierarchicalTable'], icon: dhTable, }; diff --git a/packages/dashboard-core-plugins/src/GridWidgetPlugin.tsx b/packages/dashboard-core-plugins/src/GridWidgetPlugin.tsx new file mode 100644 index 0000000000..f53a920160 --- /dev/null +++ b/packages/dashboard-core-plugins/src/GridWidgetPlugin.tsx @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; +import { type WidgetComponentProps } from '@deephaven/plugin'; +import { type Table } from '@deephaven/jsapi-types'; +import { useApi } from '@deephaven/jsapi-bootstrap'; +import { + IrisGrid, + IrisGridModelFactory, + type IrisGridModel, +} from '@deephaven/iris-grid'; + +export function GridWidgetPlugin( + props: WidgetComponentProps +): JSX.Element | null { + const dh = useApi(); + const [model, setModel] = useState(); + + const { fetch } = props; + + useEffect(() => { + let cancelled = false; + async function init() { + const table = (await fetch()) as unknown as Table; + const newModel = await IrisGridModelFactory.makeModel(dh, table); + if (!cancelled) { + setModel(newModel); + } + } + + init(); + + return () => { + cancelled = true; + }; + }, [dh, fetch]); + + return model ? : null; +} + +export default GridWidgetPlugin; diff --git a/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx b/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx new file mode 100644 index 0000000000..520130cb56 --- /dev/null +++ b/packages/dashboard-core-plugins/src/PandasPanelPlugin.tsx @@ -0,0 +1,24 @@ +import { forwardRef } from 'react'; +import { WidgetPanelProps } from '@deephaven/plugin'; +import { type Table } from '@deephaven/jsapi-types'; +import { PandasPanel } from './panels'; +import useHydrateGrid from './useHydrateGrid'; + +export const PandasPanelPlugin = forwardRef( + (props: WidgetPanelProps, ref: React.Ref) => { + const { localDashboardId, fetch } = props; + const hydratedProps = useHydrateGrid( + fetch as unknown as () => Promise
, + localDashboardId + ); + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); + } +); + +PandasPanelPlugin.displayName = 'PandasPanelPlugin'; + +export default PandasPanelPlugin; diff --git a/packages/dashboard-core-plugins/src/PandasPlugin.tsx b/packages/dashboard-core-plugins/src/PandasPlugin.tsx deleted file mode 100644 index 0b5ce7a4e9..0000000000 --- a/packages/dashboard-core-plugins/src/PandasPlugin.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { forwardRef } from 'react'; -import { WidgetComponentProps } from '@deephaven/plugin'; -import { type Table } from '@deephaven/jsapi-types'; -import { PandasPanel } from './panels'; -import useHydrateGrid from './useHydrateGrid'; - -export const PandasPlugin = forwardRef( - (props: WidgetComponentProps, ref: React.Ref) => { - const { localDashboardId, fetch } = props; - const hydratedProps = useHydrateGrid( - fetch as unknown as () => Promise
, - localDashboardId - ); - - // eslint-disable-next-line react/jsx-props-no-spreading - return ; - } -); - -PandasPlugin.displayName = 'PandasPlugin'; - -export default PandasPlugin; diff --git a/packages/dashboard-core-plugins/src/PandasPluginConfig.ts b/packages/dashboard-core-plugins/src/PandasPluginConfig.ts index b2c9ace1e0..0902e2b689 100644 --- a/packages/dashboard-core-plugins/src/PandasPluginConfig.ts +++ b/packages/dashboard-core-plugins/src/PandasPluginConfig.ts @@ -1,14 +1,14 @@ import { PluginType, WidgetPlugin } from '@deephaven/plugin'; import { dhPandas } from '@deephaven/icons'; -import PandasPlugin from './PandasPlugin'; +import { PandasWidgetPlugin } from './PandasWidgetPlugin'; +import { PandasPanelPlugin } from './PandasPanelPlugin'; const PandasPluginConfig: WidgetPlugin = { name: 'PandasPanel', title: 'Pandas', type: PluginType.WIDGET_PLUGIN, - // TODO: #1573 Replace with actual base component and not just the panel plugin - component: PandasPlugin, - panelComponent: PandasPlugin, + component: PandasWidgetPlugin, + panelComponent: PandasPanelPlugin, supportedTypes: 'pandas.DataFrame', icon: dhPandas, }; diff --git a/packages/dashboard-core-plugins/src/PandasWidgetPlugin.tsx b/packages/dashboard-core-plugins/src/PandasWidgetPlugin.tsx new file mode 100644 index 0000000000..265a1b6dc2 --- /dev/null +++ b/packages/dashboard-core-plugins/src/PandasWidgetPlugin.tsx @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useState } from 'react'; +import { WidgetComponentProps } from '@deephaven/plugin'; +import { type Table } from '@deephaven/jsapi-types'; +import IrisGrid, { + IrisGridModelFactory, + type IrisGridModel, +} from '@deephaven/iris-grid'; +import { useApi } from '@deephaven/jsapi-bootstrap'; +import { LoadingOverlay } from '@deephaven/components'; +import { PandasReloadButton } from './panels/PandasReloadButton'; + +export function PandasWidgetPlugin( + props: WidgetComponentProps +): JSX.Element | null { + const dh = useApi(); + const [model, setModel] = useState(); + const [isLoading, setIsLoading] = useState(true); + const [isLoaded, setIsLoaded] = useState(false); + + const { fetch } = props; + + const makeModel = useCallback(async () => { + const table = (await fetch()) as unknown as Table; + return IrisGridModelFactory.makeModel(dh, table); + }, [dh, fetch]); + + const handleReload = useCallback(async () => { + setIsLoading(true); + const newModel = await makeModel(); + setModel(newModel); + setIsLoading(false); + }, [makeModel]); + + useEffect(() => { + let cancelled = false; + async function init() { + const newModel = await makeModel(); + if (!cancelled) { + setModel(newModel); + setIsLoaded(true); + setIsLoading(false); + } + } + + init(); + setIsLoading(true); + + return () => { + cancelled = true; + }; + }, [makeModel]); + + return ( + <> + + {model && ( + + + + )} + + ); +} + +export default PandasWidgetPlugin; diff --git a/packages/dashboard-core-plugins/src/index.ts b/packages/dashboard-core-plugins/src/index.ts index 7c405a4a77..476e909c01 100644 --- a/packages/dashboard-core-plugins/src/index.ts +++ b/packages/dashboard-core-plugins/src/index.ts @@ -1,17 +1,20 @@ -export { default as ChartPlugin } from './ChartPlugin'; +export { default as ChartPanelPlugin } from './ChartPanelPlugin'; +export { default as ChartWidgetPlugin } from './ChartWidgetPlugin'; export { default as ChartPluginConfig } from './ChartPluginConfig'; export { default as ChartBuilderPlugin } from './ChartBuilderPlugin'; export { default as ChartBuilderPluginConfig } from './ChartBuilderPluginConfig'; export { default as ConsolePlugin } from './ConsolePlugin'; export { default as FilterPlugin } from './FilterPlugin'; export { default as FilterPluginConfig } from './FilterPluginConfig'; -export { default as GridPlugin } from './GridPlugin'; +export { default as GridPanelPlugin } from './GridPanelPlugin'; +export { default as GridWidgetPlugin } from './GridWidgetPlugin'; export { default as GridPluginConfig } from './GridPluginConfig'; export { default as LinkerPlugin } from './LinkerPlugin'; export { default as LinkerPluginConfig } from './LinkerPluginConfig'; export { default as MarkdownPlugin } from './MarkdownPlugin'; export { default as MarkdownPluginConfig } from './MarkdownPluginConfig'; -export { default as PandasPlugin } from './PandasPlugin'; +export { default as PandasPanelPlugin } from './PandasPanelPlugin'; +export { default as PandasWidgetPlugin } from './PandasWidgetPlugin'; export { default as PandasPluginConfig } from './PandasPluginConfig'; export { default as WidgetLoaderPlugin } from './WidgetLoaderPlugin'; export { default as WidgetLoaderPluginConfig } from './WidgetLoaderPluginConfig'; diff --git a/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx b/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx index 2dc94c2298..a1ba969fe3 100644 --- a/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx @@ -128,7 +128,7 @@ interface OwnProps extends DashboardPanelProps { makeModel: () => Promise; localDashboardId: string; Plotly?: typeof PlotlyType; - /** The panel container div */ + /** The plot container div */ containerRef?: RefObject; panelState?: GLChartPanelState; @@ -236,7 +236,7 @@ export class ChartPanel extends Component { ); this.handleClearAllFilters = this.handleClearAllFilters.bind(this); - this.panelContainer = props.containerRef ?? React.createRef(); + this.panelContainer = React.createRef(); this.pending = new Pending(); const { metadata, panelState } = props; @@ -1035,6 +1035,7 @@ export class ChartPanel extends Component { metadata, settings, Plotly, + containerRef, } = this.props; const { columnMap, @@ -1117,6 +1118,7 @@ export class ChartPanel extends Component { onError={this.handleError} onSettingsChanged={this.handleSettingsChanged} Plotly={Plotly} + containerRef={containerRef} /> )} diff --git a/packages/dashboard-core-plugins/src/panels/PandasPanel.tsx b/packages/dashboard-core-plugins/src/panels/PandasPanel.tsx index 2c9388fbca..9ca0cd34a3 100644 --- a/packages/dashboard-core-plugins/src/panels/PandasPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/PandasPanel.tsx @@ -1,14 +1,12 @@ /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable react/no-unused-state */ import React, { Component, ReactElement, RefObject } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { dhRefresh } from '@deephaven/icons'; -import { Button } from '@deephaven/components'; import ConnectedIrisGridPanel, { type IrisGridPanel, type OwnProps as IrisGridPanelOwnProps, type PanelState, } from './IrisGridPanel'; +import { PandasReloadButton } from './PandasReloadButton'; import './PandasPanel.scss'; export interface PandasPanelProps extends IrisGridPanelOwnProps { @@ -34,7 +32,6 @@ class PandasPanel extends Component { super(props); this.irisGridRef = React.createRef(); - this.buttonRef = React.createRef(); this.handleReload = this.handleReload.bind(this); this.handleGridStateChange = this.handleGridStateChange.bind(this); @@ -47,13 +44,10 @@ class PandasPanel extends Component { }; } - buttonRef: RefObject; - irisGridRef: RefObject; handleReload(): void { this.irisGridRef.current?.initModel(); - this.buttonRef.current?.blur(); this.setState({ shouldFocusGrid: true, }); @@ -76,32 +70,14 @@ class PandasPanel extends Component { } render(): ReactElement { - const { ...props } = this.props; - return ( - + ); } diff --git a/packages/dashboard-core-plugins/src/panels/PandasReloadButton.tsx b/packages/dashboard-core-plugins/src/panels/PandasReloadButton.tsx new file mode 100644 index 0000000000..cb993a515d --- /dev/null +++ b/packages/dashboard-core-plugins/src/panels/PandasReloadButton.tsx @@ -0,0 +1,42 @@ +import React, { useCallback, useRef } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { dhRefresh } from '@deephaven/icons'; +import { Button } from '@deephaven/components'; + +export function PandasReloadButton({ + onClick, +}: { + onClick: React.MouseEventHandler; +}): JSX.Element { + const buttonRef = useRef(null); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + buttonRef.current?.blur(); + onClick(e); + }, + [onClick] + ); + + return ( + + ); +} + +export default PandasReloadButton; diff --git a/packages/plugin/src/PluginTypes.ts b/packages/plugin/src/PluginTypes.ts index eafcf755ed..27cb33861a 100644 --- a/packages/plugin/src/PluginTypes.ts +++ b/packages/plugin/src/PluginTypes.ts @@ -106,6 +106,9 @@ export function isDashboardPlugin( export interface WidgetComponentProps { fetch: () => Promise; +} + +export interface WidgetPanelProps extends WidgetComponentProps { metadata?: { id?: string; name?: string; @@ -150,8 +153,7 @@ export interface WidgetPlugin extends Plugin { * * See @deephaven/dashboard-core-plugins WidgetPanel for the component that should be used here. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - panelComponent?: React.ComponentType; + panelComponent?: React.ComponentType; /** * The icon to display next to the console button.