JsonPanel.mjs (5197B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { 6 createFactory, 7 Component, 8 } from "resource://devtools/client/shared/vendor/react.mjs"; 9 import * as dom from "resource://devtools/client/shared/vendor/react-dom-factories.mjs"; 10 import PropTypes from "resource://devtools/client/shared/vendor/react-prop-types.mjs"; 11 import { createFactories } from "resource://devtools/client/shared/react-utils.mjs"; 12 13 import TreeViewClass from "resource://devtools/client/shared/components/tree/TreeView.mjs"; 14 import { BucketProperty } from "resource://devtools/client/shared/components/tree/ObjectProvider.mjs"; 15 import JsonToolbarClass from "resource://devtools/client/jsonview/components/JsonToolbar.mjs"; 16 17 import { 18 JSON_NUMBER, 19 MODE, 20 } from "resource://devtools/client/shared/components/reps/reps/constants.mjs"; 21 import { Rep } from "resource://devtools/client/shared/components/reps/reps/rep.mjs"; 22 23 const TreeView = createFactory(TreeViewClass); 24 const { JsonToolbar } = createFactories(JsonToolbarClass); 25 const { div } = dom; 26 27 const MAX_STRING_LENGTH = 250; 28 29 function isObject(value) { 30 return Object(value) === value; 31 } 32 33 /** 34 * This template represents the 'JSON' panel. The panel is 35 * responsible for rendering an expandable tree that allows simple 36 * inspection of JSON structure. 37 */ 38 class JsonPanel extends Component { 39 static get propTypes() { 40 return { 41 data: PropTypes.oneOfType([ 42 PropTypes.string, 43 PropTypes.array, 44 PropTypes.object, 45 PropTypes.bool, 46 PropTypes.number, 47 ]), 48 dataSize: PropTypes.number, 49 expandedNodes: PropTypes.instanceOf(Set), 50 searchFilter: PropTypes.string, 51 actions: PropTypes.object, 52 }; 53 } 54 55 constructor(props) { 56 super(props); 57 this.state = {}; 58 this.onKeyPress = this.onKeyPress.bind(this); 59 this.onFilter = this.onFilter.bind(this); 60 this.renderValue = this.renderValue.bind(this); 61 this.renderTree = this.renderTree.bind(this); 62 } 63 64 componentDidMount() { 65 document.addEventListener("keypress", this.onKeyPress, true); 66 document.getElementById("json-scrolling-panel").focus(); 67 } 68 69 componentWillUnmount() { 70 document.removeEventListener("keypress", this.onKeyPress, true); 71 } 72 73 onKeyPress() { 74 // XXX shortcut for focusing the Filter field (see Bug 1178771). 75 } 76 77 onFilter(object) { 78 if (!this.props.searchFilter) { 79 return true; 80 } 81 82 const searchFilter = this.props.searchFilter.toLowerCase(); 83 84 // For bucket nodes, check if any of their children match 85 if (object instanceof BucketProperty) { 86 const { object: array, startIndex, endIndex } = object; 87 for (let i = startIndex; i <= endIndex; i++) { 88 const childJson = JSON.stringify(array[i]); 89 if (childJson.toLowerCase().includes(searchFilter)) { 90 return true; 91 } 92 } 93 return false; 94 } 95 96 const json = object.name + JSON.stringify(object.value); 97 return json.toLowerCase().includes(searchFilter); 98 } 99 100 renderValue(props) { 101 const member = props.member; 102 103 // Hide value for bucket nodes (they show ranges like [0…99]) 104 if (member.type === "bucket") { 105 return null; 106 } 107 108 // Hide object summary when non-empty object is expanded (bug 1244912). 109 if (isObject(member.value) && member.hasChildren && member.open) { 110 return null; 111 } 112 113 // Render the value (summary) using Reps library. 114 return Rep( 115 Object.assign({}, props, { 116 cropLimit: MAX_STRING_LENGTH, 117 noGrip: true, 118 isInContentPage: true, 119 }) 120 ); 121 } 122 123 renderTree() { 124 // Append custom column for displaying values. This column 125 // Take all available horizontal space. 126 const columns = [ 127 { 128 id: "value", 129 width: "100%", 130 }, 131 ]; 132 133 // Render tree component. 134 return TreeView({ 135 object: this.props.data, 136 mode: MODE.LONG, 137 bucketLargeArrays: true, 138 onFilter: this.onFilter, 139 columns, 140 renderValue: this.renderValue, 141 expandedNodes: this.props.expandedNodes, 142 maxStringLength: MAX_STRING_LENGTH, 143 }); 144 } 145 146 render() { 147 let content; 148 const data = this.props.data; 149 150 if (!isObject(data)) { 151 content = div( 152 { className: "jsonPrimitiveValue" }, 153 Rep({ 154 object: data, 155 }) 156 ); 157 } else if (data instanceof Error) { 158 content = div({ className: "jsonParseError" }, data + ""); 159 } else if (data.type === JSON_NUMBER) { 160 content = div( 161 { className: "jsonPrimitiveValue" }, 162 Rep({ 163 object: data, 164 noGrip: true, 165 }) 166 ); 167 } else { 168 content = this.renderTree(); 169 } 170 171 return div( 172 { className: "jsonPanelBox tab-panel-inner" }, 173 JsonToolbar({ 174 actions: this.props.actions, 175 dataSize: this.props.dataSize, 176 }), 177 div( 178 { 179 className: "panelContent", 180 id: "json-scrolling-panel", 181 tabIndex: 0, 182 }, 183 content 184 ) 185 ); 186 } 187 } 188 189 export default { JsonPanel };