Remove Newsfeed Story Inspector and native plugin mechanism

Summary: Changelog: The 'nativeplugins' on Android are no longer supported

Reviewed By: jknoxville

Differential Revision: D29163281

fbshipit-source-id: fb4032f240fc306608fe57479f3124d4e7a3400f
This commit is contained in:
Michel Weststrate
2021-06-21 05:38:26 -07:00
committed by Facebook GitHub Bot
parent 244e3a434c
commit 07199323d1
20 changed files with 2 additions and 1267 deletions

View File

@@ -1,14 +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.
*/
package com.facebook.flipper.nativeplugins;
public interface NativePlugin {
String getTitle();
RawNativePlugin asFlipperPlugin();
}

View File

@@ -1,23 +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.
*/
package com.facebook.flipper.nativeplugins;
import com.facebook.flipper.core.FlipperClient;
public class NativePluginRegistry {
private final FlipperClient client;
public NativePluginRegistry(FlipperClient client) {
this.client = client;
}
public void register(final NativePlugin plugin) {
client.addPlugin(plugin.asFlipperPlugin());
}
}

View File

@@ -1,38 +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.
*/
package com.facebook.flipper.nativeplugins;
import com.facebook.flipper.core.FlipperPlugin;
/**
* Subclass of {@link FlipperPlugin} for mobile-defined plugins that conform to a template.
* Implementations should call {@link #RawNativePlugin(String, String)} to specify which template
* will be used. See {@link com.facebook.flipper.nativeplugins.table.TablePlugin} for an example
* subclass.
*/
public abstract class RawNativePlugin implements FlipperPlugin {
private final String pluginType;
private final String id;
/**
* Call super() inside subclass constructors to provide the template name and id of the concrete
* plugin instance.
*
* @param pluginType This needs to correspond to a plugin template defined in Flipper.
* @param id This will uniquely
*/
public RawNativePlugin(final String pluginType, final String id) {
this.pluginType = pluginType;
this.id = id;
}
@Override
public final String getId() {
return "_nativeplugin_" + pluginType + "_" + id;
}
}

View File

@@ -1,29 +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.
*/
package com.facebook.flipper.nativeplugins.components;
import com.facebook.flipper.core.FlipperObject;
public class JsonSection implements UISection {
private final String title;
private final FlipperObject content;
public JsonSection(String title, FlipperObject content) {
this.title = title;
this.content = content;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder()
.put("title", title)
.put("type", "json")
.put("content", content)
.build();
}
}

View File

@@ -1,24 +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.
*/
package com.facebook.flipper.nativeplugins.components;
import com.facebook.flipper.core.FlipperArray;
public class Sidebar {
private final FlipperArray.Builder sections = new FlipperArray.Builder();
public Sidebar addSection(UISection section) {
sections.put(section.serialize());
return this;
}
public FlipperArray serialize() {
return sections.build();
}
}

View File

@@ -1,29 +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.
*/
package com.facebook.flipper.nativeplugins.components;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperObject;
public class ToolbarSection implements UISection {
private FlipperArray.Builder items = new FlipperArray.Builder();
public ToolbarSection addLink(String label, String destination) {
items.put(
new FlipperObject.Builder()
.put("type", "link")
.put("label", label)
.put("destination", destination));
return this;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "toolbar").put("items", items.build()).build();
}
}

View File

@@ -1,15 +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.
*/
package com.facebook.flipper.nativeplugins.components;
import com.facebook.flipper.core.FlipperObject;
interface UISection {
FlipperObject serialize();
}

View File

