Skip to content

Commit

Permalink
Header context menus
Browse files Browse the repository at this point in the history
  • Loading branch information
mattrunyon committed Jun 7, 2024
1 parent fc9325c commit 664c72b
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 17 deletions.
56 changes: 44 additions & 12 deletions plugins/ui/src/js/src/elements/UITable.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import {
DehydratedQuickFilter,
IrisGrid,
IrisGridType,
type IrisGridContextMenuData,
IrisGridModel,
IrisGridModelFactory,
Expand All @@ -13,9 +20,10 @@ import { useApi } from '@deephaven/jsapi-bootstrap';
import type { dh } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';
import { getSettings, RootState } from '@deephaven/redux';
import { EMPTY_ARRAY } from '@deephaven/utils';
import { GridMouseHandler } from '@deephaven/grid';
import { UITableProps, wrapContextActions } from './UITableUtils';
import UITableMouseHandler from './UITableMouseHandler';
import UITableContextMenuHandler from './UITableContextMenuHandler';

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

Expand All @@ -33,13 +41,23 @@ function UITable({
showSearch: showSearchBar,
showQuickFilters,
contextItems,
contextColumnHeaderItems,
contextRowHeaderItems,
}: UITableProps): JSX.Element | null {
const dh = useApi();
const irisGridRef = useRef<IrisGridType>(null);
const [irisGrid, setIrisGrid] = useState<IrisGridType | null>(null);
const [model, setModel] = useState<IrisGridModel>();
const [columns, setColumns] = useState<dh.Table['columns']>();
const utils = useMemo(() => new IrisGridUtils(dh), [dh]);
const settings = useSelector(getSettings<RootState>);

useEffect(() => {
if (irisGridRef.current) {
setIrisGrid(irisGridRef.current);
}
}, []);

const hydratedSorts = useMemo(() => {
if (sorts !== undefined && columns !== undefined) {
log.debug('Hydrating sorts', sorts);
Expand Down Expand Up @@ -93,8 +111,8 @@ function UITable({

const mouseHandlers = useMemo(
() =>
model
? [
model && irisGrid
? ([
new UITableMouseHandler(
model,
onCellPress,
Expand All @@ -104,25 +122,35 @@ function UITable({
onRowPress,
onRowDoublePress
),
]
: EMPTY_ARRAY,
new UITableContextMenuHandler(
dh,
irisGrid,
model,
contextItems,
contextColumnHeaderItems,
contextRowHeaderItems
),
] as readonly GridMouseHandler[])
: undefined,
[
model,
dh,
irisGrid,
onCellPress,
onCellDoublePress,
onColumnPress,
onColumnDoublePress,
onRowPress,
onRowDoublePress,
contextItems,
contextColumnHeaderItems,
contextRowHeaderItems,
]
);

const onContextMenu = useCallback(
(data: IrisGridContextMenuData) =>
wrapContextActions(contextItems ?? [], data).map(item => ({
group: 999999, // Put it at the bottom of the list
...item,
})),
wrapContextActions(contextItems ?? [], data),
[contextItems]
);

Expand Down Expand Up @@ -155,8 +183,12 @@ function UITable({

return model ? (
<div className="ui-object-container">
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<IrisGrid model={model} {...irisGridProps} />
<IrisGrid
ref={ref => setIrisGrid(ref)}
model={model}
// eslint-disable-next-line react/jsx-props-no-spreading
{...irisGridProps}
/>
</div>
) : null;
}
Expand Down
75 changes: 75 additions & 0 deletions plugins/ui/src/js/src/elements/UITableContextMenuHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { GridPoint, ModelIndex } from '@deephaven/grid';
import type { ContextAction } from '@deephaven/components';
import {
IrisGridModel,
IrisGridType,
IrisGridContextMenuHandler,
} from '@deephaven/iris-grid';
import type { dh as DhType } from '@deephaven/jsapi-types';
import { UITableProps, wrapContextActions } from './UITableUtils';

/**
* Context menu handler for UITable.
*/
class UITableContextMenuHandler extends IrisGridContextMenuHandler {
order = 890;

private irisGrid: IrisGridType;

private model: IrisGridModel;

private contextMenuItems: UITableProps['contextItems'];

private contextColumnHeaderItems: UITableProps['contextColumnHeaderItems'];

private contextRowHeaderItems: UITableProps['contextRowHeaderItems'];

constructor(
dh: typeof DhType,
irisGrid: IrisGridType,
model: IrisGridModel,
contextMenuItems: UITableProps['contextItems'],
contextColumnHeaderItems: UITableProps['contextColumnHeaderItems'],
contextRowHeaderItems: UITableProps['contextRowHeaderItems']
) {
super(irisGrid, dh);
this.irisGrid = irisGrid;
this.model = model;
this.contextMenuItems = contextMenuItems;
this.contextColumnHeaderItems = contextColumnHeaderItems;
this.contextRowHeaderItems = contextRowHeaderItems;
}

getHeaderActions(
modelIndex: ModelIndex,
gridPoint: GridPoint
): ContextAction[] {
const { irisGrid, contextColumnHeaderItems, model } = this;

const { column: columnIndex } = gridPoint;
const modelColumn = irisGrid.getModelColumn(columnIndex);

if (!contextColumnHeaderItems || modelColumn == null) {
return super.getHeaderActions(modelIndex, gridPoint);
}

const { columns } = model;

const sourceCell = model.sourceForCell(modelColumn, 0);
const { column: sourceColumn } = sourceCell;
const column = columns[sourceColumn];

return [
...super.getHeaderActions(modelIndex, gridPoint),
...wrapContextActions(contextColumnHeaderItems, {
value: null,
valueText: null,
rowIndex: null,
columnIndex: sourceColumn,
column,
}),
];
}
}

export default UITableContextMenuHandler;
7 changes: 2 additions & 5 deletions plugins/ui/src/js/src/elements/UITableUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export type UIContextItem = Omit<ContextAction, 'action' | 'actions'> & {
action?: (params: {
value: unknown;
text_value: string | null;
row_index: number | null;
column_index: number | null;
column_name: string;
is_column_header: boolean;
is_row_header: boolean;
Expand Down Expand Up @@ -78,17 +76,16 @@ export function isUITable(obj: unknown): obj is UITableNode {
*/
export function wrapContextActions(
items: UIContextItem[],
data: IrisGridContextMenuData
data: Omit<IrisGridContextMenuData, 'model' | 'modelRow' | 'modelColumn'>
): ContextAction[] {
return items.map(item => ({
group: 999999, // Default to the end of the menu
...item,
action: item.action
? () => {
item.action?.({
value: data.value,
text_value: data.valueText,
row_index: data.rowIndex,
column_index: data.columnIndex,
column_name: data.column.name,
is_column_header: data.rowIndex == null,
is_row_header: data.columnIndex == null,
Expand Down

0 comments on commit 664c72b

Please sign in to comment.