Get rid of immutablejs
Summary: Changelog: 'flipper' package no longer uses or exposes immutablejs, or ManagedTable_Immutable ImmutableJS was used in a few places, requires some specific knowledge to use optimally (e.g. batching was used nowhere), but most importantly resulted in a lot of unmaintained code duplication in terms of ManagedTable_immutable and the searchable variant. For the planned trace export speedups this means that there is also serialization case less to worry about. Reviewed By: jknoxville Differential Revision: D29265677 fbshipit-source-id: 92e86081c03fb8da59d2c9f975f04a05e275c317
This commit is contained in:
committed by
Facebook GitHub Bot
parent
77612a3f7b
commit
8359f74a21
@@ -40,7 +40,6 @@
|
|||||||
"flipper-plugin-lib": "0.0.0",
|
"flipper-plugin-lib": "0.0.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"immer": "^9.0.3",
|
"immer": "^9.0.3",
|
||||||
"immutable": "^4.0.0-rc.12",
|
|
||||||
"invariant": "^2.2.2",
|
"invariant": "^2.2.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lodash.memoize": "^4.1.2",
|
"lodash.memoize": "^4.1.2",
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export {default as Popover} from './ui/components/Popover';
|
|||||||
export {
|
export {
|
||||||
TableColumns,
|
TableColumns,
|
||||||
TableRows,
|
TableRows,
|
||||||
TableRows_immutable,
|
|
||||||
TableBodyColumn,
|
TableBodyColumn,
|
||||||
TableBodyRow,
|
TableBodyRow,
|
||||||
TableHighlightedRows,
|
TableHighlightedRows,
|
||||||
@@ -81,10 +80,6 @@ export {
|
|||||||
ManagedTable as ManagedTableClass,
|
ManagedTable as ManagedTableClass,
|
||||||
} from './ui/components/table/ManagedTable';
|
} from './ui/components/table/ManagedTable';
|
||||||
export {ManagedTableProps} from './ui/components/table/ManagedTable';
|
export {ManagedTableProps} from './ui/components/table/ManagedTable';
|
||||||
export {
|
|
||||||
default as ManagedTable_immutable,
|
|
||||||
ManagedTableProps_immutable,
|
|
||||||
} from './ui/components/table/ManagedTable_immutable';
|
|
||||||
export {
|
export {
|
||||||
DataValueExtractor,
|
DataValueExtractor,
|
||||||
DataInspectorExpanded,
|
DataInspectorExpanded,
|
||||||
@@ -165,7 +160,6 @@ export {
|
|||||||
default as SearchableTable,
|
default as SearchableTable,
|
||||||
filterRowsFactory,
|
filterRowsFactory,
|
||||||
} from './ui/components/searchable/SearchableTable';
|
} from './ui/components/searchable/SearchableTable';
|
||||||
export {default as SearchableTable_immutable} from './ui/components/searchable/SearchableTable_immutable';
|
|
||||||
export {
|
export {
|
||||||
ElementsInspector,
|
ElementsInspector,
|
||||||
ElementsInspectorElement as Element,
|
ElementsInspectorElement as Element,
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 {Filter} from '../filter/types';
|
|
||||||
import {ManagedTableProps_immutable} from '../table/ManagedTable_immutable';
|
|
||||||
import {TableBodyRow} from '../table/types';
|
|
||||||
import Searchable, {SearchableProps} from './Searchable';
|
|
||||||
import {PureComponent} from 'react';
|
|
||||||
import ManagedTable_immutable from '../table/ManagedTable_immutable';
|
|
||||||
import deepEqual from 'deep-equal';
|
|
||||||
import React from 'react';
|
|
||||||
import {filterRowsFactory} from './SearchableTable';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
/** Reference to the table */
|
|
||||||
innerRef?: (ref: React.RefObject<any>) => void;
|
|
||||||
/** Filters that are added to the filterbar by default */
|
|
||||||
defaultFilters: Array<Filter>;
|
|
||||||
} & ManagedTableProps_immutable &
|
|
||||||
SearchableProps;
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
filterRows: (row: TableBodyRow) => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SearchableManagedTable_immutable extends PureComponent<Props, State> {
|
|
||||||
static defaultProps = {
|
|
||||||
defaultFilters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
filterRows: filterRowsFactory(
|
|
||||||
this.props.filters,
|
|
||||||
this.props.searchTerm,
|
|
||||||
this.props.regexEnabled || false,
|
|
||||||
this.props.contentSearchEnabled || false,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.defaultFilters.map(this.props.addFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
||||||
if (
|
|
||||||
nextProps.searchTerm !== this.props.searchTerm ||
|
|
||||||
nextProps.regexEnabled != this.props.regexEnabled ||
|
|
||||||
!deepEqual(this.props.filters, nextProps.filters)
|
|
||||||
) {
|
|
||||||
this.setState({
|
|
||||||
filterRows: filterRowsFactory(
|
|
||||||
nextProps.filters,
|
|
||||||
nextProps.searchTerm,
|
|
||||||
nextProps.regexEnabled || false,
|
|
||||||
this.props.contentSearchEnabled || false,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
addFilter,
|
|
||||||
searchTerm: _searchTerm,
|
|
||||||
filters: _filters,
|
|
||||||
innerRef,
|
|
||||||
rows,
|
|
||||||
...props
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ManagedTable_immutable
|
|
||||||
{...props}
|
|
||||||
filter={this.state.filterRows}
|
|
||||||
rows={rows.filter(this.state.filterRows)}
|
|
||||||
onAddFilter={addFilter}
|
|
||||||
ref={innerRef}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Table with filter and searchbar, supports all properties a ManagedTable
|
|
||||||
* and Searchable supports.
|
|
||||||
*/
|
|
||||||
export default Searchable(SearchableManagedTable_immutable);
|
|
||||||
@@ -1,711 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 {
|
|
||||||
TableColumnOrder,
|
|
||||||
TableColumnSizes,
|
|
||||||
TableColumns,
|
|
||||||
TableHighlightedRows,
|
|
||||||
TableRowSortOrder,
|
|
||||||
TableRows_immutable,
|
|
||||||
TableBodyRow,
|
|
||||||
TableOnAddFilter,
|
|
||||||
} from './types';
|
|
||||||
import {MenuTemplate} from '../ContextMenu';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
||||||
import {VariableSizeList as List} from 'react-window';
|
|
||||||
import {clipboard, MenuItemConstructorOptions} from 'electron';
|
|
||||||
import TableHead from './TableHead';
|
|
||||||
import TableRow from './TableRow';
|
|
||||||
import ContextMenu from '../ContextMenu';
|
|
||||||
import FlexColumn from '../FlexColumn';
|
|
||||||
import createPaste from '../../../fb-stubs/createPaste';
|
|
||||||
import debounceRender from 'react-debounce-render';
|
|
||||||
import {debounce} from 'lodash';
|
|
||||||
import {DEFAULT_ROW_HEIGHT} from './types';
|
|
||||||
import textContent from '../../../utils/textContent';
|
|
||||||
import {notNull} from '../../../utils/typeUtils';
|
|
||||||
|
|
||||||
export type ManagedTableProps_immutable = {
|
|
||||||
/**
|
|
||||||
* Column definitions.
|
|
||||||
*/
|
|
||||||
columns: TableColumns;
|
|
||||||
/**
|
|
||||||
* Row definitions.
|
|
||||||
*/
|
|
||||||
rows: TableRows_immutable;
|
|
||||||
/*
|
|
||||||
* Globally unique key for persisting data between uses of a table such as column sizes.
|
|
||||||
*/
|
|
||||||
tableKey?: string;
|
|
||||||
/**
|
|
||||||
* Whether the table has a border.
|
|
||||||
*/
|
|
||||||
floating?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether a row can span over multiple lines. Otherwise lines cannot wrap and
|
|
||||||
* are truncated.
|
|
||||||
*/
|
|
||||||
multiline?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the body is scrollable. When this is set to `true` then the table
|
|
||||||
* is not scrollable.
|
|
||||||
*/
|
|
||||||
autoHeight?: boolean;
|
|
||||||
/**
|
|
||||||
* Order of columns.
|
|
||||||
*/
|
|
||||||
columnOrder?: TableColumnOrder;
|
|
||||||
/**
|
|
||||||
* Initial size of the columns.
|
|
||||||
*/
|
|
||||||
columnSizes?: TableColumnSizes;
|
|
||||||
/**
|
|
||||||
* Value to filter rows on. Alternative to the `filter` prop.
|
|
||||||
*/
|
|
||||||
filterValue?: string;
|
|
||||||
/**
|
|
||||||
* Callback to filter rows.
|
|
||||||
*/
|
|
||||||
filter?: (row: TableBodyRow) => boolean;
|
|
||||||
/**
|
|
||||||
* Callback when the highlighted rows change.
|
|
||||||
*/
|
|
||||||
onRowHighlighted?: (keys: TableHighlightedRows) => void;
|
|
||||||
/**
|
|
||||||
* Whether rows can be highlighted or not.
|
|
||||||
*/
|
|
||||||
highlightableRows?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether multiple rows can be highlighted or not.
|
|
||||||
*/
|
|
||||||
multiHighlight?: boolean;
|
|
||||||
/**
|
|
||||||
* Height of each row.
|
|
||||||
*/
|
|
||||||
rowLineHeight?: number;
|
|
||||||
/**
|
|
||||||
* This makes it so the scroll position sticks to the bottom of the window.
|
|
||||||
* Useful for streaming data like requests, logs etc.
|
|
||||||
*/
|
|
||||||
stickyBottom?: boolean;
|
|
||||||
/**
|
|
||||||
* Used by SearchableTable to add filters for rows.
|
|
||||||
*/
|
|
||||||
onAddFilter?: TableOnAddFilter;
|
|
||||||
/**
|
|
||||||
* Enable or disable zebra striping.
|
|
||||||
*/
|
|
||||||
zebra?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to hide the column names at the top of the table.
|
|
||||||
*/
|
|
||||||
hideHeader?: boolean;
|
|
||||||
/**
|
|
||||||
* Rows that are highlighted initially.
|
|
||||||
*/
|
|
||||||
highlightedRows?: Set<string>;
|
|
||||||
/**
|
|
||||||
* Allows to create context menu items for rows.
|
|
||||||
*/
|
|
||||||
buildContextMenuItems?: () => MenuTemplate;
|
|
||||||
/**
|
|
||||||
* Callback when sorting changes.
|
|
||||||
*/
|
|
||||||
onSort?: (order: TableRowSortOrder) => void;
|
|
||||||
/**
|
|
||||||
* Initial sort order of the table.
|
|
||||||
*/
|
|
||||||
initialSortOrder?: TableRowSortOrder;
|
|
||||||
/**
|
|
||||||
* Table scroll horizontally, if needed
|
|
||||||
*/
|
|
||||||
horizontallyScrollable?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ManagedTableState = {
|
|
||||||
highlightedRows: Set<string>;
|
|
||||||
sortOrder?: TableRowSortOrder;
|
|
||||||
columnOrder: TableColumnOrder;
|
|
||||||
columnSizes: TableColumnSizes;
|
|
||||||
shouldScrollToBottom: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Container = styled(FlexColumn)<{canOverflow?: boolean}>((props) => ({
|
|
||||||
overflow: props.canOverflow ? 'scroll' : 'visible',
|
|
||||||
flexGrow: 1,
|
|
||||||
}));
|
|
||||||
Container.displayName = 'ManagedTable_immutable:Container';
|
|
||||||
|
|
||||||
const globalTableState: {[key: string]: TableColumnSizes} = {};
|
|
||||||
|
|
||||||
class ManagedTable extends React.Component<
|
|
||||||
ManagedTableProps_immutable,
|
|
||||||
ManagedTableState
|
|
||||||
> {
|
|
||||||
static defaultProps = {
|
|
||||||
highlightableRows: true,
|
|
||||||
multiHighlight: false,
|
|
||||||
autoHeight: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
getTableKey = (): string => {
|
|
||||||
return (
|
|
||||||
'TABLE_COLUMNS_' + Object.keys(this.props.columns).join('_').toUpperCase()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
state: ManagedTableState = {
|
|
||||||
columnOrder:
|
|
||||||
JSON.parse(window.localStorage.getItem(this.getTableKey()) || 'null') ||
|
|
||||||
this.props.columnOrder ||
|
|
||||||
Object.keys(this.props.columns).map((key) => ({key, visible: true})),
|
|
||||||
columnSizes:
|
|
||||||
this.props.tableKey && globalTableState[this.props.tableKey]
|
|
||||||
? globalTableState[this.props.tableKey]
|
|
||||||
: this.props.columnSizes || {},
|
|
||||||
highlightedRows: this.props.highlightedRows || new Set(),
|
|
||||||
sortOrder: this.props.initialSortOrder || undefined,
|
|
||||||
shouldScrollToBottom: Boolean(this.props.stickyBottom),
|
|
||||||
};
|
|
||||||
|
|
||||||
tableRef = React.createRef<List>();
|
|
||||||
|
|
||||||
scrollRef: {
|
|
||||||
current: null | HTMLDivElement;
|
|
||||||
} = React.createRef();
|
|
||||||
|
|
||||||
dragStartIndex: number | null = null;
|
|
||||||
|
|
||||||
// We want to call scrollToHighlightedRows on componentDidMount. However, at
|
|
||||||
// this time, tableRef is still null, because AutoSizer needs one render to
|
|
||||||
// measure the size of the table. This is why we are using this flag to
|
|
||||||
// trigger actions on the first update instead.
|
|
||||||
firstUpdate = true;
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
document.addEventListener('keydown', this.onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('keydown', this.onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps: ManagedTableProps_immutable) {
|
|
||||||
// if columnSizes has changed
|
|
||||||
if (nextProps.columnSizes !== this.props.columnSizes) {
|
|
||||||
this.setState({
|
|
||||||
columnSizes: {
|
|
||||||
...(this.state.columnSizes || {}),
|
|
||||||
...nextProps.columnSizes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.highlightedRows !== nextProps.highlightedRows) {
|
|
||||||
this.setState({highlightedRows: nextProps.highlightedRows || new Set()});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if columnOrder has changed
|
|
||||||
if (
|
|
||||||
nextProps.columnOrder !== this.props.columnOrder &&
|
|
||||||
nextProps.columnOrder
|
|
||||||
) {
|
|
||||||
if (this.tableRef && this.tableRef.current) {
|
|
||||||
this.tableRef.current.resetAfterIndex(0, true);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
columnOrder: nextProps.columnOrder,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.props.rows.size > nextProps.rows.size &&
|
|
||||||
this.tableRef &&
|
|
||||||
this.tableRef.current
|
|
||||||
) {
|
|
||||||
// rows were filtered, we need to recalculate heights
|
|
||||||
this.tableRef.current.resetAfterIndex(0, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(
|
|
||||||
prevProps: ManagedTableProps_immutable,
|
|
||||||
prevState: ManagedTableState,
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
this.props.rows.size !== prevProps.rows.size &&
|
|
||||||
this.state.shouldScrollToBottom &&
|
|
||||||
this.state.highlightedRows.size < 2
|
|
||||||
) {
|
|
||||||
this.scrollToBottom();
|
|
||||||
} else if (
|
|
||||||
prevState.highlightedRows !== this.state.highlightedRows ||
|
|
||||||
this.firstUpdate
|
|
||||||
) {
|
|
||||||
this.scrollToHighlightedRows();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.props.stickyBottom &&
|
|
||||||
!this.state.shouldScrollToBottom &&
|
|
||||||
this.scrollRef &&
|
|
||||||
this.scrollRef.current &&
|
|
||||||
this.scrollRef.current.parentElement &&
|
|
||||||
this.scrollRef.current.parentElement instanceof HTMLElement &&
|
|
||||||
this.scrollRef.current.offsetHeight <=
|
|
||||||
this.scrollRef.current.parentElement.offsetHeight
|
|
||||||
) {
|
|
||||||
this.setState({shouldScrollToBottom: true});
|
|
||||||
}
|
|
||||||
this.firstUpdate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToHighlightedRows = () => {
|
|
||||||
const {current} = this.tableRef;
|
|
||||||
const {highlightedRows} = this.state;
|
|
||||||
if (current && highlightedRows && highlightedRows.size > 0) {
|
|
||||||
const highlightedRow = Array.from(highlightedRows)[0];
|
|
||||||
const index = this.props.rows.findIndex(
|
|
||||||
({key}) => key === highlightedRow,
|
|
||||||
);
|
|
||||||
if (index >= 0) {
|
|
||||||
current.scrollToItem(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onCopy = (withHeader: boolean) => {
|
|
||||||
clipboard.writeText(
|
|
||||||
[
|
|
||||||
...(withHeader ? [this.getHeaderText()] : []),
|
|
||||||
this.getSelectedText(),
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onKeyDown = (e: KeyboardEvent) => {
|
|
||||||
const {highlightedRows} = this.state;
|
|
||||||
if (highlightedRows.size === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
((e.metaKey && process.platform === 'darwin') ||
|
|
||||||
(e.ctrlKey && process.platform !== 'darwin')) &&
|
|
||||||
e.keyCode === 67
|
|
||||||
) {
|
|
||||||
this.onCopy(false);
|
|
||||||
} else if (
|
|
||||||
(e.keyCode === 38 || e.keyCode === 40) &&
|
|
||||||
this.props.highlightableRows
|
|
||||||
) {
|
|
||||||
// arrow navigation
|
|
||||||
const {rows} = this.props;
|
|
||||||
const {highlightedRows} = this.state;
|
|
||||||
const lastItemKey = Array.from(this.state.highlightedRows).pop();
|
|
||||||
const lastItemIndex = this.props.rows.findIndex(
|
|
||||||
(row) => row.key === lastItemKey,
|
|
||||||
);
|
|
||||||
const newIndex = Math.min(
|
|
||||||
rows.size - 1,
|
|
||||||
Math.max(0, e.keyCode === 38 ? lastItemIndex - 1 : lastItemIndex + 1),
|
|
||||||
);
|
|
||||||
if (!e.shiftKey) {
|
|
||||||
highlightedRows.clear();
|
|
||||||
}
|
|
||||||
highlightedRows.add(rows.get(newIndex)!.key);
|
|
||||||
this.onRowHighlighted(highlightedRows, () => {
|
|
||||||
const {current} = this.tableRef;
|
|
||||||
if (current) {
|
|
||||||
current.scrollToItem(newIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onRowHighlighted = (highlightedRows: Set<string>, cb?: () => void) => {
|
|
||||||
if (!this.props.highlightableRows) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({highlightedRows}, cb);
|
|
||||||
const {onRowHighlighted} = this.props;
|
|
||||||
if (onRowHighlighted) {
|
|
||||||
onRowHighlighted(Array.from(highlightedRows));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onSort = (sortOrder: TableRowSortOrder) => {
|
|
||||||
this.setState({sortOrder});
|
|
||||||
this.props.onSort && this.props.onSort(sortOrder);
|
|
||||||
};
|
|
||||||
|
|
||||||
onColumnOrder = (columnOrder: TableColumnOrder) => {
|
|
||||||
this.setState({columnOrder});
|
|
||||||
// persist column order
|
|
||||||
window.localStorage.setItem(
|
|
||||||
this.getTableKey(),
|
|
||||||
JSON.stringify(columnOrder),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onColumnResize = (id: string, width: number | string) => {
|
|
||||||
this.setState(({columnSizes}) => ({
|
|
||||||
columnSizes: {
|
|
||||||
...columnSizes,
|
|
||||||
[id]: width,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
if (!this.props.tableKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!globalTableState[this.props.tableKey]) {
|
|
||||||
globalTableState[this.props.tableKey] = {};
|
|
||||||
}
|
|
||||||
globalTableState[this.props.tableKey][id] = width;
|
|
||||||
};
|
|
||||||
|
|
||||||
scrollToBottom() {
|
|
||||||
const {current: tableRef} = this.tableRef;
|
|
||||||
|
|
||||||
if (tableRef && this.props.rows.size > 1) {
|
|
||||||
tableRef.scrollToItem(this.props.rows.size - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onHighlight = (e: React.MouseEvent, row: TableBodyRow, index: number) => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
// prevents text selection
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
let {highlightedRows} = this.state;
|
|
||||||
|
|
||||||
const contextClick =
|
|
||||||
e.button !== 0 ||
|
|
||||||
(process.platform === 'darwin' && e.button === 0 && e.ctrlKey);
|
|
||||||
|
|
||||||
if (contextClick) {
|
|
||||||
if (!highlightedRows.has(row.key)) {
|
|
||||||
highlightedRows.clear();
|
|
||||||
highlightedRows.add(row.key);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dragStartIndex = index;
|
|
||||||
document.addEventListener('mouseup', this.onStopDragSelecting);
|
|
||||||
|
|
||||||
if (
|
|
||||||
((process.platform === 'darwin' && e.metaKey) ||
|
|
||||||
(process.platform !== 'darwin' && e.ctrlKey)) &&
|
|
||||||
this.props.multiHighlight
|
|
||||||
) {
|
|
||||||
highlightedRows.add(row.key);
|
|
||||||
} else if (e.shiftKey && this.props.multiHighlight) {
|
|
||||||
// range select
|
|
||||||
const lastItemKey = Array.from(this.state.highlightedRows).pop()!;
|
|
||||||
highlightedRows = new Set([
|
|
||||||
...highlightedRows,
|
|
||||||
...this.selectInRange(lastItemKey, row.key),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
// single select
|
|
||||||
this.state.highlightedRows.clear();
|
|
||||||
this.state.highlightedRows.add(row.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onRowHighlighted(highlightedRows);
|
|
||||||
};
|
|
||||||
|
|
||||||
onStopDragSelecting = () => {
|
|
||||||
this.dragStartIndex = null;
|
|
||||||
document.removeEventListener('mouseup', this.onStopDragSelecting);
|
|
||||||
};
|
|
||||||
|
|
||||||
selectInRange = (fromKey: string, toKey: string): Array<string> => {
|
|
||||||
const selected = [];
|
|
||||||
let startIndex = -1;
|
|
||||||
let endIndex = -1;
|
|
||||||
for (let i = 0; i < this.props.rows.size; i++) {
|
|
||||||
if (this.props.rows.get(i)!.key === fromKey) {
|
|
||||||
startIndex = i;
|
|
||||||
}
|
|
||||||
if (this.props.rows.get(i)!.key === toKey) {
|
|
||||||
endIndex = i;
|
|
||||||
}
|
|
||||||
if (endIndex > -1 && startIndex > -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (
|
|
||||||
let i = Math.min(startIndex, endIndex);
|
|
||||||
i <= Math.max(startIndex, endIndex);
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
selected.push(this.props.rows.get(i)!.key);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMouseEnterRow = (e: React.MouseEvent, row: TableBodyRow, index: number) => {
|
|
||||||
const {dragStartIndex} = this;
|
|
||||||
const {current} = this.tableRef;
|
|
||||||
if (
|
|
||||||
dragStartIndex &&
|
|
||||||
current &&
|
|
||||||
this.props.multiHighlight &&
|
|
||||||
this.props.highlightableRows &&
|
|
||||||
!e.shiftKey // When shift key is pressed, it's a range select not a drag select
|
|
||||||
) {
|
|
||||||
current.scrollToItem(index + 1);
|
|
||||||
const startKey = this.props.rows.get(dragStartIndex)!.key;
|
|
||||||
const highlightedRows = new Set(this.selectInRange(startKey, row.key));
|
|
||||||
this.onRowHighlighted(highlightedRows);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onCopyCell = (rowId: string, index: number) => {
|
|
||||||
const cellText = this.getTextContentOfRow(rowId)[index];
|
|
||||||
clipboard.writeText(cellText);
|
|
||||||
};
|
|
||||||
|
|
||||||
buildContextMenuItems: () => MenuItemConstructorOptions[] = () => {
|
|
||||||
const {highlightedRows} = this.state;
|
|
||||||
if (highlightedRows.size === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyCellSubMenu =
|
|
||||||
highlightedRows.size === 1
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Copy cell',
|
|
||||||
submenu: this.state.columnOrder
|
|
||||||
.filter((c) => c.visible)
|
|
||||||
.map((c) => c.key)
|
|
||||||
.map((column, index) => ({
|
|
||||||
label: this.props.columns[column].value,
|
|
||||||
click: () => {
|
|
||||||
const rowId = this.state.highlightedRows
|
|
||||||
.values()
|
|
||||||
.next().value;
|
|
||||||
rowId && this.onCopyCell(rowId, index);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...copyCellSubMenu,
|
|
||||||
{
|
|
||||||
label:
|
|
||||||
highlightedRows.size > 1
|
|
||||||
? `Copy ${highlightedRows.size} rows`
|
|
||||||
: 'Copy row',
|
|
||||||
submenu: [
|
|
||||||
{label: 'With columns header', click: () => this.onCopy(true)},
|
|
||||||
{
|
|
||||||
label: 'Without columns header',
|
|
||||||
click: () => {
|
|
||||||
this.onCopy(false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Create Paste',
|
|
||||||
click: () =>
|
|
||||||
createPaste(
|
|
||||||
[this.getHeaderText(), this.getSelectedText()].join('\n'),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
getHeaderText = (): string => {
|
|
||||||
return this.state.columnOrder
|
|
||||||
.filter((c) => c.visible)
|
|
||||||
.map((c) => c.key)
|
|
||||||
.map((key) => this.props.columns[key].value)
|
|
||||||
.join('\t');
|
|
||||||
};
|
|
||||||
|
|
||||||
getSelectedText = (): string => {
|
|
||||||
const {highlightedRows} = this.state;
|
|
||||||
|
|
||||||
if (highlightedRows.size === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return this.props.rows
|
|
||||||
.filter((row) => highlightedRows.has(row.key))
|
|
||||||
.map((row: TableBodyRow) =>
|
|
||||||
typeof row.copyText === 'function'
|
|
||||||
? row.copyText()
|
|
||||||
: row.copyText || this.getTextContentOfRow(row.key).join('\t'),
|
|
||||||
)
|
|
||||||
.join('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
getTextContentOfRow = (key: string): Array<string> => {
|
|
||||||
const row = this.props.rows.find((row) => row.key === key);
|
|
||||||
if (!row) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return this.state.columnOrder
|
|
||||||
.filter(({visible}) => visible)
|
|
||||||
.map(({key}) => textContent(row.columns[key].value));
|
|
||||||
};
|
|
||||||
|
|
||||||
onScroll = debounce(
|
|
||||||
({
|
|
||||||
scrollDirection,
|
|
||||||
scrollOffset,
|
|
||||||
}: {
|
|
||||||
scrollDirection: 'forward' | 'backward';
|
|
||||||
scrollOffset: number;
|
|
||||||
scrollUpdateWasRequested: boolean;
|
|
||||||
}) => {
|
|
||||||
const {current} = this.scrollRef;
|
|
||||||
const parent = current ? current.parentElement : null;
|
|
||||||
if (
|
|
||||||
this.props.stickyBottom &&
|
|
||||||
current &&
|
|
||||||
parent instanceof HTMLElement &&
|
|
||||||
scrollDirection === 'forward' &&
|
|
||||||
!this.state.shouldScrollToBottom &&
|
|
||||||
current.offsetHeight - parent.offsetHeight === scrollOffset
|
|
||||||
) {
|
|
||||||
this.setState({shouldScrollToBottom: true});
|
|
||||||
} else if (
|
|
||||||
this.props.stickyBottom &&
|
|
||||||
scrollDirection === 'backward' &&
|
|
||||||
this.state.shouldScrollToBottom
|
|
||||||
) {
|
|
||||||
this.setState({shouldScrollToBottom: false});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
100,
|
|
||||||
);
|
|
||||||
|
|
||||||
getRow = ({index, style}: {index: number; style: React.CSSProperties}) => {
|
|
||||||
const {onAddFilter, multiline, zebra, rows} = this.props;
|
|
||||||
const {columnOrder, columnSizes, highlightedRows} = this.state;
|
|
||||||
const columnKeys = columnOrder
|
|
||||||
.map((k) => (k.visible ? k.key : null))
|
|
||||||
.filter(notNull);
|
|
||||||
|
|
||||||
const row = rows.get(index);
|
|
||||||
if (row == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={row.key}
|
|
||||||
columnSizes={columnSizes}
|
|
||||||
columnKeys={columnKeys}
|
|
||||||
onMouseDown={(e) => this.onHighlight(e, row, index)}
|
|
||||||
onMouseEnter={(e) => this.onMouseEnterRow(e, row, index)}
|
|
||||||
multiline={multiline}
|
|
||||||
rowLineHeight={24}
|
|
||||||
highlighted={highlightedRows.has(row.key)}
|
|
||||||
row={row}
|
|
||||||
index={index}
|
|
||||||
style={style}
|
|
||||||
onAddFilter={onAddFilter}
|
|
||||||
zebra={zebra}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {columns, rows, rowLineHeight, hideHeader, horizontallyScrollable} =
|
|
||||||
this.props;
|
|
||||||
const {columnOrder, columnSizes} = this.state;
|
|
||||||
|
|
||||||
let computedWidth = 0;
|
|
||||||
if (horizontallyScrollable) {
|
|
||||||
for (let index = 0; index < columnOrder.length; index++) {
|
|
||||||
const col = columnOrder[index];
|
|
||||||
|
|
||||||
if (!col.visible) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = columnSizes[col.key];
|
|
||||||
if (typeof width === 'number' && isNaN(width)) {
|
|
||||||
// non-numeric columns with, can't caluclate
|
|
||||||
computedWidth = 0;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
computedWidth += parseInt(String(width), 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container canOverflow={horizontallyScrollable}>
|
|
||||||
{hideHeader !== true && (
|
|
||||||
<TableHead
|
|
||||||
columnOrder={columnOrder}
|
|
||||||
onColumnOrder={this.onColumnOrder}
|
|
||||||
columns={columns}
|
|
||||||
onColumnResize={this.onColumnResize}
|
|
||||||
sortOrder={this.state.sortOrder}
|
|
||||||
columnSizes={columnSizes}
|
|
||||||
onSort={this.onSort}
|
|
||||||
horizontallyScrollable={horizontallyScrollable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Container>
|
|
||||||
{this.props.autoHeight ? (
|
|
||||||
this.props.rows.map((_, index) => this.getRow({index, style: {}}))
|
|
||||||
) : (
|
|
||||||
<AutoSizer>
|
|
||||||
{({width, height}) => (
|
|
||||||
<ContextMenu
|
|
||||||
buildItems={
|
|
||||||
this.props.buildContextMenuItems ||
|
|
||||||
this.buildContextMenuItems
|
|
||||||
}>
|
|
||||||
<List
|
|
||||||
itemCount={rows.size}
|
|
||||||
itemSize={(index) =>
|
|
||||||
(rows.get(index) && rows.get(index)!.height) ||
|
|
||||||
rowLineHeight ||
|
|
||||||
DEFAULT_ROW_HEIGHT
|
|
||||||
}
|
|
||||||
ref={this.tableRef}
|
|
||||||
width={Math.max(width, computedWidth)}
|
|
||||||
estimatedItemSize={rowLineHeight || DEFAULT_ROW_HEIGHT}
|
|
||||||
overscanCount={5}
|
|
||||||
innerRef={this.scrollRef}
|
|
||||||
onScroll={this.onScroll}
|
|
||||||
height={height}>
|
|
||||||
{this.getRow}
|
|
||||||
</List>
|
|
||||||
</ContextMenu>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default debounceRender(ManagedTable, 150, {maxWait: 250});
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Filter} from '../filter/types';
|
import {Filter} from '../filter/types';
|
||||||
import {List} from 'immutable';
|
|
||||||
import {Property} from 'csstype';
|
import {Property} from 'csstype';
|
||||||
|
|
||||||
export const MINIMUM_COLUMN_WIDTH = 100;
|
export const MINIMUM_COLUMN_WIDTH = 100;
|
||||||
@@ -77,8 +76,6 @@ export type TableColumns = {
|
|||||||
|
|
||||||
export type TableRows = Array<TableBodyRow>;
|
export type TableRows = Array<TableBodyRow>;
|
||||||
|
|
||||||
export type TableRows_immutable = List<TableBodyRow>;
|
|
||||||
|
|
||||||
export type TableRowSortOrder = {
|
export type TableRowSortOrder = {
|
||||||
key: string;
|
key: string;
|
||||||
direction: 'up' | 'down';
|
direction: 'up' | 'down';
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export {default as Popover} from './components/Popover';
|
|||||||
export {
|
export {
|
||||||
TableColumns,
|
TableColumns,
|
||||||
TableRows,
|
TableRows,
|
||||||
TableRows_immutable,
|
|
||||||
TableBodyColumn,
|
TableBodyColumn,
|
||||||
TableBodyRow,
|
TableBodyRow,
|
||||||
TableHighlightedRows,
|
TableHighlightedRows,
|
||||||
@@ -37,8 +36,6 @@ export {
|
|||||||
} from './components/table/types';
|
} from './components/table/types';
|
||||||
export {default as ManagedTable} from './components/table/ManagedTable';
|
export {default as ManagedTable} from './components/table/ManagedTable';
|
||||||
export {ManagedTableProps} from './components/table/ManagedTable';
|
export {ManagedTableProps} from './components/table/ManagedTable';
|
||||||
export {default as ManagedTable_immutable} from './components/table/ManagedTable_immutable';
|
|
||||||
export {ManagedTableProps_immutable} from './components/table/ManagedTable_immutable';
|
|
||||||
|
|
||||||
export {DataValueExtractor, DataInspectorExpanded} from 'flipper-plugin';
|
export {DataValueExtractor, DataInspectorExpanded} from 'flipper-plugin';
|
||||||
export {DataInspector as ManagedDataInspector} from 'flipper-plugin';
|
export {DataInspector as ManagedDataInspector} from 'flipper-plugin';
|
||||||
@@ -135,7 +132,6 @@ export {
|
|||||||
default as SearchableTable,
|
default as SearchableTable,
|
||||||
filterRowsFactory,
|
filterRowsFactory,
|
||||||
} from './components/searchable/SearchableTable';
|
} from './components/searchable/SearchableTable';
|
||||||
export {default as SearchableTable_immutable} from './components/searchable/SearchableTable_immutable';
|
|
||||||
export {SearchableProps} from './components/searchable/Searchable';
|
export {SearchableProps} from './components/searchable/Searchable';
|
||||||
|
|
||||||
export {InspectorSidebar} from './components/elements-inspector/sidebar';
|
export {InspectorSidebar} from './components/elements-inspector/sidebar';
|
||||||
|
|||||||
@@ -7693,11 +7693,6 @@ immer@^9.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.3.tgz#146e2ba8b84d4b1b15378143c2345559915097f4"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.3.tgz#146e2ba8b84d4b1b15378143c2345559915097f4"
|
||||||
integrity sha512-mONgeNSMuyjIe0lkQPa9YhdmTv8P19IeHV0biYhcXhbd5dhdB9HSK93zBpyKjp6wersSUgT5QyU0skmejUVP2A==
|
integrity sha512-mONgeNSMuyjIe0lkQPa9YhdmTv8P19IeHV0biYhcXhbd5dhdB9HSK93zBpyKjp6wersSUgT5QyU0skmejUVP2A==
|
||||||
|
|
||||||
immutable@^4.0.0-rc.12:
|
|
||||||
version "4.0.0-rc.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0-rc.12.tgz#ca59a7e4c19ae8d9bf74a97bdf0f6e2f2a5d0217"
|
|
||||||
integrity sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==
|
|
||||||
|
|
||||||
import-fresh@^2.0.0:
|
import-fresh@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
||||||
|
|||||||
Reference in New Issue
Block a user