@@ -1,76 +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.
*/
package com.facebook.flipper.nativeplugins.table;
public class Column {
public final String id;
final String displayName;
final String displayWidth;
final boolean showByDefault;
final boolean isFilterable;
Column(
String id,
String displayName,
String displayWidth,
boolean showByDefault,
boolean isFilterable) {
if (id == null) {
throw new IllegalArgumentException("id must not be null");
}
if (displayName == null) {
throw new IllegalArgumentException("displayName must not be null");
}
this.id = id;
this.displayName = displayName;
this.displayWidth = displayWidth;
this.showByDefault = showByDefault;
this.isFilterable = isFilterable;
}
public static class Builder {
private final String id;
private String displayName;
private String displayWidth;
private boolean showByDefault = true;
private boolean isFilterable = false;
public Builder(String id) {
this.id = id;
}
public Builder displayName(String displayName) {
this.displayName = displayName;
return this;
}
public Builder displayWidthPx(int displayWidth) {
this.displayWidth = Integer.toString(displayWidth);
return this;
}
public Builder displayWidthPercent(int displayWidth) {
this.displayWidth = Integer.toString(displayWidth) + "%";
return this;
}
public Builder showByDefault(boolean showByDefault) {
this.showByDefault = showByDefault;
return this;
}
public Builder isFilterable(boolean isFilterable) {
this.isFilterable = isFilterable;
return this;
}
public Column build() {
return new Column(id, displayName, displayWidth, showByDefault, isFilterable);
}
}
}

View File

@@ -1,25 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import java.util.List;
public interface QueryableTableRowProvider {
TableQueryResult getQueryResults(String query);
class TableQueryResult {
final TableMetadata metadata;
final List<? extends TableRow> results;
public TableQueryResult(final TableMetadata metadata, final List<? extends TableRow> results) {
this.metadata = metadata;
this.results = results;
}
}
}

View File

@@ -1,87 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import androidx.annotation.Nullable;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.nativeplugins.components.ToolbarSection;
public class TableMetadata {
final Column[] mColumns;
final QueryableTableRowProvider mResponder;
final ToolbarSection mTopToolbar;
final ToolbarSection mBottomToolbar;
FlipperObject serialize() {
final FlipperObject.Builder columns = new FlipperObject.Builder();
final FlipperObject.Builder columnSizes = new FlipperObject.Builder();
final FlipperArray.Builder columnOrder = new FlipperArray.Builder();
final FlipperArray.Builder filterableColumns = new FlipperArray.Builder();
for (Column c : mColumns) {
columns.put(c.id, new FlipperObject.Builder().put("value", c.displayName).build());
columnSizes.put(c.id, c.displayWidth);
columnOrder.put(new FlipperObject.Builder().put("key", c.id).put("visible", c.showByDefault));
if (c.isFilterable) {
filterableColumns.put(c.id);
}
}
return new FlipperObject.Builder()
.put("columns", columns.build())
.put("columnSizes", columnSizes.build())
.put("columnOrder", columnOrder.build())
.put("filterableColumns", filterableColumns.build())
.put("topToolbar", mTopToolbar != null ? mTopToolbar.serialize() : null)
.put("bottomToolbar", mBottomToolbar != null ? mBottomToolbar.serialize() : null)
.build();
}
private TableMetadata(
@Nullable Column[] columns,
@Nullable QueryableTableRowProvider queryResponder,
@Nullable ToolbarSection topToolbar,
@Nullable ToolbarSection bottomToolbar) {
this.mColumns = columns == null ? new Column[] {} : columns;
this.mResponder = queryResponder;
this.mTopToolbar = topToolbar;
this.mBottomToolbar = bottomToolbar;
}
public static class Builder {
private Column[] columns;
private QueryableTableRowProvider queryResponder;
private ToolbarSection topToolbar;
private ToolbarSection bottomToolbar;
public Builder columns(Column... columns) {
this.columns = columns;
return this;
}
public Builder queryResponder(QueryableTableRowProvider responder) {
this.queryResponder = responder;
return this;
}
public Builder topToolbar(ToolbarSection bar) {
this.topToolbar = bar;
return this;
}
public Builder bottomToolbar(ToolbarSection bar) {
this.bottomToolbar = bar;
return this;
}
public TableMetadata build() {
return new TableMetadata(columns, queryResponder, topToolbar, bottomToolbar);
}
}
}

View File

