UNPKG

flipper-plugin

Version:

Flipper Desktop plugin SDK and components

426 lines 17.6 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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataInspectorNode = exports.RootDataContext = void 0; const DataDescription_1 = require("./DataDescription"); const react_1 = require("react"); const styled_1 = __importDefault(require("@emotion/styled")); const DataPreview_1 = __importStar(require("./DataPreview")); const utils_1 = require("./utils"); const react_2 = __importDefault(require("react")); const Highlight_1 = require("../Highlight"); const antd_1 = require("antd"); const useInUnitTest_1 = require("../../utils/useInUnitTest"); const theme_1 = require("../theme"); exports.RootDataContext = (0, react_1.createContext)(() => ({})); const BaseContainer = styled_1.default.div((props) => ({ fontFamily: 'Menlo, monospace', fontSize: 11, lineHeight: '17px', filter: props.disabled ? 'grayscale(100%)' : '', margin: props.depth === 0 ? '7.5px 0' : '0', paddingLeft: 10, userSelect: 'text', width: '100%', backgroundColor: props.hovered ? theme_1.theme.selectionBackgroundColor : '', })); BaseContainer.displayName = 'DataInspector:BaseContainer'; const RecursiveBaseWrapper = styled_1.default.span({ color: theme_1.theme.errorColor, }); RecursiveBaseWrapper.displayName = 'DataInspector:RecursiveBaseWrapper'; const Wrapper = styled_1.default.span({ color: theme_1.theme.textColorSecondary, }); Wrapper.displayName = 'DataInspector:Wrapper'; const PropertyContainer = styled_1.default.span({ paddingTop: '2px', }); PropertyContainer.displayName = 'DataInspector:PropertyContainer'; const ExpandControl = styled_1.default.span({ color: theme_1.theme.textColorSecondary, fontSize: 10, marginLeft: -11, marginRight: 5, whiteSpace: 'pre', }); ExpandControl.displayName = 'DataInspector:ExpandControl'; const Added = styled_1.default.div({ backgroundColor: theme_1.theme.semanticColors.diffAddedBackground, }); const Removed = styled_1.default.div({ backgroundColor: theme_1.theme.semanticColors.diffRemovedBackground, }); const defaultValueExtractor = (value) => { const type = typeof value; if (type === 'number') { return { mutable: true, type: 'number', value }; } if (type === 'bigint') { return { mutable: true, type: 'bigint', value }; } if (type === 'string') { return { mutable: true, type: 'string', value }; } if (type === 'boolean') { return { mutable: true, type: 'boolean', value }; } if (type === 'undefined') { return { mutable: true, type: 'undefined', value }; } if (value === null) { return { mutable: true, type: 'null', value }; } if (Array.isArray(value)) { return { mutable: true, type: 'array', value }; } if (Object.prototype.toString.call(value) === '[object Date]') { return { mutable: true, type: 'date', value }; } if (type === 'object') { return { mutable: true, type: 'object', value }; } }; function isPureObject(obj) { return (obj !== null && Object.prototype.toString.call(obj) !== '[object Date]' && typeof obj === 'object'); } const diffMetadataExtractor = (data, key, diff) => { if (diff == null) { return [{ data: data[key] }]; } const val = data[key]; const diffVal = diff[key]; if (!data.hasOwnProperty(key)) { return [{ data: diffVal, status: 'removed' }]; } if (!diff.hasOwnProperty(key)) { return [{ data: val, status: 'added' }]; } if (isPureObject(diffVal) && isPureObject(val)) { return [{ data: val, diff: diffVal }]; } if (diffVal !== val) { // Check if there's a difference between the original value and // the value from the diff prop // The property name still exists, but the values may be different. return [ { data: val, status: 'added' }, { data: diffVal, status: 'removed' }, ]; } return Object.prototype.hasOwnProperty.call(data, key) ? [{ data: val }] : []; }; function isComponentExpanded(data, diffType, diffValue) { if (diffValue == null) { return false; } if (diffType === 'object') { const sortedDataValues = Object.keys(data) .sort() .map((key) => data[key]); const sortedDiffValues = Object.keys(diffValue) .sort() .map((key) => diffValue[key]); if (JSON.stringify(sortedDataValues) !== JSON.stringify(sortedDiffValues)) { return true; } } else { if (data !== diffValue) { return true; } } return false; } const recursiveMarker = react_2.default.createElement(RecursiveBaseWrapper, null, "Recursive"); /** * An expandable data inspector. * * This component is fairly low level. It's likely you're looking for * [`<ManagedDataInspector>`](#manageddatainspector). */ exports.DataInspectorNode = (0, react_1.memo)(function DataInspectorImpl({ data, depth, diff, expandRoot, parentPath, onExpanded, onRenderName, onRenderDescription, extractValue: extractValueProp, expanded: expandedPaths, name, parentAncestry, collapsed, tooltips, setValue: setValueProp, hoveredNodePath, setHoveredNodePath, }) { const highlighter = (0, Highlight_1.useHighlighter)(); const isUnitTest = (0, useInUnitTest_1.useInUnitTest)(); const shouldExpand = (0, react_1.useRef)(false); const expandHandle = (0, react_1.useRef)(undefined); const [renderExpanded, setRenderExpanded] = (0, react_1.useState)(false); const path = (0, react_1.useMemo)(() => (name === undefined ? parentPath : parentPath.concat([name])), [parentPath, name]); const extractValue = (0, react_1.useCallback)((data, depth, path) => { let res; if (extractValueProp) { res = extractValueProp(data, depth, path); } if (!res) { res = defaultValueExtractor(data, depth, path); } return res; }, [extractValueProp]); const res = (0, react_1.useMemo)(() => extractValue(data, depth, path), [extractValue, data, depth, path]); const resDiff = (0, react_1.useMemo)(() => extractValue(diff, depth, path), [extractValue, diff, depth, path]); const ancestry = (0, react_1.useMemo)( // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion () => (res ? parentAncestry.concat([res.value]) : []), [parentAncestry, res?.value]); let isExpandable = false; if (!res) { shouldExpand.current = false; } else { isExpandable = isValueExpandable(res.value); } if (isExpandable) { if (expandRoot === true || shouldBeExpanded(expandedPaths, path, collapsed)) { shouldExpand.current = true; } else if (resDiff) { shouldExpand.current = isComponentExpanded( // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion res.value, resDiff.type, resDiff.value); } } (0, react_1.useEffect)(() => { if (!shouldExpand.current) { setRenderExpanded(false); } else { if (isUnitTest) { setRenderExpanded(true); } else { expandHandle.current = requestIdleCallback(() => { setRenderExpanded(true); }); } } return () => { if (!isUnitTest) { cancelIdleCallback(expandHandle.current); } }; }, [shouldExpand.current, isUnitTest]); const setExpanded = (0, react_1.useCallback)((pathParts, isExpanded) => { if (!onExpanded || !expandedPaths) { return; } const path = pathParts.join('.'); onExpanded(path, isExpanded); }, [onExpanded, expandedPaths]); const handleClick = (0, react_1.useCallback)((event) => { if (!isUnitTest) { cancelIdleCallback(expandHandle.current); } if (event.buttons !== 0) { //only process left click return; } const isExpanded = shouldBeExpanded(expandedPaths, path, collapsed); setExpanded(path, !isExpanded); }, [expandedPaths, path, collapsed, isUnitTest]); /** * RENDERING */ if (!res) { return null; } // the data inspector makes values read only when setValue isn't set so we just need to set it // to null and the readOnly status will be propagated to all children const setValue = res.mutable ? setValueProp : null; const { value, type, extra } = res; // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (parentAncestry.includes(value)) { return recursiveMarker; } let expandGlyph = ''; if (isExpandable) { if (shouldExpand.current) { expandGlyph = '▼'; } else { expandGlyph = '▶'; } } else { if (depth !== 0) { expandGlyph = ' '; } } let propertyNodesContainer = null; if (isExpandable && renderExpanded) { const propertyNodes = []; const diffValue = diff && resDiff ? resDiff.value : null; const keys = (0, utils_1.getSortedKeys)({ ...value, ...diffValue }); for (const key of keys) { const diffMetadataArr = diffMetadataExtractor(value, key, diffValue); for (const [index, metadata] of diffMetadataArr.entries()) { const metaKey = key + index; const dataInspectorNode = (react_2.default.createElement(exports.DataInspectorNode, { setHoveredNodePath: setHoveredNodePath, hoveredNodePath: hoveredNodePath, parentAncestry: ancestry, extractValue: extractValue, setValue: setValue, expanded: expandedPaths, collapsed: collapsed, onExpanded: onExpanded, onRenderName: onRenderName, onRenderDescription: onRenderDescription, parentPath: path, depth: depth + 1, key: metaKey, name: key, data: metadata.data, diff: metadata.diff, tooltips: tooltips })); switch (metadata.status) { case 'added': propertyNodes.push(react_2.default.createElement(Added, { key: metaKey }, dataInspectorNode)); break; case 'removed': propertyNodes.push(react_2.default.createElement(Removed, { key: metaKey }, dataInspectorNode)); break; default: propertyNodes.push(dataInspectorNode); } } } propertyNodesContainer = propertyNodes; } if (expandRoot === true) { return react_2.default.createElement(react_2.default.Fragment, null, propertyNodesContainer); } // create name components const nameElems = []; if (typeof name !== 'undefined') { const text = onRenderName ? onRenderName(path, name, highlighter) : highlighter.render(name); nameElems.push(react_2.default.createElement(antd_1.Tooltip, { title: tooltips != null && tooltips[name], key: "name", placement: "left" }, react_2.default.createElement(DataPreview_1.InspectorName, null, text))); nameElems.push(react_2.default.createElement("span", { key: "sep" }, ": ")); } // create description or preview let descriptionOrPreview; if (renderExpanded || !isExpandable) { descriptionOrPreview = (react_2.default.createElement(DataDescription_1.DataDescription, { path: path, setValue: setValue, type: type, value: value, extra: extra })); descriptionOrPreview = onRenderDescription ? onRenderDescription(descriptionOrPreview) : descriptionOrPreview; } else { descriptionOrPreview = (react_2.default.createElement(DataPreview_1.default, { path: path, type: type, value: value, extractValue: extractValue, depth: depth })); } descriptionOrPreview = (react_2.default.createElement("span", null, nameElems, descriptionOrPreview)); let wrapperStart; let wrapperEnd; if (renderExpanded) { if (type === 'object') { wrapperStart = react_2.default.createElement(Wrapper, null, '{'); wrapperEnd = react_2.default.createElement(Wrapper, null, '}'); } if (type === 'array') { wrapperStart = react_2.default.createElement(Wrapper, null, '['); wrapperEnd = react_2.default.createElement(Wrapper, null, ']'); } } const nodePath = path.join('.'); return (react_2.default.createElement(BaseContainer, { hovered: hoveredNodePath === nodePath, onMouseEnter: () => { setHoveredNodePath(nodePath); }, onMouseLeave: () => { setHoveredNodePath(parentPath.join('.')); }, depth: depth, disabled: !!setValueProp && !!setValue === false }, react_2.default.createElement(PropertyContainer, { onClick: isExpandable ? handleClick : undefined }, expandedPaths && react_2.default.createElement(ExpandControl, null, expandGlyph), descriptionOrPreview, wrapperStart), propertyNodesContainer, wrapperEnd)); }, dataInspectorPropsAreEqual); function shouldBeExpanded(expanded, pathParts, collapsed) { // if we have no expanded object then expand everything if (expanded == null) { return true; } const path = pathParts.join('.'); // check if there's a setting for this path if (Object.prototype.hasOwnProperty.call(expanded, path)) { return expanded[path]; } // check if all paths are collapsed if (collapsed === true) { return false; } // by default all items are expanded return true; } function dataInspectorPropsAreEqual(props, nextProps) { // Optimization: it would be much faster to not pass the expanded tree // down the tree, but rather introduce an ExpandStateManager, and subscribe per node // check if any expanded paths effect this subtree if (nextProps.expanded !== props.expanded) { const path = !nextProps.name ? '' // root : !nextProps.parentPath.length ? nextProps.name // root element : `${nextProps.parentPath.join('.')}.${nextProps.name}`; // we are being collapsed if (props.expanded[path] !== nextProps.expanded[path]) { return false; } // one of our children was expande for (const key in nextProps.expanded) { if (key.startsWith(path) === false) { // this key doesn't effect us continue; } if (nextProps.expanded[key] !== props.expanded[key]) { return false; } } } const nodePath = (props.name === undefined ? props.parentPath : props.parentPath.concat([props.name])).join('.'); //if the node is a prefix then we of the hovered path(s) then we *should* render this branch of the tree //Otherwise we don't need to rerender since this node is not changing hover state const nodePathIsPrefixOfCurrentOrNextHoverPath = nextProps.hoveredNodePath?.startsWith(nodePath) || props.hoveredNodePath?.startsWith(nodePath); // basic equality checks for the rest return (nextProps.data === props.data && nextProps.diff === props.diff && nextProps.name === props.name && nextProps.depth === props.depth && nextProps.parentPath === props.parentPath && nextProps.onExpanded === props.onExpanded && nextProps.setValue === props.setValue && nextProps.collapsed === props.collapsed && nextProps.expandRoot === props.expandRoot && !nodePathIsPrefixOfCurrentOrNextHoverPath); } function isValueExpandable(data) { return (typeof data === 'object' && data !== null && Object.keys(data).length > 0); } //# sourceMappingURL=DataInspectorNode.js.map