UNPKG

flipper-plugin

Version:

Flipper Desktop plugin SDK and components

198 lines 8.64 kB
"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