Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
{
files: ['**/*.@(ts|tsx)'],
parserOptions: {
project: ['./tsconfig.json'],
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
},
},
Expand Down
1 change: 1 addition & 0 deletions jest.config.lint.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
maxWorkers: '50%',
watchPlugins: ['jest-runner-eslint/watch-fix'],
projects: [
{
Expand Down
727 changes: 404 additions & 323 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions plugins/ui/src/deephaven/ui/components/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
from ..elements import DashboardElement, FunctionElement


def dashboard(element: FunctionElement) -> DashboardElement:
def dashboard(
element: FunctionElement,
*,
show_headers: bool = True,
) -> DashboardElement:
"""
A dashboard is the container for an entire layout.

Args:
element: Element to render as the dashboard.
The element should render a layout that contains 1 root column or row.
show_headers: Whether to show headers on the dashboard panels. Defaults to True.

Returns:
The rendered dashboard.
"""
return DashboardElement(element)
return DashboardElement(element, show_headers=show_headers)
13 changes: 11 additions & 2 deletions plugins/ui/src/deephaven/ui/elements/DashboardElement.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,14 @@


class DashboardElement(BaseElement):
def __init__(self, element: FunctionElement):
super().__init__("deephaven.ui.components.Dashboard", element)
def __init__(
self,
element: FunctionElement,
*,
show_headers: bool = True,
):
super().__init__(
"deephaven.ui.components.Dashboard",
element,
show_headers=show_headers,
)
4 changes: 4 additions & 0 deletions plugins/ui/src/js/__mocks__/@deephaven/plugin.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Mock @deephaven/plugin package
const React = require('react');
const PluginActual = jest.requireActual('@deephaven/plugin');

module.exports = {
...PluginActual,
useDashboardPlugins: jest.fn(() => []),
// Mock usePersistentState to behave like useState.
// The real implementation requires FiberProvider which is internal to Dashboard.
usePersistentState: (initialState, _config) => React.useState(initialState),
__esModule: true,
};
10 changes: 10 additions & 0 deletions plugins/ui/src/js/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
const path = require('path');
const baseConfig = require('../../../../jest.config.base.cjs');
const packageJson = require('./package');

module.exports = {
...baseConfig,
displayName: packageJson.name,
moduleNameMapper: {
...baseConfig.moduleNameMapper,
// Force all react imports to the local React 18 to avoid dual-instance issues
// with the root workspace's React 17.
'^react$': path.resolve(__dirname, 'node_modules/react'),
'^react/(.*)$': path.resolve(__dirname, 'node_modules/react/$1'),
'^react-dom$': path.resolve(__dirname, 'node_modules/react-dom'),
'^react-dom/(.*)$': path.resolve(__dirname, 'node_modules/react-dom/$1'),
},
};
43 changes: 21 additions & 22 deletions plugins/ui/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,34 @@
"update-dh-packages": "node ../../../../tools/update-dh-packages.mjs"
},
"devDependencies": {
"@deephaven/test-utils": "^1.8.0",
"@types/memoizee": "^0.4.5",
"@types/react": "^17.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"@types/react": "^18.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"dependencies": {
"@deephaven/chart": "^1.5.1",
"@deephaven/components": "^1.5.1",
"@deephaven/console": "^1.5.1",
"@deephaven/dashboard": "^1.5.1",
"@deephaven/dashboard-core-plugins": "^1.5.1",
"@deephaven/golden-layout": "^1.5.1",
"@deephaven/grid": "^1.3.0",
"@deephaven/components": "^1.17.0",
"@deephaven/console": "^1.17.0",
"@deephaven/dashboard": "^1.17.1",
"@deephaven/dashboard-core-plugins": "^1.17.1",
"@deephaven/golden-layout": "^1.17.1",
"@deephaven/grid": "^1.17.1",
"@deephaven/icons": "^1.2.0",
"@deephaven/iris-grid": "^1.5.1",
"@deephaven/jsapi-bootstrap": "^1.5.1",
"@deephaven/jsapi-components": "^1.5.1",
"@deephaven/iris-grid": "^1.17.1",
"@deephaven/jsapi-bootstrap": "^1.17.0",
"@deephaven/jsapi-components": "^1.17.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.39.6",
"@deephaven/jsapi-utils": "^1.4.0",
"@deephaven/log": "^1.1.0",
"@deephaven/plugin": "^1.5.1",
"@deephaven/react-hooks": "^1.2.0",
"@deephaven/redux": "^1.5.1",
"@deephaven/test-utils": "^1.1.0",
"@deephaven/utils": "^1.1.0",
"@deephaven/jsapi-utils": "^1.16.0",
"@deephaven/log": "^1.8.0",
"@deephaven/plugin": "^1.17.1",
"@deephaven/react-hooks": "^1.14.0",
"@deephaven/redux": "^1.17.0",
"@deephaven/utils": "^1.10.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@internationalized/date": "^3.5.5",
"classnames": "^2.5.1",
Expand Down
87 changes: 14 additions & 73 deletions plugins/ui/src/js/src/DashboardPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { nanoid } from 'nanoid';
import {
DashboardPluginComponentProps,
LayoutManagerContext,
LayoutUtils,
PanelEvent,
useListener,
useDashboardPluginData,
emitCreateDashboard,
WidgetDescriptor,
PanelOpenEventDetail,
DEFAULT_DASHBOARD_ID,
useDashboardPanel,
} from '@deephaven/dashboard';
import Log from '@deephaven/log';
import { DeferredApiBootstrap } from '@deephaven/jsapi-bootstrap';
import { dh } from '@deephaven/jsapi-types';
import { ErrorBoundary } from '@deephaven/components';
import { useDebouncedCallback } from '@deephaven/react-hooks';
import styles from './styles.scss?inline';
import {
ReadonlyWidgetData,
WidgetDataUpdate,
Expand All @@ -27,11 +22,6 @@ import {
import PortalPanel from './layout/PortalPanel';
import PortalPanelManager from './layout/PortalPanelManager';
import DashboardWidgetHandler from './widget/DashboardWidgetHandler';
import {
getPreservedData,
DASHBOARD_ELEMENT,
WIDGET_ELEMENT,
} from './widget/WidgetUtils';
import { usePanelId } from './layout/ReactPanelContext';

const PLUGIN_NAME = '@deephaven/js-plugin-ui.DashboardPlugin';
Expand Down Expand Up @@ -63,6 +53,13 @@ interface WidgetWrapper {
data?: ReadonlyWidgetData;
}

/**
* Handle legacy behaviour of an open widget being saved with the dashboard.
*
* Now UIWidgetPlugin is responsible for opening widgets in the dashboard.
* @param props Dashboard plugin props
* @returns Dashboard plugin content, which is responsible for handling legacy behaviour of an open widget being saved with the dashboard
*/
function InnerDashboardPlugin(
props: DashboardPluginComponentProps
): JSX.Element | null {
Expand All @@ -78,66 +75,6 @@ function InnerDashboardPlugin(
ReadonlyMap<WidgetId, WidgetWrapper>
>(new Map());

const handleWidgetOpen = useCallback(
({ widgetId, widget }: { widgetId: string; widget: WidgetDescriptor }) => {
log.debug('Opening widget with ID', widgetId, widget);
setWidgetMap(prevWidgetMap => {
const newWidgetMap = new Map(prevWidgetMap);
const oldWidget = newWidgetMap.get(widgetId);
newWidgetMap.set(widgetId, {
id: widgetId,
widget,
data: getPreservedData(oldWidget?.data),
});
return newWidgetMap;
});
},
[]
);

const handleDashboardOpen = useCallback(
({
widget,
dashboardId,
}: {
widget: WidgetDescriptor;
dashboardId: string;
}) => {
const { name: title } = widget;
log.debug('Emitting create dashboard event for', dashboardId, widget);
emitCreateDashboard(layout.eventHub, {
pluginId: PLUGIN_NAME,
title: title ?? 'Untitled',
data: { openWidgets: { [dashboardId]: { descriptor: widget } } },
});
},
[layout.eventHub]
);

const handlePanelOpen = useCallback(
({
panelId: widgetId = nanoid(),
widget,
}: PanelOpenEventDetail<dh.Widget>) => {
const { type } = widget;

switch (type) {
case WIDGET_ELEMENT: {
handleWidgetOpen({ widgetId, widget });
break;
}
case DASHBOARD_ELEMENT: {
handleDashboardOpen({ widget, dashboardId: widgetId });
break;
}
default: {
break;
}
}
},
[handleDashboardOpen, handleWidgetOpen]
);

useEffect(
function loadInitialPluginData() {
if (initialPluginData == null) {
Expand Down Expand Up @@ -203,8 +140,6 @@ function InnerDashboardPlugin(
});
}, []);

// TODO: We need to change up the event system for how objects are opened, since in this case it could be opening multiple panels
useListener(layout.eventHub, PanelEvent.OPEN, handlePanelOpen);
useListener(layout.eventHub, PanelEvent.CLOSE, handlePanelClose);

const sendPluginDataUpdate = useCallback(
Expand Down Expand Up @@ -282,12 +217,18 @@ function InnerDashboardPlugin(

return (
<LayoutManagerContext.Provider value={layout}>
<style>{styles}</style>
<PortalPanelManager>{widgetHandlers}</PortalPanelManager>
</LayoutManagerContext.Provider>
);
}

/**
* Dashboard plugin that registers the PortalPanel type for deephaven.ui
*
* It's also responsible for handling legacy behaviour, for old dashboards that may have opened a deephaven.ui widget previously.
* @param props Dashboard plugin props
* @returns Dashboard plugin
*/
export function DashboardPlugin(
props: DashboardPluginComponentProps
): JSX.Element | null {
Expand Down
50 changes: 50 additions & 0 deletions plugins/ui/src/js/src/UIComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useCallback, useMemo } from 'react';
import { UriVariableDescriptor } from '@deephaven/jsapi-bootstrap';
import type { dh } from '@deephaven/jsapi-types';
import {
usePersistentState,
type WidgetComponentProps,
} from '@deephaven/plugin';
import { nanoid } from 'nanoid';
import { WidgetData, WidgetDataUpdate } from './widget/WidgetTypes';
import WidgetHandler from './widget/WidgetHandler';

type UIComponentProps = WidgetComponentProps<dh.Widget> & {
// Might be loading a URI resolved widget...
uri?: UriVariableDescriptor;
};

export function UIComponent(props: UIComponentProps): JSX.Element | null {
const { metadata: widgetDescriptor, uri, __dhId } = props;

const [widgetData, setWidgetData] = usePersistentState<
WidgetData | undefined
>(undefined, { type: 'UIComponentWidgetData', version: 1 });

const id = useMemo(
() => __dhId ?? widgetDescriptor?.id ?? nanoid(),
[__dhId, widgetDescriptor]
);

const handleDataChange = useCallback(
(data: WidgetDataUpdate) => {
setWidgetData(oldData => ({ ...oldData, ...data }));
},
[setWidgetData]
);

const descriptor = uri ?? widgetDescriptor;
if (descriptor == null) {
throw new Error('No widget descriptor');
}
return (
<WidgetHandler
widgetDescriptor={descriptor}
initialData={widgetData}
onDataChange={handleDataChange}
id={id}
/>
);
}

export default UIComponent;
18 changes: 18 additions & 0 deletions plugins/ui/src/js/src/UIWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { dh } from '@deephaven/jsapi-types';
import { type WidgetComponentProps } from '@deephaven/plugin';
import UIComponent from './UIComponent';
import PortalPanel from './layout/PortalPanel';

type UIWidgetProps = WidgetComponentProps<dh.Widget>;

export function UIWidget(props: UIWidgetProps): JSX.Element | null {
const { metadata: widgetDescriptor } = props;
if (widgetDescriptor?.type === PortalPanel.displayName) {
return null;
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <UIComponent {...props} />;
}

export default UIWidget;
16 changes: 16 additions & 0 deletions plugins/ui/src/js/src/UIWidgetPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type WidgetPlugin, PluginType } from '@deephaven/plugin';
import { vsGraph } from '@deephaven/icons';
import type { dh } from '@deephaven/jsapi-types';
import { DASHBOARD_ELEMENT, WIDGET_ELEMENT } from './widget/WidgetUtils';
import PortalPanel from './layout/PortalPanel';
import UIWidget from './UIWidget';

export const UIWidgetPlugin: WidgetPlugin<dh.Widget> = {
name: '@deephaven/js-plugin-ui',
type: PluginType.WIDGET_PLUGIN,
supportedTypes: [WIDGET_ELEMENT, DASHBOARD_ELEMENT, PortalPanel.displayName],
component: UIWidget,
icon: vsGraph,
};

export default UIWidgetPlugin;
Loading
Loading