Files
flipper/desktop/flipper-plugin/src/utils/createTablePlugin.tsx
Andrey Goncharov 97b8b8a1c4 Split flipper-plugin package
Summary:
flipper-server-companion depends on flipper-plugin. flipper-plugin includes dependencies that run only in a browser. Splitting flipper-plugin into core and browser packages helps to avoid including browser-only dependencies into flipper-server bundle.
As a result, bundle size could be cut in half. Subsequently, RSS usage drops as there is twice as less code to process for V8.

Note: it currently breaks external flipper-data-source package. It will be restored in subsequent diffs

Reviewed By: lblasa

Differential Revision: D38658285

fbshipit-source-id: 751b11fa9f3a2d938ce166687b8310ba8b059dee
2022-09-15 10:02:19 -07:00

151 lines
4.6 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and 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 {notification, Typography} from 'antd';
import {DataSource} from 'flipper-plugin-core';
import React from 'react';
import {PluginClient} from 'flipper-plugin-core';
import {usePlugin} from '../plugin/PluginContext';
import {createState} from 'flipper-plugin-core';
import {DataTableColumn} from '../ui/data-table/DataTable';
import {MasterDetail} from '../ui/MasterDetail';
import {createDataSource} from 'flipper-plugin-core';
type PluginResult<Raw, Row> = {
plugin(client: PluginClient<Record<string, Raw | {}>>): {
rows: DataSource<Row, Row[keyof Row]>;
};
Component(): React.ReactElement;
};
/**
* `createTablePlugin` creates a plugin that handles receiving data from the client and
* displaying it in a table. The table handles selection of items, sorting, filtering and
* rendering a sidebar where more detailed information can be presented about the selected row.
*
* The plugin expects the be able to subscribe to the `method` argument and receive either single data objects.
* Each data object represents a row in the table.
*
* 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 the client in an unknown state.
*
* Since the `createTablePlugin` defines both the `plugin` and `Component` for the plugin in one go,
* making the result is most easily done by using `module.exports = createTablePlugin(....)` so that both are exported from the plugin package.
*/
export function createTablePlugin<Row extends object>(props: {
method: string;
resetMethod?: string;
columns: DataTableColumn<Row>[];
renderSidebar?: (record: Row) => any;
key?: keyof Row;
onCopyRows?(records: Row[]): string;
}): PluginResult<Row, Row>;
export function createTablePlugin<
Raw extends object,
Row extends object = Raw,
>(props: {
buildRow: (record: Raw) => Row;
method: string;
resetMethod?: string;
columns: DataTableColumn<Row>[];
renderSidebar?: (record: Row) => any;
key?: keyof Raw;
onCopyRows?(records: Row[]): string;
}): PluginResult<Raw, Row>;
export function createTablePlugin<
Raw extends object,
Method extends string,
ResetMethod extends string,
Row extends object = Raw,
>(props: {
method: Method;
resetMethod?: ResetMethod;
columns: DataTableColumn<Row>[];
renderSidebar?: (record: Row) => any;
buildRow?: (record: Raw) => Row;
key?: keyof Raw;
onCopyRows?(records: Row[]): string;
}) {
function plugin(
client: PluginClient<Record<Method, Raw> & Record<ResetMethod, {}>, {}>,
) {
const rows = createDataSource<Row, keyof Row>([], {
persist: 'rows',
key: props.key as keyof Row | undefined,
});
const selection = createState<undefined | Row>(undefined);
const isPaused = createState(false);
client.onMessage(props.method, (event) => {
if (isPaused.get()) {
return;
}
const record = props.buildRow
? props.buildRow(event)
: (event as any as Row);
if (props.key) {
rows.upsert(record);
} else {
rows.append(record);
}
});
if (props.resetMethod) {
client.onMessage(props.resetMethod, () => {
rows.clear();
});
}
// help plugin authors with finding out what the events and data shape is from the plugin
const unhandledMessagesSeen = new Set<string>();
client.onUnhandledMessage((message, params) => {
if (unhandledMessagesSeen.has(message)) {
return;
}
unhandledMessagesSeen.add(message);
notification.warn({
message: 'Unhandled message: ' + message,
description: (
<Typography.Paragraph>
<pre>{JSON.stringify(params, null, 2)}</pre>
</Typography.Paragraph>
),
});
});
return {
selection,
rows,
isPaused,
};
}
function SidebarComponent({record}: {record: Row}) {
return props.renderSidebar!(record);
}
function Component() {
const instance = usePlugin(plugin);
return (
<MasterDetail<Row>
columns={props.columns}
dataSource={instance.rows}
sidebarComponent={props.renderSidebar ? SidebarComponent : undefined}
selection={instance.selection}
isPaused={instance.isPaused}
enableMenuEntries
enableClear
onCopyRows={props.onCopyRows}
/>
);
}
return {plugin, Component};
}