forked from mirrors/gecko-dev
219 lines
6 KiB
JavaScript
219 lines
6 KiB
JavaScript
/* 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 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 Editor = createFactory(require("./editor"));
|
|
|
|
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 and content is object ('cause it shouldn't hide
|
|
// when string or number)
|
|
// 2. the `value` object has a `value` property, only happened in Cookies panel
|
|
// Put 2 here to not dup this method
|
|
if (member.level === 0 && member.type === "object" ||
|
|
(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,
|
|
useQuotes: false,
|
|
expandedNodes: this.getExpandedNodes(object),
|
|
onFilter: (props) => this.onFilter(props, sectionNames),
|
|
renderRow: renderRow || this.renderRowWithEditor,
|
|
renderValue: renderValue || this.renderValueWithRep,
|
|
}),
|
|
),
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
module.exports = PropertiesView;
|