/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-disable react/prop-types */ "use strict"; const { createClass, createFactory, DOM, PropTypes, } = require("devtools/client/shared/vendor/react"); const { REPS, MODE } = require("devtools/client/shared/components/reps/reps"); const Rep = createFactory(REPS.Rep); const { FILTER_SEARCH_DELAY } = require("../../constants"); // Components const Editor = createFactory(require("devtools/client/netmonitor/shared/components/editor")); const SearchBox = createFactory(require("devtools/client/shared/components/search-box")); const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view")); const TreeRow = createFactory(require("devtools/client/shared/components/tree/tree-row")); const { div, tr, td } = DOM; const AUTO_EXPAND_MAX_LEVEL = 7; const AUTO_EXPAND_MAX_NODES = 50; const EDITOR_CONFIG_ID = "EDITOR_CONFIG"; /* * Properties View component * A scrollable tree view component which provides some useful features for * representing object properties. * * Search filter - Set enableFilter to enable / disable SearchBox feature. * Tree view - Default enabled. * Source editor - Enable by specifying object level 1 property name to EDITOR_CONFIG_ID. * Rep - Default enabled. */ const PropertiesView = createClass({ displayName: "PropertiesView", propTypes: { object: PropTypes.object, enableInput: PropTypes.bool, expandableStrings: PropTypes.bool, filterPlaceHolder: PropTypes.string, sectionNames: PropTypes.array, }, getDefaultProps() { return { enableInput: true, enableFilter: true, expandableStrings: false, filterPlaceHolder: "", sectionNames: [], }; }, getInitialState() { return { filterText: "", }; }, getRowClass(object, sectionNames) { return sectionNames.includes(object.name) ? "tree-section" : ""; }, onFilter(object, whiteList) { let { name, value } = object; let filterText = this.state.filterText; if (!filterText || whiteList.includes(name)) { return true; } let jsonString = JSON.stringify({ [name]: value }).toLowerCase(); return jsonString.includes(filterText.toLowerCase()); }, renderRowWithEditor(props) { const { level, name, value, path } = props.member; // Display source editor when specifying to EDITOR_CONFIG_ID along with config if (level === 1 && name === EDITOR_CONFIG_ID) { return ( tr({ className: "editor-row-container" }, td({ colSpan: 2 }, Editor(value) ) ) ); } // Skip for editor config if (level >= 1 && path.includes(EDITOR_CONFIG_ID)) { return null; } return TreeRow(props); }, renderValueWithRep(props) { const { member } = props; // Hide strings with following conditions // 1. this row is a togglable section // 2. the `value` object has a `value` property, only happend in Cookies panel // Put 2 here to not dup this method if (member.level === 0 || (typeof member.value === "object" && member.value && member.value.value)) { return null; } return Rep(Object.assign(props, { // FIXME: A workaround for the issue in StringRep // Force StringRep to crop the text everytime member: Object.assign({}, member, { open: false }), mode: MODE.TINY, cropLimit: 60, })); }, shouldRenderSearchBox(object) { return this.props.enableFilter && object && Object.keys(object) .filter((section) => !object[section][EDITOR_CONFIG_ID]).length > 0; }, updateFilterText(filterText) { this.setState({ filterText, }); }, getExpandedNodes: function (object, path = "", level = 0) { if (typeof object != "object") { return null; } if (level > AUTO_EXPAND_MAX_LEVEL) { return null; } let expandedNodes = new Set(); for (let prop in object) { if (expandedNodes.size > AUTO_EXPAND_MAX_NODES) { // If we reached the limit of expandable nodes, bail out to avoid performance // issues. break; } let nodePath = path + "/" + prop; expandedNodes.add(nodePath); let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1); if (nodes) { let newSize = expandedNodes.size + nodes.size; if (newSize < AUTO_EXPAND_MAX_NODES) { // Avoid having a subtree half expanded. expandedNodes = new Set([...expandedNodes, ...nodes]); } } } return expandedNodes; }, render() { const { decorator, enableInput, expandableStrings, filterPlaceHolder, object, renderRow, renderValue, sectionNames, } = this.props; return ( div({ className: "properties-view" }, this.shouldRenderSearchBox(object) && div({ className: "searchbox-section" }, SearchBox({ delay: FILTER_SEARCH_DELAY, type: "filter", onChange: this.updateFilterText, placeholder: filterPlaceHolder, }), ), div({ className: "tree-container" }, TreeView({ object, columns: [{ id: "value", width: "100%", }], decorator: decorator || { getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames), }, enableInput, expandableStrings, expandedNodes: this.getExpandedNodes(object), onFilter: (props) => this.onFilter(props, sectionNames), renderRow: renderRow || this.renderRowWithEditor, renderValue: renderValue || this.renderValueWithRep, }), ), ) ); } }); module.exports = PropertiesView;