Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 13 additions & 64 deletions plugins/pivot/src/js/src/DashboardPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,24 @@
import React, { type DragEvent, useCallback, useEffect } from 'react';
import { nanoid } from 'nanoid';
import React from 'react';
import {
type DashboardPluginComponentProps,
LayoutUtils,
useListener,
useDashboardPanel,
} from '@deephaven/dashboard';
import type { dh } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';
import { assertNotNull } from '@deephaven/utils';
import PivotPanel from './PivotPanel';

const VARIABLE_TYPE = 'PivotTable';

const log = Log.module('@deephaven/js-plugin-pivot/DashboardPlugin');

export function DashboardPlugin({
id,
layout,
registerComponent,
}: DashboardPluginComponentProps): React.ReactNode {
const handlePanelOpen = useCallback(
({
dragEvent,
fetch,
metadata = {},
panelId = nanoid(),
widget,
}: {
dragEvent?: DragEvent;
fetch: () => Promise<dh.Widget>;
metadata?: Record<string, unknown>;
panelId?: string;
widget: dh.ide.VariableDescriptor;
}) => {
const { name, type } = widget;
if (type !== VARIABLE_TYPE) {
// Ignore unsupported panel types
return;
}
log.info('Panel opened of type', type);
const config = {
type: 'react-component' as const,
component: PivotPanel.displayName,
props: {
localDashboardId: id,
id: panelId,
metadata: {
...metadata,
...widget,
},
fetch,
},
title: name ?? undefined,
id: panelId,
};

const { root } = layout;
LayoutUtils.openComponent({ root, config, dragEvent });
},
[id, layout]
);

useEffect(() => {
assertNotNull(PivotPanel.displayName);
const cleanups = [registerComponent(PivotPanel.displayName, PivotPanel)];
return () => {
cleanups.forEach(cleanup => cleanup());
};
}, [registerComponent]);

useListener(layout.eventHub, 'PanelEvent.OPEN', handlePanelOpen);
export function DashboardPlugin(
dashboardProps: DashboardPluginComponentProps
): React.ReactNode {
assertNotNull(PivotPanel.displayName);

useDashboardPanel({
dashboardProps,
componentName: PivotPanel.displayName,
supportedTypes: VARIABLE_TYPE,
component: PivotPanel,
});

return null;
}
Expand Down
45 changes: 33 additions & 12 deletions plugins/pivot/src/js/src/PivotPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import { forwardRef } from 'react';
import { WidgetPanelProps } from '@deephaven/plugin';
import { type dh } from '@deephaven/jsapi-types';
import { IrisGridPanel } from '@deephaven/dashboard-core-plugins';
import { LoadingOverlay } from '@deephaven/components';
import {
IrisGridPanel,
type IrisGridPanelProps,
} from '@deephaven/dashboard-core-plugins';
import { getErrorMessage } from '@deephaven/utils';
import type { DashboardPanelProps } from '@deephaven/dashboard';
import type { WidgetPanelProps } from '@deephaven/plugin';
import useHydratePivotGrid from './useHydratePivotGrid';

// Unconnected IrisGridPanel type is not exported from dashboard-core-plugins
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const PivotPanel = forwardRef<any, WidgetPanelProps<dh.Widget>>(
export const PivotPanel = forwardRef(
// Unconnected IrisGridPanel type is not exported from dashboard-core-plugins
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(props: WidgetPanelProps<dh.Widget>, ref: React.Ref<any>): JSX.Element => {
const { localDashboardId, fetch, metadata } = props;
(panelProps: WidgetPanelProps<dh.Widget>, ref: React.Ref<any>) => {
const { localDashboardId, metadata, panelState, ...props } =
panelProps as DashboardPanelProps & {
metadata?: dh.ide.VariableDescriptor;
panelState?: IrisGridPanelProps['panelState'];
};

const hydratedProps = useHydratePivotGrid(
fetch,
localDashboardId,
metadata
);
const hydrateResult = useHydratePivotGrid(localDashboardId, metadata);

if (hydrateResult.status === 'loading') {
return <LoadingOverlay isLoading />;
}

if (hydrateResult.status === 'error') {
return (
<LoadingOverlay
errorMessage={getErrorMessage(hydrateResult.error)}
isLoading={false}
/>
);
}

const { props: hydratedProps } = hydrateResult;

return (
<IrisGridPanel
Expand All @@ -24,6 +44,7 @@ export const PivotPanel = forwardRef<any, WidgetPanelProps<dh.Widget>>(
{...props}
// eslint-disable-next-line react/jsx-props-no-spreading
{...hydratedProps}
panelState={panelState}
/>
);
}
Expand Down
54 changes: 13 additions & 41 deletions plugins/pivot/src/js/src/PivotWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,25 @@
import { useCallback, useMemo } from 'react';
import { type WidgetComponentProps } from '@deephaven/plugin';
import { type dh as DhType } from '@deephaven/jsapi-types';
import { IrisGrid, type MouseHandlersProp } from '@deephaven/iris-grid';
import { useApi } from '@deephaven/jsapi-bootstrap';
import { LoadingOverlay, useTheme } from '@deephaven/components';
import { IrisGrid } from '@deephaven/iris-grid';
import { LoadingOverlay } from '@deephaven/components';
import { getErrorMessage } from '@deephaven/utils';
import Log from '@deephaven/log';
import { useIrisGridPivotModel } from './useIrisGridPivotModel';
import PivotColumnGroupMouseHandler from './PivotColumnGroupMouseHandler';
import { isCorePlusDh } from './PivotUtils';
import IrisGridPivotRenderer from './IrisGridPivotRenderer';
import { getIrisGridPivotTheme } from './IrisGridPivotTheme';

const log = Log.module('@deephaven/js-plugin-pivot/PivotWidget');
import {
usePivotTableFetch,
usePivotMouseHandlers,
usePivotRenderer,
usePivotTheme,
} from './usePivotTableUtils';

export function PivotWidget({
fetch,
}: WidgetComponentProps<DhType.Widget>): JSX.Element | null {
const dh = useApi();

const mouseHandlers: MouseHandlersProp = useMemo(
() => [irisGrid => new PivotColumnGroupMouseHandler(irisGrid)],
[]
);

const renderer = useMemo(() => new IrisGridPivotRenderer(), []);

const theme = useTheme();

const pivotTheme = useMemo(() => {
log.debug('Theme changed, updating pivot theme', theme);
return getIrisGridPivotTheme();
}, [theme]);

const pivotTableFetch = useCallback(
() =>
fetch().then(result => {
log.debug('pivotWidget fetch result:', result);
if (!isCorePlusDh(dh)) {
throw new Error('CorePlus is not available');
}
const pivot = new dh.coreplus.pivot.PivotTable(result);
log.debug('Created pivot table:', pivot);
return pivot;
}),
[dh, fetch]
);
const pivotFetch = usePivotTableFetch(fetch);
const mouseHandlers = usePivotMouseHandlers();
const renderer = usePivotRenderer();
const pivotTheme = usePivotTheme();

const fetchResult = useIrisGridPivotModel(pivotTableFetch);
const fetchResult = useIrisGridPivotModel(pivotFetch);

if (fetchResult.status === 'loading') {
return <LoadingOverlay isLoading />;
Expand Down
4 changes: 2 additions & 2 deletions plugins/pivot/src/js/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { IrisGridPanel } from '@deephaven/dashboard-core-plugins';
import useHydratePivotGrid from './useHydratePivotGrid';

function PivotPanel(props: WidgetPanelProps<dh.Widget>): JSX.Element {
const { localDashboardId, fetch, metadata } = props;
const { localDashboardId, metadata } = props;

// Provides makeModel, custom renderer, theme, and other props needed to render a Pivot in IrisGridPanel
const hydratedProps = useHydratePivotGrid(fetch, localDashboardId, metadata);
const hydratedProps = useHydratePivotGrid(localDashboardId, metadata);

return (
<IrisGridPanel
Expand Down
129 changes: 71 additions & 58 deletions plugins/pivot/src/js/src/useHydratePivotGrid.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,107 @@
import { useCallback, useMemo } from 'react';
import { useApi } from '@deephaven/jsapi-bootstrap';
import { useApi, useObjectFetch } from '@deephaven/jsapi-bootstrap';
import type { dh } from '@deephaven-enterprise/jsapi-coreplus-types';
import {
useLoadTablePlugin,
type IrisGridPanelProps,
} from '@deephaven/dashboard-core-plugins';
import type { MouseHandlersProp } from '@deephaven/iris-grid';
import { useTheme } from '@deephaven/components';
import { assertNotNull } from '@deephaven/utils';
import Log from '@deephaven/log';
import IrisGridPivotModel from './IrisGridPivotModel';
import { isCorePlusDh } from './PivotUtils';
import PivotColumnGroupMouseHandler from './PivotColumnGroupMouseHandler';
import { IrisGridPivotRenderer } from './IrisGridPivotRenderer';
import { getIrisGridPivotTheme } from './IrisGridPivotTheme';
import PivotSortMouseHandler from './PivotSortMouseHandler';
import {
usePivotMouseHandlers,
usePivotRenderer,
usePivotTheme,
} from './usePivotTableUtils';

const log = Log.module('@deephaven/js-plugin-pivot/useHydratePivotGrid');

export interface HydratePivotGridResultLoading {
status: 'loading';
}

export interface HydratePivotGridResultError {
status: 'error';
error: NonNullable<unknown>;
}

export interface HydratePivotGridResultSuccess {
props: { localDashboardId: string } & Pick<
IrisGridPanelProps,
| 'loadPlugin'
| 'makeModel'
| 'metadata'
| 'mouseHandlers'
| 'renderer'
| 'theme'
>;
status: 'success';
}

export type HydratePivotGridResult =
| HydratePivotGridResultLoading
| HydratePivotGridResultError
| HydratePivotGridResultSuccess;

/**
* Hydrate the props for a Pivot grid panel
* @param fetch Function to fetch the Widget
* @param id ID of the dashboard
* @param metadata Optional serializable metadata for re-fetching the table later
* @returns Props hydrated for a Pivot grid panel
*/
export function useHydratePivotGrid(
fetch: () => Promise<dh.Widget>,
id: string,
metadata: dh.ide.VariableDescriptor | undefined
): { localDashboardId: string } & Pick<
IrisGridPanelProps,
'loadPlugin' | 'makeModel'
> {
): HydratePivotGridResult {
assertNotNull(metadata, 'Missing Pivot metadata');

// Manage loading and error states
const objectFetch = useObjectFetch<dh.Widget>(metadata);
const api = useApi();
const loadPlugin = useLoadTablePlugin();
const mouseHandlers = usePivotMouseHandlers();
const renderer = usePivotRenderer();
const theme = usePivotTheme();

const fetchTable = useCallback(
() =>
fetch().then(result => {
log.debug('Pivot fetch result:', result);
if (!isCorePlusDh(api)) {
throw new Error('CorePlus is not available');
}
const pivot = new api.coreplus.pivot.PivotTable(result);
log.debug('Created pivot table:', pivot);
return pivot;
}),
[api, fetch]
);
const { status } = objectFetch;

const mouseHandlers: MouseHandlersProp = useMemo(
() => [
irisGrid => new PivotColumnGroupMouseHandler(irisGrid),
irisGrid => new PivotSortMouseHandler(irisGrid),
],
[]
);
if (status === 'loading') {
log.debug('Widget is loading');
return { status: 'loading' };
}

const renderer = useMemo(() => new IrisGridPivotRenderer(), []);
if (status === 'error') {
log.debug('Error fetching widget:', objectFetch.error);
return {
status: 'error',
error: objectFetch.error,
};
}

const theme = useTheme();
const { fetch } = objectFetch;

const pivotTheme = useMemo(() => {
log.debug('Theme changed, updating pivot theme', theme);
return getIrisGridPivotTheme();
}, [theme]);

const hydratedProps = useMemo(
() => ({
return {
status: 'success',
props: {
loadPlugin,
localDashboardId: id,
makeModel: async () => {
const pivotWidget = await fetchTable();
return new IrisGridPivotModel(api, pivotWidget);
makeModel: async function makeModel() {
log.debug('Fetching pivot widget');
const widget = await fetch();
log.debug('Pivot fetch result:', widget);
if (!isCorePlusDh(api)) {
throw new Error('CorePlus is not available');
}
const pivotTable = new api.coreplus.pivot.PivotTable(widget);
log.debug('Created pivot table:', pivotTable);
return new IrisGridPivotModel(api, pivotTable);
},
metadata,
mouseHandlers,
renderer,
}),
[api, fetchTable, id, loadPlugin, metadata, mouseHandlers, renderer]
);

// Memoize the theme separately from the rest of the hydrated props
// so that the the theme changes don't cause the model to be recreated
const hydratedPropsWithTheme = useMemo(
() => ({ ...hydratedProps, theme: pivotTheme }),
[hydratedProps, pivotTheme]
);

return hydratedPropsWithTheme;
theme,
},
};
}

export default useHydratePivotGrid;
Loading
Loading