Skip to content

Commit

Permalink
WIP: UI dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
mattrunyon committed Dec 14, 2023
1 parent fc84ae7 commit 7ca6a34
Show file tree
Hide file tree
Showing 20 changed files with 499 additions and 70 deletions.
8 changes: 8 additions & 0 deletions plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from .panel import panel
from .spectrum import *
from .table import table
from .row import row
from .column import column
from .stack import stack
from .dashboard import dashboard
from . import html


Expand All @@ -13,9 +17,11 @@
"button",
"button_group",
"checkbox",
"column",
"component",
"content",
"contextual_help",
"dashboard",
"flex",
"form",
"fragment",
Expand All @@ -30,8 +36,10 @@
"object_view",
"panel",
"range_slider",
"row",
"slider",
"spectrum_element",
"stack",
"switch",
"table",
"tab_list",
Expand Down
18 changes: 18 additions & 0 deletions plugins/ui/src/deephaven/ui/components/column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from typing import Any
from ..elements import BaseElement


def column(*children: Any, width: float | None = None, **kwargs: Any):
"""
A column is a container that can be used to group elements.
Each element will be placed below its prior sibling.
Args:
children: Elements to render in the column.
width: The percent width of the column relative to other children of its parent. If not provided, the column will be sized automatically.
"""
return BaseElement(
"deephaven.ui.components.Column", *children, width=width, **kwargs
)
14 changes: 14 additions & 0 deletions plugins/ui/src/deephaven/ui/components/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from __future__ import annotations

from typing import Any
from ..elements import BaseElement


def dashboard(*children: Any, **kwargs: Any):
"""
A dashboard is the container for an entire layout.
Args:
children: Elements to render in the dashboard. Must have only 1 root element.
"""
return BaseElement("deephaven.ui.components.Dashboard", *children, **kwargs)
18 changes: 18 additions & 0 deletions plugins/ui/src/deephaven/ui/components/row.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from typing import Any
from ..elements import BaseElement


def row(*children: Any, height: float | None = None, **kwargs: Any):
"""
A row is a container that can be used to group elements.
Each element will be placed to the right of its prior sibling.
Args:
children: Elements to render in the row.
height: The percent height of the row relative to other children of its parent. If not provided, the row will be sized automatically.
"""
return BaseElement(
"deephaven.ui.components.Row", *children, height=height, **kwargs
)
30 changes: 30 additions & 0 deletions plugins/ui/src/deephaven/ui/components/stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from typing import Any
from ..elements import BaseElement


def stack(
*children: Any,
height: float | None = None,
width: float | None = None,
activeItemIndex: int | None = None,
**kwargs: Any,
):
"""
A stack is a container that can be used to group elements which creates a set of tabs.
Each element will get a tab and only one element can be visible at a time.
Args:
children: Elements to render in the row.
height: The percent height of the stack relative to other children of its parent. If not provided, the stack will be sized automatically.
width: The percent width of the stack relative to other children of its parent. If not provided, the stack will be sized automatically.
"""
return BaseElement(
"deephaven.ui.components.Stack",
*children,
height=height,
width=width,
activeItemIndex=activeItemIndex,
**kwargs,
)
2 changes: 1 addition & 1 deletion plugins/ui/src/js/src/DocumentHandler.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { WidgetDefinition } from '@deephaven/dashboard';
import { TestUtils } from '@deephaven/utils';
import { render } from '@testing-library/react';
import DocumentHandler, { DocumentHandlerProps } from './DocumentHandler';
import { PANEL_ELEMENT_NAME, ReactPanelProps } from './PanelUtils';
import { PANEL_ELEMENT_NAME, ReactPanelProps } from './layout/LayoutUtils';
import { MixedPanelsError, NoChildrenError } from './errors';
import { getComponentForElement } from './WidgetUtils';

Expand Down
22 changes: 11 additions & 11 deletions plugins/ui/src/js/src/DocumentUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ export function getRootChildren(
throw new MixedPanelsError('Cannot mix panel and non-panel elements');
}

if (childPanelCount === 0) {
// Just wrap it in a panel
return (
<ReactPanel
key="root"
title={definition.title ?? definition.id ?? definition.type}
>
{children}
</ReactPanel>
);
}
// if (childPanelCount === 0) {
// // Just wrap it in a panel
// return (
// <ReactPanel
// key="root"
// title={definition.title ?? definition.id ?? definition.type}
// >
// {children}
// </ReactPanel>
// );
// }

// It's already got panels defined, just return it
return children;
Expand Down
10 changes: 0 additions & 10 deletions plugins/ui/src/js/src/ElementUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { WidgetExportedObject } from '@deephaven/jsapi-types';
import { ReactNode } from 'react';

export const CALLABLE_KEY = '__dhCbid';
export const OBJECT_KEY = '__dhObid';
Expand Down Expand Up @@ -32,15 +31,6 @@ export type ElementNode<
props?: P;
};

export type ElementNodeWithChildren<
K extends string = string,
P extends Record<string, unknown> = Record<string, unknown>
> = ElementNode<K, P> & {
props: P & {
children: ReactNode;
};
};

export function isObjectNode(obj: unknown): obj is ObjectNode {
return obj != null && typeof obj === 'object' && OBJECT_KEY in obj;
}
Expand Down
11 changes: 0 additions & 11 deletions plugins/ui/src/js/src/PanelUtils.test.ts

This file was deleted.

29 changes: 0 additions & 29 deletions plugins/ui/src/js/src/PanelUtils.ts

