flipper-plugin
Version:
Flipper Desktop plugin SDK and components
198 lines • 8.64 kB
JavaScript
"use strict";
/**
* 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
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataInspector = void 0;
const DataInspectorNode_1 = require("./DataInspectorNode");
const react_1 = require("react");
const DataInspectorNode_2 = require("./DataInspectorNode");
const react_2 = __importDefault(require("react"));
const Highlight_1 = require("../Highlight");
const Layout_1 = require("../Layout");
const antd_1 = require("antd");
const flipper_plugin_1 = require("flipper-plugin");
const flipper_plugin_2 = require("flipper-plugin");
const DataTableManager_1 = require("../data-table/DataTableManager");
const MAX_RESULTS = 50;
const EMPTY_ARRAY = [];
/**
* Wrapper around `DataInspector` that handles expanded state.
*
* If you require lower level access to the state then use `DataInspector`
* directly.
*/
class DataInspector extends react_1.PureComponent {
constructor() {
super(...arguments);
this.state = {
expanded: {},
userExpanded: {},
filterExpanded: {},
filter: '',
hoveredNodePath: undefined,
};
this.isContextMenuOpen = false;
this.onExpanded = (path, isExpanded) => {
this.setState({
userExpanded: {
...this.state.userExpanded,
[path]: isExpanded,
},
expanded: {
...this.state.expanded,
[path]: isExpanded,
},
});
};
this.setHoveredNodePath = (path) => {
if (!this.isContextMenuOpen) {
this.setState({
hoveredNodePath: path,
});
}
};
this.removeHover = () => {
this.setHoveredNodePath(undefined);
};
// make sure this fn is a stable ref to not invalidate the whole tree on new data
this.getRootData = () => {
return this.props.data;
};
this.getContextMenu = () => {
const lib = (0, flipper_plugin_1._tryGetFlipperLibImplementation)();
let extraItems = [];
const hoveredNodePath = this.state.hoveredNodePath;
const value = hoveredNodePath != null && hoveredNodePath.length > 0
? (0, DataTableManager_1.getValueAtPath)(this.props.data, hoveredNodePath)
: this.props.data;
if (this.props.additionalContextMenuItems != null &&
hoveredNodePath != null &&
hoveredNodePath.length > 0) {
const fullPath = hoveredNodePath.split('.');
const parentPath = fullPath.slice(0, fullPath.length - 1);
const name = fullPath[fullPath.length - 1];
const additionalItems = this.props.additionalContextMenuItems(parentPath, value, name);
extraItems = [
...additionalItems.map((component) => ({
key: `additionalItem-${parentPath}.${name}`,
label: component,
})),
{ type: 'divider' },
];
}
const items = [
...extraItems,
{ key: 'copy-value', label: 'Copy' },
...(this.props.onDelete != null
? [{ key: 'delete-value', label: 'Delete' }]
: []),
{ type: 'divider' },
{ key: 'copy-tree', label: 'Copy full tree' },
...(lib?.isFB ? [{ key: 'create-paste', label: 'Create paste' }] : []),
];
return {
items,
onClick: (info) => {
this.isContextMenuOpen = false;
if (info.key === 'copy-value') {
if (this.state.hoveredNodePath != null) {
const value = (0, DataTableManager_1.getValueAtPath)(this.props.data, this.state.hoveredNodePath);
lib?.writeTextToClipboard((0, flipper_plugin_2.safeStringify)(value));
}
}
else if (info.key === 'delete-value') {
const pathStr = this.state.hoveredNodePath;
this.props.onDelete?.(pathStr?.split('.') ?? []);
}
else if (info.key === 'copy-tree') {
lib?.writeTextToClipboard((0, flipper_plugin_2.safeStringify)(this.props.data));
}
else if (info.key === 'create-paste') {
lib?.createPaste((0, flipper_plugin_2.safeStringify)(this.props.data));
}
},
};
};
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.filter?.toLowerCase() === currentState.filter) {
return null;
}
if (!nextProps.filter) {
return {
filter: '',
filterExpanded: {},
// reset expanded when removing filter
expanded: currentState.userExpanded,
};
}
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const filter = nextProps.filter.toLowerCase();
const paths = [];
function walk(value, path) {
if (paths.length > MAX_RESULTS) {
return;
}
if (!value) {
return;
}
if (typeof value !== 'object') {
// TODO: Fix this the next time the file is edited.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (`${value}`.toLowerCase().includes(filter)) {
paths.push(path.slice());
}
}
else if (Array.isArray(value)) {
value.forEach((value, index) => {
path.push(index);
walk(value, path);
path.pop();
});
}
else {
// a plain object
Object.keys(value).forEach((key) => {
path.push(key);
walk(key, path); // is the key interesting?
walk(value[key], path);
path.pop();
});
}
}
if (filter.length >= 2) {
walk(nextProps.data, []);
}
const filterExpanded = {};
paths.forEach((path) => {
for (let i = 1; i < path.length; i++)
filterExpanded[path.slice(0, i).join('.')] = true;
});
return {
filterExpanded,
expanded: { ...currentState.userExpanded, ...filterExpanded },
filter,
};
}
render() {
return (react_2.default.createElement(antd_1.Dropdown, { menu: this.getContextMenu(), onOpenChange: (open) => {
this.isContextMenuOpen = open;
}, trigger: ['contextMenu'] },
react_2.default.createElement(Layout_1.Layout.Container, { onMouseLeave: this.removeHover },
react_2.default.createElement(DataInspectorNode_1.RootDataContext.Provider, { value: this.getRootData },
react_2.default.createElement(Highlight_1.HighlightProvider, { text: this.props.filter, highlightColor: this.props.highlightColor },
react_2.default.createElement(DataInspectorNode_2.DataInspectorNode, { hoveredNodePath: this.state.hoveredNodePath, setHoveredNodePath: this.setHoveredNodePath, data: this.props.data, diff: this.props.diff, extractValue: this.props.extractValue, setValue: this.props.setValue, expanded: this.state.expanded, onExpanded: this.onExpanded, onRenderName: this.props.onRenderName, onRenderDescription: this.props.onRenderDescription, expandRoot: this.props.expandRoot, collapsed: this.props.filter ? true : this.props.collapsed, tooltips: this.props.tooltips, parentPath: EMPTY_ARRAY, depth: 0, parentAncestry: EMPTY_ARRAY }))))));
}
}
exports.DataInspector = DataInspector;
//# sourceMappingURL=DataInspector.js.map