JS side of Native Plugins
Summary: Native plugins are plugins that can be written in mobile code alone (java/objc), provided they conform to a template, currently table is the only implemented template. This adds support to flipper for handling them. Reviewed By: danielbuechele Differential Revision: D14502188 fbshipit-source-id: a96be9b06de1cecf7977c4ef2fd05b168f7f1330
This commit is contained in:
committed by
Facebook Github Bot
parent
ba0cdf641d
commit
57a24769e8
@@ -18,6 +18,8 @@ import {ReactiveSocket, PartialResponder} from 'rsocket-core';
|
|||||||
import {performance} from 'perf_hooks';
|
import {performance} from 'perf_hooks';
|
||||||
import {reportPluginFailures} from './utils/metrics';
|
import {reportPluginFailures} from './utils/metrics';
|
||||||
import {default as isProduction} from './utils/isProduction.js';
|
import {default as isProduction} from './utils/isProduction.js';
|
||||||
|
import {registerPlugins} from './reducers/plugins';
|
||||||
|
import createTableNativePlugin from './plugins/TableNativePlugin';
|
||||||
|
|
||||||
const EventEmitter = (require('events'): any);
|
const EventEmitter = (require('events'): any);
|
||||||
const invariant = require('invariant');
|
const invariant = require('invariant');
|
||||||
@@ -175,6 +177,21 @@ export default class Client extends EventEmitter {
|
|||||||
data => data.plugins,
|
data => data.plugins,
|
||||||
);
|
);
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
|
const nativeplugins = plugins
|
||||||
|
.map(plugin => /_nativeplugin_([^_]+)_([^_]+)/.exec(plugin))
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(([id, type, title]) => {
|
||||||
|
// TODO put this in another component, and make the "types" registerable
|
||||||
|
switch (type) {
|
||||||
|
case 'Table':
|
||||||
|
return createTableNativePlugin(id, title);
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
this.store.dispatch(registerPlugins(nativeplugins));
|
||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,42 +8,57 @@
|
|||||||
import {createTablePlugin} from '../createTablePlugin.js';
|
import {createTablePlugin} from '../createTablePlugin.js';
|
||||||
import {FlipperPlugin} from '../plugin.js';
|
import {FlipperPlugin} from '../plugin.js';
|
||||||
|
|
||||||
const PROPS = {
|
const KNOWN_METADATA_PROPS = {
|
||||||
method: 'method',
|
method: 'method',
|
||||||
resetMethod: 'resetMethod',
|
resetMethod: 'resetMethod',
|
||||||
columns: {},
|
columns: {},
|
||||||
columnSizes: {},
|
columnSizes: {},
|
||||||
renderSidebar: () => {},
|
renderSidebar: () => {},
|
||||||
buildRow: () => {},
|
buildRow: () => {
|
||||||
|
return {columns: {}, key: 'someKey'};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DYNAMIC_METADATA_PROPS = {
|
||||||
|
method: 'method',
|
||||||
|
resetMethod: 'resetMethod',
|
||||||
|
id: 'testytest',
|
||||||
|
title: 'TestPlugin',
|
||||||
|
renderSidebar: () => {},
|
||||||
|
buildRow: () => {
|
||||||
|
return {columns: {}, key: 'someKey'};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
test('createTablePlugin returns FlipperPlugin', () => {
|
test('createTablePlugin returns FlipperPlugin', () => {
|
||||||
const tablePlugin = createTablePlugin({...PROPS});
|
const tablePlugin = createTablePlugin({...KNOWN_METADATA_PROPS});
|
||||||
expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin);
|
expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('persistedStateReducer is resetting data', () => {
|
test('persistedStateReducer is resetting data', () => {
|
||||||
const resetMethod = 'resetMethod';
|
const resetMethod = 'resetMethod';
|
||||||
const tablePlugin = createTablePlugin({...PROPS, resetMethod});
|
const tablePlugin = createTablePlugin({...KNOWN_METADATA_PROPS, resetMethod});
|
||||||
|
|
||||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
const ps = {
|
||||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
datas: {'1': {id: '1', rowNumber: 0}},
|
||||||
{
|
rows: [
|
||||||
datas: {'1': {id: '1'}},
|
{
|
||||||
rows: [
|
key: '1',
|
||||||
{
|
columns: {
|
||||||
key: '1',
|
id: {
|
||||||
columns: {
|
value: '1',
|
||||||
id: {
|
|
||||||
value: '1',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
resetMethod,
|
tableMetadata: null,
|
||||||
{},
|
};
|
||||||
);
|
|
||||||
|
if (!tablePlugin.persistedStateReducer) {
|
||||||
|
expect(tablePlugin.persistedStateReducer).toBeDefined();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {rows, datas} = tablePlugin.persistedStateReducer(ps, resetMethod, {});
|
||||||
|
|
||||||
expect(datas).toEqual({});
|
expect(datas).toEqual({});
|
||||||
expect(rows).toEqual([]);
|
expect(rows).toEqual([]);
|
||||||
@@ -51,18 +66,44 @@ test('persistedStateReducer is resetting data', () => {
|
|||||||
|
|
||||||
test('persistedStateReducer is adding data', () => {
|
test('persistedStateReducer is adding data', () => {
|
||||||
const method = 'method';
|
const method = 'method';
|
||||||
const tablePlugin = createTablePlugin({...PROPS, method});
|
const tablePlugin = createTablePlugin({...KNOWN_METADATA_PROPS, method});
|
||||||
const id = '1';
|
const id = '1';
|
||||||
|
|
||||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
const ps = {
|
||||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
datas: {},
|
||||||
{
|
rows: [],
|
||||||
datas: {},
|
tableMetadata: null,
|
||||||
rows: [],
|
};
|
||||||
},
|
|
||||||
method,
|
if (!tablePlugin.persistedStateReducer) {
|
||||||
{id},
|
expect(tablePlugin.persistedStateReducer).toBeDefined();
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
const {rows, datas} = tablePlugin.persistedStateReducer(ps, method, {id});
|
||||||
|
|
||||||
|
expect(rows.length).toBe(1);
|
||||||
|
expect(Object.keys(datas)).toEqual([id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dyn persistedStateReducer is adding data', () => {
|
||||||
|
const method = 'method';
|
||||||
|
const tablePlugin = createTablePlugin({...DYNAMIC_METADATA_PROPS, method});
|
||||||
|
const id = '1';
|
||||||
|
|
||||||
|
const ps = {
|
||||||
|
datas: {},
|
||||||
|
rows: [],
|
||||||
|
tableMetadata: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!tablePlugin.persistedStateReducer) {
|
||||||
|
expect(tablePlugin.persistedStateReducer).toBeDefined();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {rows, datas} = tablePlugin.persistedStateReducer(ps, method, {
|
||||||
|
id,
|
||||||
|
columns: {},
|
||||||
|
});
|
||||||
|
|
||||||
expect(rows.length).toBe(1);
|
expect(rows.length).toBe(1);
|
||||||
expect(Object.keys(datas)).toEqual([id]);
|
expect(Object.keys(datas)).toEqual([id]);
|
||||||
|
|||||||
@@ -171,7 +171,11 @@ class PluginDebugger extends Component<Props> {
|
|||||||
// bundled plugins are loaded from the defaultPlugins directory within
|
// bundled plugins are loaded from the defaultPlugins directory within
|
||||||
// Flipper's package.
|
// Flipper's package.
|
||||||
const externalPluginPath = (p: PluginDefinition) =>
|
const externalPluginPath = (p: PluginDefinition) =>
|
||||||
p.out.startsWith('./defaultPlugins/') ? null : p.entry;
|
p.out
|
||||||
|
? p.out.startsWith('./defaultPlugins/')
|
||||||
|
? null
|
||||||
|
: p.entry
|
||||||
|
: 'Native Plugin';
|
||||||
|
|
||||||
this.props.gatekeepedPlugins.forEach(plugin =>
|
this.props.gatekeepedPlugins.forEach(plugin =>
|
||||||
rows.push(
|
rows.push(
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import type {
|
|||||||
TableRows,
|
TableRows,
|
||||||
TableColumnSizes,
|
TableColumnSizes,
|
||||||
TableColumns,
|
TableColumns,
|
||||||
|
TableColumnOrder,
|
||||||
|
TableColumnOrderVal,
|
||||||
|
TableBodyRow,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
|
|
||||||
import FlexColumn from './ui/components/FlexColumn';
|
import FlexColumn from './ui/components/FlexColumn';
|
||||||
@@ -24,20 +27,45 @@ type ID = string;
|
|||||||
|
|
||||||
type RowData = {
|
type RowData = {
|
||||||
id: ID,
|
id: ID,
|
||||||
|
columns?: {[key: string]: any},
|
||||||
|
details?: Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Numbered<T> = {
|
||||||
|
...T,
|
||||||
|
id: ID,
|
||||||
|
rowNumber: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type TableMetadata = {
|
||||||
|
columns: TableColumns,
|
||||||
|
columnSizes?: TableColumnSizes,
|
||||||
|
columnOrder?: Array<TableColumnOrderVal>,
|
||||||
|
filterableColumns?: Set<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props<T> = {|
|
type Props<T> = {|
|
||||||
method: string,
|
method: string,
|
||||||
resetMethod?: string,
|
resetMethod?: string,
|
||||||
columns: TableColumns,
|
...
|
||||||
columnSizes: TableColumnSizes,
|
| {|
|
||||||
|
columns: TableColumns,
|
||||||
|
columnSizes?: TableColumnSizes,
|
||||||
|
columnOrder?: TableColumnOrder,
|
||||||
|
filterableColumns?: Set<string>,
|
||||||
|
|}
|
||||||
|
| {|
|
||||||
|
id: string,
|
||||||
|
title: string,
|
||||||
|
|},
|
||||||
renderSidebar: (row: T) => any,
|
renderSidebar: (row: T) => any,
|
||||||
buildRow: (row: T) => any,
|
buildRow: (row: T, previousData: ?T) => TableBodyRow,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
type PersistedState<T> = {|
|
type PersistedState<T> = {|
|
||||||
rows: TableRows,
|
rows: TableRows,
|
||||||
datas: {[key: ID]: T},
|
datas: {[key: ID]: Numbered<T>},
|
||||||
|
tableMetadata: ?TableMetadata,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
type State = {|
|
type State = {|
|
||||||
@@ -53,6 +81,9 @@ type State = {|
|
|||||||
* of data objects or a single data object. Each data object represents a row in the table which is
|
* of data objects or a single data object. Each data object represents a row in the table which is
|
||||||
* build by calling the `buildRow` function argument.
|
* build by calling the `buildRow` function argument.
|
||||||
*
|
*
|
||||||
|
* The component can be constructed directly with the table metadata in props,
|
||||||
|
or if omitted, will call the mobile plugin to dynamically determine the table metadata.
|
||||||
|
*
|
||||||
* An optional resetMethod argument can be provided which will replace the current rows with the
|
* An optional resetMethod argument can be provided which will replace the current rows with the
|
||||||
* data provided. This is useful when connecting to Flipper for this first time, or reconnecting to
|
* data provided. This is useful when connecting to Flipper for this first time, or reconnecting to
|
||||||
* the client in an unknown state.
|
* the client in an unknown state.
|
||||||
@@ -60,17 +91,27 @@ type State = {|
|
|||||||
export function createTablePlugin<T: RowData>(props: Props<T>) {
|
export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||||
return class extends FlipperPlugin<State, *, PersistedState<T>> {
|
return class extends FlipperPlugin<State, *, PersistedState<T>> {
|
||||||
static keyboardActions = ['clear', 'createPaste'];
|
static keyboardActions = ['clear', 'createPaste'];
|
||||||
|
static id = props.id || '';
|
||||||
|
static title = props.title || '';
|
||||||
|
|
||||||
static defaultPersistedState: PersistedState<T> = {
|
static defaultPersistedState: PersistedState<T> = {
|
||||||
rows: [],
|
rows: [],
|
||||||
datas: {},
|
datas: {},
|
||||||
|
tableMetadata: props.columns
|
||||||
|
? {
|
||||||
|
columns: props.columns,
|
||||||
|
columnSizes: props.columnSizes,
|
||||||
|
columnOrder: props.columnOrder,
|
||||||
|
filterableColumns: props.filterableColumns,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
static persistedStateReducer = (
|
static persistedStateReducer = (
|
||||||
persistedState: PersistedState<T>,
|
persistedState: PersistedState<T>,
|
||||||
method: string,
|
method: string,
|
||||||
payload: T | Array<T>,
|
payload: T | Array<T>,
|
||||||
): $Shape<PersistedState<RowData>> => {
|
): $Shape<PersistedState<T>> => {
|
||||||
if (method === props.method) {
|
if (method === props.method) {
|
||||||
const newRows = [];
|
const newRows = [];
|
||||||
const newData = {};
|
const newData = {};
|
||||||
@@ -80,17 +121,33 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
if (data.id == null) {
|
if (data.id == null) {
|
||||||
console.warn('The data sent did not contain an ID.', data);
|
console.warn('The data sent did not contain an ID.', data);
|
||||||
}
|
}
|
||||||
|
const previousRowData: ?Numbered<T> = persistedState.datas[data.id];
|
||||||
|
const newRow = props.buildRow(data, previousRowData);
|
||||||
if (persistedState.datas[data.id] == null) {
|
if (persistedState.datas[data.id] == null) {
|
||||||
newData[data.id] = data;
|
newData[data.id] = {
|
||||||
newRows.push(props.buildRow(data));
|
...data,
|
||||||
|
rowNumber: persistedState.rows.length + newRows.length,
|
||||||
|
};
|
||||||
|
newRows.push(newRow);
|
||||||
|
} else {
|
||||||
|
persistedState.rows = persistedState.rows
|
||||||
|
.slice(0, persistedState.datas[data.id].rowNumber)
|
||||||
|
.concat(
|
||||||
|
[newRow],
|
||||||
|
persistedState.rows.slice(
|
||||||
|
persistedState.datas[data.id].rowNumber + 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
...persistedState,
|
||||||
datas: {...persistedState.datas, ...newData},
|
datas: {...persistedState.datas, ...newData},
|
||||||
rows: [...persistedState.rows, ...newRows],
|
rows: [...persistedState.rows, ...newRows],
|
||||||
};
|
};
|
||||||
} else if (method === props.resetMethod) {
|
} else if (method === props.resetMethod) {
|
||||||
return {
|
return {
|
||||||
|
...persistedState,
|
||||||
rows: [],
|
rows: [],
|
||||||
datas: {},
|
datas: {},
|
||||||
};
|
};
|
||||||
@@ -103,6 +160,23 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.getTableMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableMetadata = () => {
|
||||||
|
if (!this.props.persistedState.tableMetadata) {
|
||||||
|
this.client.call('getMetadata').then(metadata => {
|
||||||
|
this.props.setPersistedState({
|
||||||
|
tableMetadata: {
|
||||||
|
...metadata,
|
||||||
|
filterableColumns: new Set(metadata.filterableColumns),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onKeyboardAction = (action: string) => {
|
onKeyboardAction = (action: string) => {
|
||||||
if (action === 'clear') {
|
if (action === 'clear') {
|
||||||
this.clear();
|
this.clear();
|
||||||
@@ -122,9 +196,16 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createPaste = () => {
|
createPaste = () => {
|
||||||
|
if (!this.props.persistedState.tableMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let paste = '';
|
let paste = '';
|
||||||
const mapFn = row =>
|
const mapFn = row =>
|
||||||
Object.keys(props.columns)
|
(
|
||||||
|
(this.props.persistedState.tableMetadata &&
|
||||||
|
Object.keys(this.props.persistedState.tableMetadata.columns)) ||
|
||||||
|
[]
|
||||||
|
)
|
||||||
.map(key => textContent(row.columns[key].value))
|
.map(key => textContent(row.columns[key].value))
|
||||||
.join('\t');
|
.join('\t');
|
||||||
|
|
||||||
@@ -147,8 +228,36 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We don't necessarily have the table metadata at the time when buildRow
|
||||||
|
// is being used. This includes presentation layer info like which
|
||||||
|
// columns should be filterable. This does a pass over the built rows and
|
||||||
|
// applies that presentation layer information.
|
||||||
|
applyMetadataToRows(rows: TableRows): TableRows {
|
||||||
|
if (!this.props.persistedState.tableMetadata) {
|
||||||
|
console.error(
|
||||||
|
'applyMetadataToRows called without tableMetadata present',
|
||||||
|
);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
return rows.map(r => {
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
columns: Object.keys(r.columns).reduce((map, columnName) => {
|
||||||
|
map[columnName].isFilterable =
|
||||||
|
this.props.persistedState.tableMetadata &&
|
||||||
|
this.props.persistedState.tableMetadata.filterableColumns
|
||||||
|
? this.props.persistedState.tableMetadata.filterableColumns.has(
|
||||||
|
columnName,
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
return map;
|
||||||
|
}, r.columns),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderSidebar = () => {
|
renderSidebar = () => {
|
||||||
const {renderSidebar} = props;
|
const renderSidebar = props.renderSidebar;
|
||||||
const {selectedIds} = this.state;
|
const {selectedIds} = this.state;
|
||||||
const {datas} = this.props.persistedState;
|
const {datas} = this.props.persistedState;
|
||||||
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
|
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
|
||||||
@@ -161,7 +270,14 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {columns, columnSizes} = props;
|
if (!this.props.persistedState.tableMetadata) {
|
||||||
|
return 'Loading...';
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
columnSizes,
|
||||||
|
columnOrder,
|
||||||
|
} = this.props.persistedState.tableMetadata;
|
||||||
const {rows} = this.props.persistedState;
|
const {rows} = this.props.persistedState;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -172,10 +288,11 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
floating={false}
|
floating={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
columnSizes={columnSizes}
|
columnSizes={columnSizes}
|
||||||
|
columnOrder={columnOrder}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
onRowHighlighted={this.onRowHighlighted}
|
onRowHighlighted={this.onRowHighlighted}
|
||||||
multiHighlight={true}
|
multiHighlight={true}
|
||||||
rows={rows}
|
rows={this.applyMetadataToRows(rows)}
|
||||||
stickyBottom={true}
|
stickyBottom={true}
|
||||||
actions={<Button onClick={this.clear}>Clear Table</Button>}
|
actions={<Button onClick={this.clear}>Clear Table</Button>}
|
||||||
/>
|
/>
|
||||||
|
|||||||
69
src/plugins/TableNativePlugin.js
Normal file
69
src/plugins/TableNativePlugin.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ManagedDataInspector, Panel} from 'flipper';
|
||||||
|
import {createTablePlugin} from '../createTablePlugin';
|
||||||
|
|
||||||
|
type RowData = {
|
||||||
|
id: string,
|
||||||
|
columns: {},
|
||||||
|
details: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildRow(rowData: RowData, previousRowData: ?RowData) {
|
||||||
|
if (!rowData.columns) {
|
||||||
|
throw new Error('defaultBuildRow used with incorrect data format.');
|
||||||
|
}
|
||||||
|
const oldColumns =
|
||||||
|
previousRowData && previousRowData.columns
|
||||||
|
? Object.keys(previousRowData.columns).reduce((map, key) => {
|
||||||
|
if (key !== previousRowData?.id) {
|
||||||
|
map[key] = {
|
||||||
|
value: (previousRowData?.columns || {})[key].value,
|
||||||
|
isFilterable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, {})
|
||||||
|
: {};
|
||||||
|
const columns = Object.keys(rowData.columns).reduce((map, key) => {
|
||||||
|
if (key !== rowData.id) {
|
||||||
|
map[key] = {
|
||||||
|
value: rowData.columns && rowData.columns[key].value,
|
||||||
|
isFilterable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, oldColumns);
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
key: rowData.id,
|
||||||
|
copyText: JSON.stringify(rowData),
|
||||||
|
filterValue: rowData.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSidebar(rowData: RowData) {
|
||||||
|
if (!rowData.details) {
|
||||||
|
throw new Error('defaultRenderSidebar used with incorrect data format.');
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Panel floating={false} heading={'Details'}>
|
||||||
|
<ManagedDataInspector data={rowData.details} expandRoot={true} />
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createTableNativePlugin(id: string, title: string) {
|
||||||
|
return createTablePlugin({
|
||||||
|
method: 'updateRows',
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
renderSidebar: renderSidebar,
|
||||||
|
buildRow: buildRow,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -130,21 +130,16 @@ export default class TableRow extends React.PureComponent<Props> {
|
|||||||
{columnKeys.map(key => {
|
{columnKeys.map(key => {
|
||||||
const col = row.columns[key];
|
const col = row.columns[key];
|
||||||
|
|
||||||
if (col == null) {
|
const isFilterable = col?.isFilterable || false;
|
||||||
throw new Error(
|
const value = col?.value;
|
||||||
`Trying to access column "${key}" which does not exist on row. Make sure buildRow is returning a valid row.`,
|
const title = col?.title;
|
||||||
);
|
|
||||||
}
|
|
||||||
const isFilterable = col.isFilterable || false;
|
|
||||||
const value = col ? col.value : '';
|
|
||||||
const title = col ? col.title : '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableBodyColumnContainer
|
<TableBodyColumnContainer
|
||||||
key={key}
|
key={key}
|
||||||
title={title}
|
title={title}
|
||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
justifyContent={col.align || 'flex-start'}
|
justifyContent={col?.align || 'flex-start'}
|
||||||
width={normaliseColumnWidth(columnSizes[key])}>
|
width={normaliseColumnWidth(columnSizes[key])}>
|
||||||
{isFilterable && onAddFilter != null ? (
|
{isFilterable && onAddFilter != null ? (
|
||||||
<FilterRow addFilter={onAddFilter} filterKey={key}>
|
<FilterRow addFilter={onAddFilter} filterKey={key}>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const MINIMUM_COLUMN_WIDTH = 100;
|
|||||||
export const DEFAULT_COLUMN_WIDTH = 200;
|
export const DEFAULT_COLUMN_WIDTH = 200;
|
||||||
export const DEFAULT_ROW_HEIGHT = 23;
|
export const DEFAULT_ROW_HEIGHT = 23;
|
||||||
|
|
||||||
type TableColumnOrderVal = {
|
export type TableColumnOrderVal = {
|
||||||
key: string,
|
key: string,
|
||||||
visible: boolean,
|
visible: boolean,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type {
|
|||||||
TableHighlightedRows,
|
TableHighlightedRows,
|
||||||
TableRowSortOrder,
|
TableRowSortOrder,
|
||||||
TableColumnOrder,
|
TableColumnOrder,
|
||||||
|
TableColumnOrderVal,
|
||||||
TableColumnSizes,
|
TableColumnSizes,
|
||||||
} from './components/table/types.js';
|
} from './components/table/types.js';
|
||||||
export {default as ManagedTable} from './components/table/ManagedTable.js';
|
export {default as ManagedTable} from './components/table/ManagedTable.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user