@@ -1,43 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import com.facebook.flipper.core.FlipperConnection;
import com.facebook.flipper.nativeplugins.NativePlugin;
import com.facebook.flipper.nativeplugins.RawNativePlugin;
public abstract class TablePlugin implements NativePlugin {
public abstract TableMetadata getMetadata();
public void onConnect(TableRowDisplay display) {}
public void onDisconnect() {};
@Override
public final RawNativePlugin asFlipperPlugin() {
return new RawNativePlugin("Table", getTitle()) {
@Override
public void onConnect(final FlipperConnection connection) throws Exception {
final TableRowDisplay display = new TableRowDisplayImpl(connection, TablePlugin.this);
TablePlugin.this.onConnect(display);
}
@Override
public void onDisconnect() throws Exception {
TablePlugin.this.onDisconnect();
}
@Override
public boolean runInBackground() {
return false;
}
};
}
}

View File

@@ -1,120 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.nativeplugins.components.Sidebar;
import java.util.Map;
public abstract class TableRow {
public interface Value {
FlipperObject serialize();
}
public static class StringValue implements Value {
private String val;
public StringValue(String s) {
this.val = s;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "string").put("value", val).build();
}
}
public static class IntValue implements Value {
private int val;
public IntValue(int i) {
this.val = i;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "int").put("value", val).build();
}
}
public static class BooleanValue implements Value {
private boolean val;
public BooleanValue(boolean i) {
this.val = i;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "boolean").put("value", val).build();
}
}
public static class TimeValue implements Value {
private long millis;
public TimeValue(long millis) {
this.millis = millis;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "time").put("value", millis).build();
}
}
public static class DurationValue implements Value {
private long millis;
public DurationValue(long millis) {
this.millis = millis;
}
@Override
public FlipperObject serialize() {
return new FlipperObject.Builder().put("type", "duration").put("value", millis).build();
}
}
final String id;
final Map<Column, ? extends Value> values;
final Sidebar sidebar;
public TableRow(String id, Map<Column, ? extends Value> values, Sidebar sidebar) {
this.id = id;
this.values = values;
this.sidebar = sidebar;
}
final FlipperObject serialize() {
FlipperObject.Builder columnsObject = new FlipperObject.Builder();
for (Map.Entry<Column, ? extends Value> e : values.entrySet()) {
columnsObject.put(e.getKey().id, e.getValue().serialize());
}
columnsObject.put("id", id);
return new FlipperObject.Builder()
.put("columns", columnsObject.build())
.put("sidebar", sidebar != null ? sidebar.serialize() : null)
.put("id", id)
.build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
return serialize().equals(((TableRow) o).serialize());
}
}

View File

@@ -1,16 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import java.util.List;
public interface TableRowDisplay {
void updateRow(TableRow row, TableMetadata tableMetadata);
void updateRows(List<? extends TableRow> rows, TableMetadata tableMetadata);
}

View File

@@ -1,49 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperConnection;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.core.FlipperReceiver;
import com.facebook.flipper.core.FlipperResponder;
import java.util.List;
public class TableRowDisplayImpl implements TableRowDisplay {
private final FlipperConnection mConnection;
TableRowDisplayImpl(FlipperConnection connection, final TablePlugin subscriber) {
this.mConnection = connection;
connection.receive(
"getMetadata",
new FlipperReceiver() {
@Override
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
final FlipperObject metadata = subscriber.getMetadata().serialize();
responder.success(metadata);
}
});
}
@Override
public final void updateRow(TableRow row, TableMetadata tableMetadata) {
final FlipperArray.Builder array = new FlipperArray.Builder();
array.put(row.serialize());
this.mConnection.send("updateRows", array.build());
}
@Override
public final void updateRows(List<? extends TableRow> rows, TableMetadata tableMetadata) {
final FlipperArray.Builder array = new FlipperArray.Builder();
for (TableRow r : rows) {
array.put(r.serialize());
}
this.mConnection.send("updateRows", array.build());
}
}

View File

@@ -1,21 +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.
*/
package com.facebook.flipper.nativeplugins.table;
public class MockTablePlugin extends TablePlugin {
@Override
public TableMetadata getMetadata() {
return new TableMetadata.Builder().columns().build();
}
@Override
public String getTitle() {
return "Mock Table Plugin";
}
}

