Summary: Current implementation uses type `string` as a key for indexing items stored in datasource. However, users can provide any key as an index which means that the type of index item can be anything, not only string. This diff introduces a more refined types for the key. It adds another requirement to provide a key property to a generic which is used to infer the index type. Reviewed By: mweststrate, aigoncharov Differential Revision: D31895751 fbshipit-source-id: 19ba907bd6f35df87e3fa442db5fc5cec6af174d
199 lines
5.5 KiB
TypeScript
199 lines
5.5 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import {CopyOutlined, FilterOutlined, TableOutlined} from '@ant-design/icons';
|
|
import {Checkbox, Menu} from 'antd';
|
|
import {
|
|
DataTableDispatch,
|
|
getSelectedItem,
|
|
getSelectedItems,
|
|
Selection,
|
|
} from './DataTableManager';
|
|
import React from 'react';
|
|
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
|
|
import {DataTableColumn} from './DataTable';
|
|
import {toFirstUpper} from '../../utils/toFirstUpper';
|
|
import {DataSource} from '../../data-source/index';
|
|
import {renderColumnValue} from './TableRow';
|
|
import {textContent} from '../../utils/textContent';
|
|
|
|
const {Item, SubMenu} = Menu;
|
|
|
|
export function tableContextMenuFactory<T>(
|
|
datasource: DataSource<T, T[keyof T]>,
|
|
dispatch: DataTableDispatch<T>,
|
|
selection: Selection,
|
|
columns: DataTableColumn<T>[],
|
|
visibleColumns: DataTableColumn<T>[],
|
|
onCopyRows: (
|
|
rows: T[],
|
|
visibleColumns: DataTableColumn<T>[],
|
|
) => string = defaultOnCopyRows,
|
|
onContextMenu?: (selection: undefined | T) => React.ReactElement,
|
|
) {
|
|
const lib = tryGetFlipperLibImplementation();
|
|
if (!lib) {
|
|
return (
|
|
<Menu>
|
|
<Item>Menu not ready</Item>
|
|
</Menu>
|
|
);
|
|
}
|
|
const hasSelection = selection.items.size > 0 ?? false;
|
|
return (
|
|
<Menu>
|
|
{onContextMenu
|
|
? onContextMenu(getSelectedItem(datasource, selection))
|
|
: null}
|
|
<SubMenu
|
|
key="filter same"
|
|
title="Filter on same"
|
|
icon={<FilterOutlined />}
|
|
disabled={!hasSelection}>
|
|
{visibleColumns.map((column, idx) => (
|
|
<Item
|
|
key={column.key ?? idx}
|
|
onClick={() => {
|
|
dispatch({
|
|
type: 'setColumnFilterFromSelection',
|
|
column: column.key,
|
|
});
|
|
}}>
|
|
{friendlyColumnTitle(column)}
|
|
</Item>
|
|
))}
|
|
</SubMenu>
|
|
<SubMenu
|
|
key="copy rows"
|
|
title="Copy row(s)"
|
|
icon={<TableOutlined />}
|
|
disabled={!hasSelection}>
|
|
<Item
|
|
key="copyToClipboard"
|
|
disabled={!hasSelection}
|
|
onClick={() => {
|
|
const items = getSelectedItems(datasource, selection);
|
|
if (items.length) {
|
|
lib.writeTextToClipboard(onCopyRows(items, visibleColumns));
|
|
}
|
|
}}>
|
|
Copy row(s)
|
|
</Item>
|
|
{lib.isFB && (
|
|
<Item
|
|
key="createPaste"
|
|
disabled={!hasSelection}
|
|
onClick={() => {
|
|
const items = getSelectedItems(datasource, selection);
|
|
if (items.length) {
|
|
lib.createPaste(onCopyRows(items, visibleColumns));
|
|
}
|
|
}}>
|
|
Create paste
|
|
</Item>
|
|
)}
|
|
<Item
|
|
key="copyToClipboardJSON"
|
|
disabled={!hasSelection}
|
|
onClick={() => {
|
|
const items = getSelectedItems(datasource, selection);
|
|
if (items.length) {
|
|
lib.writeTextToClipboard(rowsToJson(items));
|
|
}
|
|
}}>
|
|
Copy row(s) (JSON)
|
|
</Item>
|
|
{lib.isFB && (
|
|
<Item
|
|
key="createPaste"
|
|
disabled={!hasSelection}
|
|
onClick={() => {
|
|
const items = getSelectedItems(datasource, selection);
|
|
if (items.length) {
|
|
lib.createPaste(rowsToJson(items));
|
|
}
|
|
}}>
|
|
Create paste (JSON)
|
|
</Item>
|
|
)}
|
|
</SubMenu>
|
|
|
|
<SubMenu
|
|
key="copy cells"
|
|
title="Copy cell(s)"
|
|
icon={<CopyOutlined />}
|
|
disabled={!hasSelection}>
|
|
{visibleColumns.map((column, idx) => (
|
|
<Item
|
|
key={column.key ?? idx}
|
|
onClick={() => {
|
|
const items = getSelectedItems(datasource, selection);
|
|
if (items.length) {
|
|
lib.writeTextToClipboard(
|
|
items.map((item) => '' + item[column.key]).join('\n'),
|
|
);
|
|
}
|
|
}}>
|
|
{friendlyColumnTitle(column)}
|
|
</Item>
|
|
))}
|
|
</SubMenu>
|
|
<Menu.Divider />
|
|
<SubMenu title="Visible columns" key="visible columns">
|
|
{columns.map((column, idx) => (
|
|
<Menu.Item key={column.key ?? idx}>
|
|
<Checkbox
|
|
checked={column.visible}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
dispatch({type: 'toggleColumnVisibility', column: column.key});
|
|
}}>
|
|
{friendlyColumnTitle(column)}
|
|
</Checkbox>
|
|
</Menu.Item>
|
|
))}
|
|
</SubMenu>
|
|
<Menu.Item
|
|
key="reset"
|
|
onClick={() => {
|
|
dispatch({type: 'reset'});
|
|
}}>
|
|
Reset view
|
|
</Menu.Item>
|
|
</Menu>
|
|
);
|
|
}
|
|
|
|
function friendlyColumnTitle(column: DataTableColumn<any>): string {
|
|
const name = column.title || column.key;
|
|
return toFirstUpper(name);
|
|
}
|
|
|
|
function defaultOnCopyRows<T>(
|
|
items: T[],
|
|
visibleColumns: DataTableColumn<T>[],
|
|
) {
|
|
return (
|
|
visibleColumns.map(friendlyColumnTitle).join('\t') +
|
|
'\n' +
|
|
items
|
|
.map((row, idx) =>
|
|
visibleColumns
|
|
.map((col) => textContent(renderColumnValue(col, row, true, idx)))
|
|
.join('\t'),
|
|
)
|
|
.join('\n')
|
|
);
|
|
}
|
|
|
|
function rowsToJson<T>(items: T[]) {
|
|
return JSON.stringify(items.length > 1 ? items : items[0], null, 2);
|
|
}
|