Bug 1328828 - Implement Properties View r?jsnajdr,honza
MozReview-Commit-ID: EuFGC12V6BU
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/editor.js
@@ -0,0 +1,92 @@
+/* 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/. */
+
+"use strict";
+
+const { createClass, DOM, PropTypes } = require("devtools/client/shared/vendor/react");
+const SourceEditor = require("devtools/client/sourceeditor/editor");
+
+const { div } = DOM;
+
+/**
+ * CodeMirror editor as a React component
+ */
+const Editor = createClass({
+ displayName: "Editor",
+
+ propTypes: {
+ open: PropTypes.bool,
+ text: PropTypes.string,
+ },
+
+ getDefaultProps() {
+ return {
+ open: true,
+ text: "",
+ };
+ },
+
+ componentDidMount() {
+ const { text } = this.props;
+
+ this.editor = new SourceEditor({
+ lineNumbers: true,
+ readOnly: true,
+ value: text,
+ });
+
+ this.deferEditor = this.editor.appendTo(this.refs.editorElement);
+ },
+
+ componentDidUpdate(prevProps) {
+ const { mode, open, text } = this.props;
+
+ if (!open) {
+ return;
+ }
+
+ if (prevProps.mode !== mode) {
+ this.deferEditor.then(() => {
+ this.editor.setMode(mode);
+ });
+ }
+
+ if (prevProps.text !== text) {
+ this.deferEditor.then(() => {
+ // FIXME: Workaround for browser_net_accessibility test to
+ // make sure editor node exists while setting editor text.
+ // deferEditor workround should be removed in bug 1308442
+ if (this.refs.editor) {
+ this.editor.setText(text);
+ }
+ });
+ }
+ },
+
+ componentWillUnmount() {
+ this.deferEditor.then(() => {
+ this.editor.destroy();
+ this.editor = null;
+ });
+ this.deferEditor = null;
+ },
+
+ render() {
+ const { open } = this.props;
+
+ return (
+ div({ className: "editor-container devtools-monospace" },
+ div({
+ ref: "editorElement",
+ className: "editor-mount devtools-monospace",
+ // Using visibility instead of display property to avoid breaking
+ // CodeMirror indentation
+ style: { visibility: open ? "visible" : "hidden" },
+ }),
+ )
+ );
+ }
+});
+
+module.exports = Editor;
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,9 +1,11 @@
# 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/.
DevToolsModules(
+ 'editor.js',
'preview-panel.js',
+ 'properties-view.js',
'security-panel.js',
'timings-panel.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/properties-view.js
@@ -0,0 +1,162 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { MODE } = require("devtools/client/shared/components/reps/constants");
+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 { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+
+const { div, tr, td } = DOM;
+
+/*
+ * 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 "editorText".
+ * 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: "",
+ };
+ },
+
+ 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 } = props.member;
+ // Display source editor when prop name specify to editorText
+ if (level === 1 && name === "editorText") {
+ return (
+ tr({},
+ td({ colSpan: 2 },
+ Editor({ text: value })
+ )
+ )
+ );
+ }
+
+ return TreeRow(props);
+ },
+
+ renderValueWithRep(props) {
+ // Hide rep summary for sections
+ if (props.member.level === 0) {
+ 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({}, props.member, { open: false }),
+ mode: MODE.TINY,
+ cropLimit: 60,
+ }));
+ },
+
+ updateFilterText(filterText) {
+ this.setState({
+ filterText,
+ });
+ },
+
+ render() {
+ const {
+ object,
+ decorator,
+ enableInput,
+ enableFilter,
+ expandableStrings,
+ filterPlaceHolder,
+ renderRow,
+ renderValue,
+ sectionNames,
+ } = this.props;
+
+ return (
+ div({ className: "properties-view" },
+ enableFilter && 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: new Set(sectionNames.map((sec) => "/" + sec)),
+ onFilter: (props) => this.onFilter(props, sectionNames),
+ renderRow: renderRow || this.renderRowWithEditor,
+ renderValue: renderValue || this.renderValueWithRep,
+ }),
+ ),
+ )
+ );
+ }
+
+});
+
+module.exports = PropertiesView;
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1115,34 +1115,83 @@
outline: 0;
box-shadow: var(--theme-focus-box-shadow-textbox);
}
.treeTable .treeLabel {
font-weight: 600;
}
-/* Customize default tree table style to align with devtools theme */
-.theme-light .treeTable .treeLabel,
-.theme-light .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
- color: var(--theme-highlight-red);
+.properties-view {
+ /* FIXME: Minus 24px * 2 for toolbox height + panel height
+ * Give a fixed panel container height in order to force tree view scrollable */
+ height: calc(100vh - 48px);
+ display: flex;
+ flex-direction: column;
+}
+
+.properties-view .searchbox-section {
+ flex: 0 1 auto;
+}
+
+.properties-view .devtools-searchbox {
+ padding: 0;
+}
+
+.properties-view .devtools-searchbox input {
+ margin: 1px 3px;
+}
+
+.tree-container {
+ position: relative;
+ height: 100%;
}
-.theme-dark .treeTable .treeLabel,
-.theme-dark .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
- color: var(--theme-highlight-purple);
+/* Make treeTable fill parent element and scrollable */
+.tree-container .treeTable {
+ position: absolute;
+ display: block;
+ overflow-y: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.properties-view .devtools-searchbox,
+.tree-container .treeTable .tree-section {
+ width: 100%;
+ background-color: var(--theme-toolbar-background);
+}
+
+.tree-container .treeTable .tree-section > * {
+ vertical-align: middle;
}
-.theme-firebug .treeTable .treeLabel {
- color: var(--theme-body-color);
+.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel,
+.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover {
+ color: var(--theme-body-color-alt);
+}
+
+.tree-container .treeTable .treeValueCell {
+ /* FIXME: Make value cell can be reduced to shorter width */
+ max-width: 0;
+ padding-inline-end: 5px;
}
-.treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
- cursor: default;
- text-decoration: none;
+.tree-container .objectBox {
+ white-space: nowrap;
+}
+
+.editor-container,
+.editor-mount,
+.editor-mount iframe {
+ border: none;
+ width: 100%;
+ height: 100%;
}
/*
* FIXME: normal html block element cannot fill outer XUL element
* This workaround should be removed after netmonitor is migrated to react
*/
#react-preview-tabpanel-hook,
#react-security-tabpanel-hook,