View File

@@ -1,17 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import com.facebook.flipper.nativeplugins.components.Sidebar;
import java.util.Map;
public class MockTableRow extends TableRow {
public MockTableRow(String id, Map<Column, ? extends Value> values, Sidebar sidebar) {
super(id, values, sidebar);
}
}

View File

@@ -1,19 +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.
*/
package com.facebook.flipper.nativeplugins.table;
public class TableMetadataTestUtils {
public static Column[] getColumns(TableMetadata tableMetadata) {
return tableMetadata.mColumns;
}
public static QueryableTableRowProvider getQueryResponder(TableMetadata tableMetadata) {
return tableMetadata.mResponder;
}
}

View File

@@ -1,98 +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.
*/
package com.facebook.flipper.nativeplugins.table;
import static org.junit.Assert.assertEquals;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.nativeplugins.components.Sidebar;
import com.facebook.flipper.testing.FlipperConnectionMock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class TableRowDisplayImplTest {
FlipperConnectionMock mConnection;
MockTablePlugin mockTablePlugin;
static final Column NAME_COLUMN =
new Column.Builder("name")
.displayName("Name")
.displayWidthPercent(90)
.isFilterable(true)
.showByDefault(false)
.build();
static final Column AGE_COLUMN =
new Column.Builder("age")
.displayName("Age")
.displayWidthPercent(50)
.isFilterable(false)
.showByDefault(true)
.build();
@Before
public void setup() {
mConnection = new FlipperConnectionMock();
mockTablePlugin = new MockTablePlugin();
}
private TableRow row(String id, String name, int age) {
Map<Column, TableRow.Value> map1 = new HashMap<>();
map1.put(NAME_COLUMN, new TableRow.StringValue(name));
map1.put(AGE_COLUMN, new TableRow.IntValue(age));
return new MockTableRow(id, map1, new Sidebar());
}
@Test
public void testUpdateRow() {
TableRowDisplay display = new TableRowDisplayImpl(mConnection, mockTablePlugin);
display.updateRow(row("row1", "santa", 55), null);
assertEquals(1, mConnection.sent.get("updateRows").size());
FlipperArray rowArray = (FlipperArray) mConnection.sent.get("updateRows").get(0);
assertEquals(1, rowArray.length());
FlipperObject updatedRow = rowArray.getObject(0);
assertEquals(serializedRow("row1", "santa", 55), updatedRow);
}
@Test
public void testUpdateRows() {
TableRowDisplay display = new TableRowDisplayImpl(mConnection, mockTablePlugin);
List<TableRow> rows = new ArrayList<>();
rows.add(row("row1", "santa", 55));
rows.add(row("row2", "elf", 15));
display.updateRows(rows, null);
assertEquals(1, mConnection.sent.get("updateRows").size());
FlipperArray rowArray = (FlipperArray) mConnection.sent.get("updateRows").get(0);
assertEquals(2, rowArray.length());
assertEquals(serializedRow("row1", "santa", 55), rowArray.getObject(0));
assertEquals(serializedRow("row2", "elf", 15), rowArray.getObject(1));
}
private FlipperObject serializedRow(String id, String name, int age) {
return new FlipperObject(
"{\"columns\":{\"name\":{\"type\":\"string\",\"value\":\""
+ name
+ "\"},\"id\":\""
+ id
+ "\",\"age\":{\"type\":\"int\",\"value\":"
+ age
+ "}},\"sidebar\":[],\"id\":\""
+ id
+ "\"}");
}
}

View File

