diff --git a/desktop/flipper-plugin/src/ui/DataList.tsx b/desktop/flipper-plugin/src/ui/DataList.tsx index 8db683eba..b8322eb28 100644 --- a/desktop/flipper-plugin/src/ui/DataList.tsx +++ b/desktop/flipper-plugin/src/ui/DataList.tsx @@ -169,6 +169,8 @@ DataList.defaultProps = { enableSearchbar: false, enableColumnHeaders: false, enableArrow: true, + enableContextMenu: false, + enableMultiSelect: false, }; const DataListItem = memo( diff --git a/desktop/flipper-plugin/src/ui/data-inspector/DataInspectorNode.tsx b/desktop/flipper-plugin/src/ui/data-inspector/DataInspectorNode.tsx index f2671a70b..cc361faed 100644 --- a/desktop/flipper-plugin/src/ui/data-inspector/DataInspectorNode.tsx +++ b/desktop/flipper-plugin/src/ui/data-inspector/DataInspectorNode.tsx @@ -32,7 +32,7 @@ export {DataValueExtractor} from './DataPreview'; export const RootDataContext = createContext<() => any>(() => ({})); -const contextMenuTrigger = ['contextMenu' as const]; +export const contextMenuTrigger = ['contextMenu' as const]; const BaseContainer = styled.div<{depth?: number; disabled?: boolean}>( (props) => ({ diff --git a/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx b/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx index eaf641cf4..f801866da 100644 --- a/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx @@ -59,6 +59,7 @@ interface DataTableBaseProps { enableAutoScroll?: boolean; enableColumnHeaders?: boolean; enableMultiSelect?: boolean; + enableContextMenu?: boolean; // if set (the default) will grow and become scrollable. Otherwise will use natural size scrollable?: boolean; extraActions?: React.ReactElement; @@ -119,6 +120,7 @@ export interface TableRowRenderContext { itemId: number, ): void; onRowStyle?(item: T): React.CSSProperties | undefined; + onContextMenu?(): React.ReactElement; } export type DataTableProps = DataTableInput & DataTableBaseProps; @@ -185,7 +187,10 @@ export function DataTable( }, onMouseDown(e, _item, index) { if (!dragging.current) { - if (e.ctrlKey || e.metaKey) { + if (e.buttons > 1) { + // for right click we only want to add if needed, not deselect + tableManager.addRangeToSelection(index, index, false); + } else if (e.ctrlKey || e.metaKey) { tableManager.addRangeToSelection(index, index, true); } else if (e.shiftKey) { tableManager.selectItem(index, true, true); @@ -205,8 +210,15 @@ export function DataTable( } }, onRowStyle, + onContextMenu: props.enableContextMenu + ? () => { + // using a ref keeps the config stable, so that a new context menu doesn't need + // all rows to be rerendered, but rather shows it conditionally + return contextMenuRef.current?.()!; + } + : undefined, }; - }, [visibleColumns, tableManager, onRowStyle]); + }, [visibleColumns, tableManager, onRowStyle, props.enableContextMenu]); const itemRenderer = useCallback( function itemRenderer( @@ -415,6 +427,9 @@ export function DataTable( ], ); + const contextMenuRef = useRef(contexMenu); + contextMenuRef.current = contexMenu; + useEffect(function initialSetup() { return function cleanup() { // write current prefs to local storage @@ -519,7 +534,8 @@ DataTable.defaultProps = { enableSearchbar: true, enableAutoScroll: false, enableColumnHeaders: true, - eanbleMultiSelect: true, + enableMultiSelect: true, + enableContextMenu: true, onRenderEmpty: emptyRenderer, } as Partial>; diff --git a/desktop/flipper-plugin/src/ui/data-table/TableContextMenu.tsx b/desktop/flipper-plugin/src/ui/data-table/TableContextMenu.tsx index 22eac5211..5a030c9e2 100644 --- a/desktop/flipper-plugin/src/ui/data-table/TableContextMenu.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/TableContextMenu.tsx @@ -45,19 +45,19 @@ export function tableContextMenuFactory( ); } const hasSelection = selection.items.size > 0 ?? false; - return ( {onContextMenu ? onContextMenu(getSelectedItem(datasource, selection)) : null} } disabled={!hasSelection}> - {visibleColumns.map((column) => ( + {visibleColumns.map((column, idx) => ( { dispatch({ type: 'setColumnFilterFromSelection', @@ -69,12 +69,13 @@ export function tableContextMenuFactory( ))} } disabled={!hasSelection}> - {visibleColumns.map((column) => ( + {visibleColumns.map((column, idx) => ( { const items = getSelectedItems(datasource, selection); if (items.length) { @@ -88,6 +89,7 @@ export function tableContextMenuFactory( ))} { const items = getSelectedItems(datasource, selection); @@ -99,6 +101,7 @@ export function tableContextMenuFactory( {lib.isFB && ( { const items = getSelectedItems(datasource, selection); @@ -110,9 +113,9 @@ export function tableContextMenuFactory( )} - - {columns.map((column) => ( - + + {columns.map((column, idx) => ( + { diff --git a/desktop/flipper-plugin/src/ui/data-table/TableRow.tsx b/desktop/flipper-plugin/src/ui/data-table/TableRow.tsx index 59d43958f..00e804409 100644 --- a/desktop/flipper-plugin/src/ui/data-table/TableRow.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/TableRow.tsx @@ -13,6 +13,8 @@ import {theme} from '../theme'; import type {TableRowRenderContext} from './DataTable'; import {Width} from '../../utils/widthUtils'; import {DataFormatter} from '../DataFormatter'; +import {Dropdown} from 'antd'; +import {contextMenuTrigger} from '../data-inspector/DataInspectorNode'; // heuristic for row estimation, should match any future styling updates export const DEFAULT_ROW_HEIGHT = 24; @@ -106,7 +108,7 @@ export const TableRow = memo(function TableRow({ highlighted, config, }: TableRowProps) { - return ( + const row = ( { @@ -135,4 +137,13 @@ export const TableRow = memo(function TableRow({ })} ); + if (config.onContextMenu) { + return ( + + {row} + + ); + } else { + return row; + } }); diff --git a/desktop/flipper-plugin/src/ui/data-table/__tests__/DataTable.node.tsx b/desktop/flipper-plugin/src/ui/data-table/__tests__/DataTable.node.tsx index 98feeaa6b..5ad3712c7 100644 --- a/desktop/flipper-plugin/src/ui/data-table/__tests__/DataTable.node.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/__tests__/DataTable.node.tsx @@ -50,7 +50,7 @@ test('update and append', async () => { expect(elem.length).toBe(1); expect(elem[0].parentElement).toMatchInlineSnapshot(`
{ expect(elem.length).toBe(1); expect(elem[0].parentElement).toMatchInlineSnapshot(`
{ expect(elem.length).toBe(1); expect(elem[0].parentElement).toMatchInlineSnapshot(`
{ expect(elem.length).toBe(1); expect(elem[0].parentElement).toMatchInlineSnapshot(`
{ expect((await renderer.findByText('unique-string')).parentElement) .toMatchInlineSnapshot(`