This file was deleted.

2 changes: 1 addition & 1 deletion plugins/ui/src/js/src/ReactPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ReactPanelManager,
ReactPanelManagerContext,
} from './ReactPanelManager';
import { ReactPanelProps } from './PanelUtils';
import { ReactPanelProps } from './layout/LayoutUtils';

// Mock LayoutUtils, useListener, and PanelEvent from @deephaven/dashboard package
const mockLayout = { root: {}, eventHub: {} };
Expand Down
13 changes: 7 additions & 6 deletions plugins/ui/src/js/src/ReactPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import Log from '@deephaven/log';
import PortalPanel from './PortalPanel';
import { useReactPanelManager } from './ReactPanelManager';
import { ReactPanelProps } from './PanelUtils';
import { ReactPanelProps } from './layout/LayoutUtils';
import { useParentItem } from './layout/ParentItemContext';

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

Expand All @@ -25,19 +26,20 @@ function ReactPanel({ children, title }: ReactPanelProps) {
const [element, setElement] = useState<HTMLElement>();
const isPanelOpenRef = useRef(false);
const openedMetadataRef = useRef<Record<string, unknown>>();
const parent = useParentItem();

log.debug2('Rendering panel', panelId);

useEffect(
() => () => {
if (isPanelOpenRef.current) {
log.debug('Closing panel', panelId);
LayoutUtils.closeComponent(layoutManager.root, { id: panelId });
LayoutUtils.closeComponent(parent, { id: panelId });
isPanelOpenRef.current = false;
onClose(panelId);
}
},
[layoutManager, onClose, panelId]
[parent, onClose, panelId]
);

const handlePanelClosed = useCallback(
Expand Down Expand Up @@ -76,15 +78,14 @@ function ReactPanel({ children, title }: ReactPanelProps) {
id: panelId,
};

const { root } = layoutManager;
LayoutUtils.openComponent({ root, config });
LayoutUtils.openComponent({ root: parent, config });
log.debug('Opened panel', panelId, config);
isPanelOpenRef.current = true;
openedMetadataRef.current = metadata;

onOpen(panelId);
}
}, [layoutManager, metadata, onOpen, panelId, title]);
}, [parent, metadata, onOpen, panelId, title]);

return element ? ReactDOM.createPortal(children, element) : null;
}
Expand Down
24 changes: 23 additions & 1 deletion plugins/ui/src/js/src/WidgetUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@ import { isIconElementNode } from './IconElementUtils';
import IconElementView from './IconElementView';
import { isUITable } from './UITableUtils';
import UITable from './UITable';
import { isPanelElementNode } from './PanelUtils';
import {
isColumnElementNode,
isDashboardElementNode,
isPanelElementNode,
isRowElementNode,
isStackElementNode,
} from './layout/LayoutUtils';
import ReactPanel from './ReactPanel';
import ObjectView from './ObjectView';
import { isObjectElementNode } from './ObjectUtils';
import Row from './layout/Row';
import Stack from './layout/Stack';
import Column from './layout/Column';
import Dashboard from './layout/Dashboard';

export function getComponentForElement(element: ElementNode): React.ReactNode {
// Need to convert the children of the element if they are exported objects to an ObjectView
Expand Down Expand Up @@ -58,6 +68,18 @@ export function getComponentForElement(element: ElementNode): React.ReactNode {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{newElement.props?.children}</>;
}
if (isRowElementNode(newElement)) {
return <Row {...newElement.props} />;
}
if (isColumnElementNode(newElement)) {
return <Column {...newElement.props} />;
}
if (isStackElementNode(newElement)) {
return <Stack {...newElement.props} />;
}
if (isDashboardElementNode(newElement)) {
return <Dashboard {...newElement.props} />;
}

return newElement.props?.children;
}
38 changes: 38 additions & 0 deletions plugins/ui/src/js/src/layout/Column.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useEffect, useState } from 'react';
import { useLayoutManager } from '@deephaven/dashboard';
import type { ColumnElementProps } from './LayoutUtils';
import { ParentItemContext, useParentItem } from './ParentItemContext';

function Column({ children, width }: ColumnElementProps): JSX.Element | null {
const layoutManager = useLayoutManager();
const parent = useParentItem();
const [column] = useState(() => {
const newColumn = layoutManager.createContentItem(
{
type: 'column',
width,
},
parent
);

parent.addChild(newColumn, undefined, true);

return newColumn;
});

useEffect(() => {
column.setSize();

return () => {
column.remove();
};
}, [column]);

return (
<ParentItemContext.Provider value={column}>
{children}
</ParentItemContext.Provider>
);
}

export default Column;
27 changes: 27 additions & 0 deletions plugins/ui/src/js/src/layout/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useEffect, useState } from 'react';
import { useLayoutManager } from '@deephaven/dashboard';
import type { StackElementProps } from './LayoutUtils';
import { ParentItemContext } from './ParentItemContext';

function Dashboard({ children }: StackElementProps): JSX.Element | null {
const layoutManager = useLayoutManager();
const [dashboard, setDashboard] = useState(false);

useEffect(() => {
layoutManager.root.callDownwards('_$destroy', [], true, true);
layoutManager.root.contentItems = [];
setDashboard(true);
}, []);

if (!dashboard) {
return null;
}

return (
<ParentItemContext.Provider value={layoutManager.root}>
{children}
</ParentItemContext.Provider>
);
}

export default Dashboard;
Loading

0 comments on commit 7ca6a34

Please sign in to comment.