@@ -18,7 +18,6 @@ import {reportPluginFailures} from './utils/metrics';
import {notNull} from './utils/typeUtils'; import {notNull} from './utils/typeUtils';
import {default as isProduction} from './utils/isProduction'; import {default as isProduction} from './utils/isProduction';
import {registerPlugins} from './reducers/plugins'; import {registerPlugins} from './reducers/plugins';
import createTableNativePlugin from './plugins/TableNativePlugin';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import invariant from 'invariant'; import invariant from 'invariant';
import { import {
@@ -241,27 +240,11 @@ export default class Client extends EventEmitter {
// get the supported plugins // get the supported plugins
async loadPlugins(): Promise<Plugins> { async loadPlugins(): Promise<Plugins> {
const plugins = await this.rawCall<{plugins: Plugins}>( const {plugins} = await this.rawCall<{plugins: Plugins}>(
'getPlugins', 'getPlugins',
false, false,
).then((data) => data.plugins); );
this.plugins = plugins; this.plugins = plugins;
const nativeplugins = plugins
.map((plugin) => /_nativeplugin_([^_]+)_([^_]+)/.exec(plugin))
.filter(notNull)
.map(([id, type, title]) => {
// TODO put this in another component, and make the "types" registerable
console.warn(`TableNative plugins are deprecated: ${id}`);
switch (type) {
case 'Table':
return createTableNativePlugin(id, title);
default: {
return null;
}
}
})
.filter(Boolean);
this.store.dispatch(registerPlugins(nativeplugins as any));
return plugins; return plugins;
} }

View File

@@ -1,505 +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 {DataInspector} from 'flipper-plugin';
import Panel from '../ui/components/Panel';
import {colors} from '../ui/components/colors';
import styled from '@emotion/styled';
import Text from '../ui/components/Text';
import {Spacer} from '../ui/components/Toolbar';
import Button from '../ui/components/Button';
import Select from '../ui/components/Select';
import ErrorBlock from '../ui/components/ErrorBlock';
import FlexColumn from '../ui/components/FlexColumn';
import SearchableTable from '../ui/components/searchable/SearchableTable';
import {
TableHighlightedRows,
TableRows,
TableColumnSizes,
TableColumns,
TableColumnOrderVal,
TableBodyRow,
} from '../ui/components/table/types';
import {DetailSidebar} from 'flipper-plugin';
import {FlipperPlugin} from '../plugin';
import textContent from '../utils/textContent';
import createPaste from '../fb-stubs/createPaste';
import {ReactNode} from 'react';
import React from 'react';
import {KeyboardActions} from '../MenuBar';
import {BundledPluginDetails} from 'flipper-plugin-lib';
import {Toolbar} from 'flipper-plugin';
type ID = string;
type TableMetadata = {
topToolbar?: ToolbarSection;
bottomToolbar?: ToolbarSection;
columns: TableColumns;
columnSizes?: TableColumnSizes;
columnOrder?: Array<TableColumnOrderVal>;
filterableColumns?: Set<string>;
};
type PersistedState = {
rows: TableRows;
datas: {[key: string]: NumberedRowData};
tableMetadata: TableMetadata | null | undefined;
};
type State = {
selectedIds: Array<ID>;
error: string | null | undefined;
};
type RowData = {
id: string;
columns: {[key: string]: any};
sidebar?: Array<SidebarSection>;
};
type NumberedRowData = {
id: string;
columns: {[key: string]: any};
sidebar?: Array<SidebarSection>;
rowNumber: number;
};
type SidebarSection = JsonSection | ToolbarSection;
type JsonSection = {
type: 'json';
title: string;
content: string;
};
type ToolbarSection = {
type: 'toolbar';
items: Array<ToolbarItem>;
};
type ToolbarItem =
| {type: 'link'; destination: string; label: string}
| {
type: 'input';
inputType: 'select';
id: string;
label: string;
options: Array<string>;
value: string;
};
const NonWrappingText = styled(Text)({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
const BooleanValue = styled(NonWrappingText)<{active?: boolean}>((props) => ({
'&::before': {
content: '""',
display: 'inline-block',
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: props.active ? colors.green : colors.red,
marginRight: 5,
marginTop: 1,
},
}));
const Label = styled.span({
fontSize: 12,
color: '#90949c',
fontWeight: 'bold',
textTransform: 'uppercase',
marginLeft: 5,
marginRight: 12,
});
function renderValue({type, value}: {type: string; value: any}) {
switch (type) {
case 'boolean':
return (
<BooleanValue code active={value}>
{value.toString()}
</BooleanValue>
);
default:
return value;
}
}
function buildRow(
rowData: RowData,
previousRowData: RowData | null | undefined,
): TableBodyRow {
if (!rowData.columns) {
throw new Error('defaultBuildRow used with incorrect data format.');
}
const oldColumns =
previousRowData && previousRowData.columns
? Object.keys(previousRowData.columns).reduce(
(map: {[key: string]: {value: any; isFilterable: boolean}}, key) => {
if (key !== 'id') {
let value = null;
if (previousRowData && previousRowData.columns) {
value = previousRowData.columns[key].value;
}
map[key] = {
value,
isFilterable: true,
};
}
return map;
},
{},
)
: {};
const columns = Object.keys(rowData.columns).reduce((map, key) => {
if (rowData.columns && key !== 'id') {
const renderedValue = renderValue(rowData.columns[key]);
map[key] = {
value: renderedValue,
isFilterable: true,
};
}
return map;
}, oldColumns);
return {
columns,
key: rowData.id,
copyText: () => JSON.stringify(rowData),
filterValue: rowData.id,
};
}
function renderToolbar(section: ToolbarSection) {
const toolbarComponents = section.items.map((item, index) => {
switch (item.type) {
case 'link':
return (
<Button href={item.destination} key={index + 1}>
{item.label}
</Button>
);
case 'input':
return (
<>
<Label>{item.label}</Label>
<Select
options={item.options.reduce(
(obj: {[key: string]: string}, item) => {
obj[item] = item;
return obj;
},
{},
)}
selected={item.value}
onChange={() => {}}
/>
</>
);
}
});
return (
<Toolbar key="toolbar">
<Spacer key={0} />
{toolbarComponents}
</Toolbar>
);
}
function renderSidebarForRow(rowData: RowData): ReactNode {
if (!rowData.sidebar) {
return null;
}
if (!Array.isArray(rowData.sidebar)) {
throw new Error('typeof rowData.sidebar is not array as expected: ');
}
return rowData.sidebar.map(renderSidebarSection);
}
function renderSidebarSection(
section: SidebarSection,
index: number,
): ReactNode {
switch (section.type) {
case 'json':
return (
<Panel floating={false} heading={section.title} key={index}>
<DataInspector data={section.content} expandRoot />
</Panel>
);
case 'toolbar':
return renderToolbar(section);
default:
return (
<Panel floating={false} heading={'Details'} key={index}>
<DataInspector data={section} expandRoot />
</Panel>
);
}
}
type IncomingMessage =
| {method: 'updateRows'; data: Array<RowData>}
| {method: 'clearTable'};
export default function createTableNativePlugin(id: string, title: string) {
return class extends FlipperPlugin<State, any, PersistedState> {
static keyboardActions: KeyboardActions = ['clear', 'createPaste'];
static id = id || '';
static title = title || '';
static details: BundledPluginDetails = {
id,
title,
icon: 'apps',
name: id,
pluginType: 'client',
// all hmm...
specVersion: 1,
version: 'auto',
source: '',
main: '',
isBundled: true,
isActivatable: true,
};
static defaultPersistedState: PersistedState = {
rows: [],
datas: {},
tableMetadata: null,
};
static typedPersistedStateReducer = (
persistedState: PersistedState,
message: IncomingMessage,
): Partial<PersistedState> => {
if (message.method === 'updateRows') {
const newRows = [];
const newData: {[key: string]: NumberedRowData} = {};
for (const rowData of message.data.reverse()) {
if (rowData.id == null) {
throw new Error(
`updateRows: row is missing id: ${JSON.stringify(rowData)}`,
);
}
const previousRowData: NumberedRowData | null | undefined =
persistedState.datas[rowData.id];
const newRow: TableBodyRow = buildRow(rowData, previousRowData);
if (persistedState.datas[rowData.id] == null) {
newData[rowData.id] = {
...rowData,
rowNumber: persistedState.rows.length + newRows.length,
};
newRows.push(newRow);
} else {
persistedState.rows = persistedState.rows
.slice(0, persistedState.datas[rowData.id].rowNumber)
.concat(
[newRow],
persistedState.rows.slice(
persistedState.datas[rowData.id].rowNumber + 1,
),
);
}
}
return {
...persistedState,
datas: {...persistedState.datas, ...newData},
rows: [...persistedState.rows, ...newRows],
};
} else if (message.method === 'clearTable') {
return {
...persistedState,
rows: [],
datas: {},
};
} else {
return {};
}
};
static persistedStateReducer(
persistedState: PersistedState,
method: string,
data: Array<RowData> | undefined,
): Partial<PersistedState> {
const methodEnum = method as 'updateRows' | 'clearTable';
const message: IncomingMessage =
methodEnum === 'updateRows'
? {
method: methodEnum,
data: data || [],
}
: {method: methodEnum};
return this.typedPersistedStateReducer(persistedState, message);
}
state = {
selectedIds: [] as Array<ID>,
error: null,
};
init() {
this.getTableMetadata();
}
getTableMetadata = () => {
if (!this.props.persistedState.tableMetadata) {
if (!this.client.isConnected) {
this.setState({error: 'Application disconnected'});
return;
}
this.client
.call('getMetadata')
.then((metadata) => {
this.props.setPersistedState({
tableMetadata: {
...metadata,
filterableColumns: new Set(metadata.filterableColumns),
},
});
})
.catch((e) => this.setState({error: e}));
}
};
onKeyboardAction = (action: string) => {
if (action === 'clear') {
this.clear();
} else if (action === 'createPaste') {
this.createPaste();
}
};
clear = () => {
this.props.setPersistedState({
rows: [],
datas: {},
});
this.setState({
selectedIds: [],
});
};
createPaste = () => {
if (!this.props.persistedState.tableMetadata) {
return;
}
let paste = '';
const mapFn = (row: TableBodyRow) =>
(
(this.props.persistedState.tableMetadata &&
Object.keys(this.props.persistedState.tableMetadata.columns)) ||
[]
)
.map((key) => textContent(row.columns[key].value))
.join('\t');
if (this.state.selectedIds.length > 0) {
// create paste from selection
paste = this.props.persistedState.rows
.filter((row) => this.state.selectedIds.indexOf(row.key) > -1)
.map(mapFn)
.join('\n');
} else {
// create paste with all rows
paste = this.props.persistedState.rows.map(mapFn).join('\n');
}
createPaste(paste);
};
onRowHighlighted = (keys: TableHighlightedRows) => {
this.setState({
selectedIds: keys,
});
};
// 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 = () => {
const {selectedIds} = this.state;
const {datas} = this.props.persistedState;
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
if (selectedId != null) {
return renderSidebarForRow(datas[selectedId]);
} else {
return null;
}
};
render() {
if (this.state.error) {
return <ErrorBlock error={this.state.error} />;
}
if (!this.props.persistedState.tableMetadata) {
return 'Loading...';
}
const {topToolbar, bottomToolbar, columns, columnSizes, columnOrder} =
this.props.persistedState.tableMetadata;
const {rows} = this.props.persistedState;
const topToolbarComponent = topToolbar ? renderToolbar(topToolbar) : null;
const bottomToolbarComponent = bottomToolbar
? renderToolbar(bottomToolbar)
: null;
return (
<FlexColumn grow>
{topToolbarComponent}
<SearchableTable
key={this.constructor.id}
rowLineHeight={28}
floating={false}
multiline
columnSizes={columnSizes}
columnOrder={columnOrder}
columns={columns}
onRowHighlighted={this.onRowHighlighted}
multiHighlight
rows={this.applyMetadataToRows(rows)}
stickyBottom
actions={<Button onClick={this.clear}>Clear Table</Button>}
/>
<DetailSidebar>{this.renderSidebar()}</DetailSidebar>
{bottomToolbarComponent}
</FlexColumn>
);
}
};
}