diff options
Diffstat (limited to 'devtools/client/jsonview/components')
-rw-r--r-- | devtools/client/jsonview/components/headers-panel.js | 79 | ||||
-rw-r--r-- | devtools/client/jsonview/components/headers.js | 105 | ||||
-rw-r--r-- | devtools/client/jsonview/components/json-panel.js | 194 | ||||
-rw-r--r-- | devtools/client/jsonview/components/main-tabbed-area.js | 89 | ||||
-rw-r--r-- | devtools/client/jsonview/components/moz.build | 18 | ||||
-rw-r--r-- | devtools/client/jsonview/components/reps/moz.build | 9 | ||||
-rw-r--r-- | devtools/client/jsonview/components/reps/toolbar.js | 58 | ||||
-rw-r--r-- | devtools/client/jsonview/components/search-box.js | 55 | ||||
-rw-r--r-- | devtools/client/jsonview/components/text-panel.js | 95 |
9 files changed, 702 insertions, 0 deletions
diff --git a/devtools/client/jsonview/components/headers-panel.js b/devtools/client/jsonview/components/headers-panel.js new file mode 100644 index 000000000..9229aaa01 --- /dev/null +++ b/devtools/client/jsonview/components/headers-panel.js @@ -0,0 +1,79 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("devtools/client/shared/components/reps/rep-utils"); + const { Headers } = createFactories(require("./headers")); + const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar")); + + const { div } = dom; + + /** + * This template represents the 'Headers' panel + * s responsible for rendering its content. + */ + let HeadersPanel = createClass({ + displayName: "HeadersPanel", + + propTypes: { + actions: PropTypes.object, + data: PropTypes.object, + }, + + getInitialState: function () { + return { + data: {} + }; + }, + + render: function () { + let data = this.props.data; + + return ( + div({className: "headersPanelBox"}, + HeadersToolbar({actions: this.props.actions}), + div({className: "panelContent"}, + Headers({data: data}) + ) + ) + ); + } + }); + + /** + * This template is responsible for rendering a toolbar + * within the 'Headers' panel. + */ + let HeadersToolbar = createFactory(createClass({ + displayName: "HeadersToolbar", + + propTypes: { + actions: PropTypes.object, + }, + + // Commands + + onCopy: function (event) { + this.props.actions.onCopyHeaders(); + }, + + render: function () { + return ( + Toolbar({}, + ToolbarButton({className: "btn copy", onClick: this.onCopy}, + Locale.$STR("jsonViewer.Copy") + ) + ) + ); + }, + })); + + // Exports from this module + exports.HeadersPanel = HeadersPanel; +}); diff --git a/devtools/client/jsonview/components/headers.js b/devtools/client/jsonview/components/headers.js new file mode 100644 index 000000000..38ac4051c --- /dev/null +++ b/devtools/client/jsonview/components/headers.js @@ -0,0 +1,105 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + + const { div, span, table, tbody, tr, td, } = dom; + + /** + * This template is responsible for rendering basic layout + * of the 'Headers' panel. It displays HTTP headers groups such as + * received or response headers. + */ + let Headers = createClass({ + displayName: "Headers", + + propTypes: { + data: PropTypes.object, + }, + + getInitialState: function () { + return {}; + }, + + render: function () { + let data = this.props.data; + + return ( + div({className: "netInfoHeadersTable"}, + div({className: "netHeadersGroup"}, + div({className: "netInfoHeadersGroup"}, + Locale.$STR("jsonViewer.responseHeaders") + ), + table({cellPadding: 0, cellSpacing: 0}, + HeaderList({headers: data.response}) + ) + ), + div({className: "netHeadersGroup"}, + div({className: "netInfoHeadersGroup"}, + Locale.$STR("jsonViewer.requestHeaders") + ), + table({cellPadding: 0, cellSpacing: 0}, + HeaderList({headers: data.request}) + ) + ) + ) + ); + } + }); + + /** + * This template renders headers list, + * name + value pairs. + */ + let HeaderList = createFactory(createClass({ + displayName: "HeaderList", + + propTypes: { + headers: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + value: PropTypes.string + })) + }, + + getInitialState: function () { + return { + headers: [] + }; + }, + + render: function () { + let headers = this.props.headers; + + headers.sort(function (a, b) { + return a.name > b.name ? 1 : -1; + }); + + let rows = []; + headers.forEach(header => { + rows.push( + tr({key: header.name}, + td({className: "netInfoParamName"}, + span({title: header.name}, header.name) + ), + td({className: "netInfoParamValue"}, header.value) + ) + ); + }); + + return ( + tbody({}, + rows + ) + ); + } + })); + + // Exports from this module + exports.Headers = Headers; +}); diff --git a/devtools/client/jsonview/components/json-panel.js b/devtools/client/jsonview/components/json-panel.js new file mode 100644 index 000000000..c7280a0b1 --- /dev/null +++ b/devtools/client/jsonview/components/json-panel.js @@ -0,0 +1,194 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("devtools/client/shared/components/reps/rep-utils"); + const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view")); + const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep")); + const { SearchBox } = createFactories(require("./search-box")); + const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar")); + + const { div } = dom; + const AUTO_EXPAND_MAX_SIZE = 100 * 1024; + const AUTO_EXPAND_MAX_LEVEL = 7; + + /** + * This template represents the 'JSON' panel. The panel is + * responsible for rendering an expandable tree that allows simple + * inspection of JSON structure. + */ + let JsonPanel = createClass({ + displayName: "JsonPanel", + + propTypes: { + data: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.array, + PropTypes.object + ]), + jsonTextLength: PropTypes.number, + searchFilter: PropTypes.string, + actions: PropTypes.object, + }, + + getInitialState: function () { + return {}; + }, + + componentDidMount: function () { + document.addEventListener("keypress", this.onKeyPress, true); + }, + + componentWillUnmount: function () { + document.removeEventListener("keypress", this.onKeyPress, true); + }, + + onKeyPress: function (e) { + // XXX shortcut for focusing the Filter field (see Bug 1178771). + }, + + onFilter: function (object) { + if (!this.props.searchFilter) { + return true; + } + + let json = JSON.stringify(object).toLowerCase(); + return json.indexOf(this.props.searchFilter.toLowerCase()) >= 0; + }, + + 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) { + let nodePath = path + "/" + prop; + expandedNodes.add(nodePath); + + let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1); + if (nodes) { + expandedNodes = new Set([...expandedNodes, ...nodes]); + } + } + return expandedNodes; + }, + + renderValue: props => { + let member = props.member; + + // Hide object summary when object is expanded (bug 1244912). + if (typeof member.value == "object" && member.open) { + return null; + } + + // Render the value (summary) using Reps library. + return Rep(Object.assign({}, props, { + cropLimit: 50, + })); + }, + + renderTree: function () { + // Append custom column for displaying values. This column + // Take all available horizontal space. + let columns = [{ + id: "value", + width: "100%" + }]; + + // Expand the document by default if its size isn't bigger than 100KB. + let expandedNodes = new Set(); + if (this.props.jsonTextLength <= AUTO_EXPAND_MAX_SIZE) { + expandedNodes = this.getExpandedNodes(this.props.data); + } + + // Render tree component. + return TreeView({ + object: this.props.data, + mode: "tiny", + onFilter: this.onFilter, + columns: columns, + renderValue: this.renderValue, + expandedNodes: expandedNodes, + }); + }, + + render: function () { + let content; + let data = this.props.data; + + try { + if (typeof data == "object") { + content = this.renderTree(); + } else { + content = div({className: "jsonParseError"}, + data + "" + ); + } + } catch (err) { + content = div({className: "jsonParseError"}, + err + "" + ); + } + + return ( + div({className: "jsonPanelBox"}, + JsonToolbar({actions: this.props.actions}), + div({className: "panelContent"}, + content + ) + ) + ); + } + }); + + /** + * This template represents a toolbar within the 'JSON' panel. + */ + let JsonToolbar = createFactory(createClass({ + displayName: "JsonToolbar", + + propTypes: { + actions: PropTypes.object, + }, + + // Commands + + onSave: function (event) { + this.props.actions.onSaveJson(); + }, + + onCopy: function (event) { + this.props.actions.onCopyJson(); + }, + + render: function () { + return ( + Toolbar({}, + ToolbarButton({className: "btn save", onClick: this.onSave}, + Locale.$STR("jsonViewer.Save") + ), + ToolbarButton({className: "btn copy", onClick: this.onCopy}, + Locale.$STR("jsonViewer.Copy") + ), + SearchBox({ + actions: this.props.actions + }) + ) + ); + }, + })); + + // Exports from this module + exports.JsonPanel = JsonPanel; +}); diff --git a/devtools/client/jsonview/components/main-tabbed-area.js b/devtools/client/jsonview/components/main-tabbed-area.js new file mode 100644 index 000000000..ecba73807 --- /dev/null +++ b/devtools/client/jsonview/components/main-tabbed-area.js @@ -0,0 +1,89 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("devtools/client/shared/components/reps/rep-utils"); + const { JsonPanel } = createFactories(require("./json-panel")); + const { TextPanel } = createFactories(require("./text-panel")); + const { HeadersPanel } = createFactories(require("./headers-panel")); + const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs")); + + /** + * This object represents the root application template + * responsible for rendering the basic tab layout. + */ + let MainTabbedArea = createClass({ + displayName: "MainTabbedArea", + + propTypes: { + jsonText: PropTypes.string, + tabActive: PropTypes.number, + actions: PropTypes.object, + headers: PropTypes.object, + searchFilter: PropTypes.string, + json: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + PropTypes.array + ]) + }, + + getInitialState: function () { + return { + json: {}, + headers: {}, + jsonText: this.props.jsonText, + tabActive: this.props.tabActive + }; + }, + + onTabChanged: function (index) { + this.setState({tabActive: index}); + }, + + render: function () { + return ( + Tabs({ + tabActive: this.state.tabActive, + onAfterChange: this.onTabChanged}, + TabPanel({ + className: "json", + title: Locale.$STR("jsonViewer.tab.JSON")}, + JsonPanel({ + data: this.props.json, + jsonTextLength: this.props.jsonText.length, + actions: this.props.actions, + searchFilter: this.state.searchFilter + }) + ), + TabPanel({ + className: "rawdata", + title: Locale.$STR("jsonViewer.tab.RawData")}, + TextPanel({ + data: this.state.jsonText, + actions: this.props.actions + }) + ), + TabPanel({ + className: "headers", + title: Locale.$STR("jsonViewer.tab.Headers")}, + HeadersPanel({ + data: this.props.headers, + actions: this.props.actions, + searchFilter: this.props.searchFilter + }) + ) + ) + ); + } + }); + + // Exports from this module + exports.MainTabbedArea = MainTabbedArea; +}); diff --git a/devtools/client/jsonview/components/moz.build b/devtools/client/jsonview/components/moz.build new file mode 100644 index 000000000..fa66c8709 --- /dev/null +++ b/devtools/client/jsonview/components/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += [ + 'reps' +] + +DevToolsModules( + 'headers-panel.js', + 'headers.js', + 'json-panel.js', + 'main-tabbed-area.js', + 'search-box.js', + 'text-panel.js' +) diff --git a/devtools/client/jsonview/components/reps/moz.build b/devtools/client/jsonview/components/reps/moz.build new file mode 100644 index 000000000..1d239b7bd --- /dev/null +++ b/devtools/client/jsonview/components/reps/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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( + 'toolbar.js', +) diff --git a/devtools/client/jsonview/components/reps/toolbar.js b/devtools/client/jsonview/components/reps/toolbar.js new file mode 100644 index 000000000..52a35ffbe --- /dev/null +++ b/devtools/client/jsonview/components/reps/toolbar.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const React = require("devtools/client/shared/vendor/react"); + const DOM = React.DOM; + + /** + * Renders a simple toolbar. + */ + let Toolbar = React.createClass({ + displayName: "Toolbar", + + propTypes: { + children: React.PropTypes.oneOfType([ + React.PropTypes.array, + React.PropTypes.element + ]) + }, + + render: function () { + return ( + DOM.div({className: "toolbar"}, + this.props.children + ) + ); + } + }); + + /** + * Renders a simple toolbar button. + */ + let ToolbarButton = React.createClass({ + displayName: "ToolbarButton", + + propTypes: { + active: React.PropTypes.bool, + disabled: React.PropTypes.bool, + children: React.PropTypes.string, + }, + + render: function () { + let props = Object.assign({className: "btn"}, this.props); + return ( + DOM.button(props, this.props.children) + ); + }, + }); + + // Exports from this module + exports.Toolbar = Toolbar; + exports.ToolbarButton = ToolbarButton; +}); diff --git a/devtools/client/jsonview/components/search-box.js b/devtools/client/jsonview/components/search-box.js new file mode 100644 index 000000000..fc9bcbcb8 --- /dev/null +++ b/devtools/client/jsonview/components/search-box.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + + const { input } = dom; + + // For smooth incremental searching (in case the user is typing quickly). + const searchDelay = 250; + + /** + * This object represents a search box located at the + * top right corner of the application. + */ + let SearchBox = createClass({ + displayName: "SearchBox", + + propTypes: { + actions: PropTypes.object, + }, + + onSearch: function (event) { + let searchBox = event.target; + let win = searchBox.ownerDocument.defaultView; + + if (this.searchTimeout) { + win.clearTimeout(this.searchTimeout); + } + + let callback = this.doSearch.bind(this, searchBox); + this.searchTimeout = win.setTimeout(callback, searchDelay); + }, + + doSearch: function (searchBox) { + this.props.actions.onSearch(searchBox.value); + }, + + render: function () { + return ( + input({className: "searchBox", + placeholder: Locale.$STR("jsonViewer.filterJSON"), + onChange: this.onSearch}) + ); + }, + }); + + // Exports from this module + exports.SearchBox = SearchBox; +}); diff --git a/devtools/client/jsonview/components/text-panel.js b/devtools/client/jsonview/components/text-panel.js new file mode 100644 index 000000000..1df2e349d --- /dev/null +++ b/devtools/client/jsonview/components/text-panel.js @@ -0,0 +1,95 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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"; + +define(function (require, exports, module) { + const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("devtools/client/shared/components/reps/rep-utils"); + const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar")); + const { div, pre } = dom; + + /** + * This template represents the 'Raw Data' panel displaying + * JSON as a text received from the server. + */ + let TextPanel = createClass({ + displayName: "TextPanel", + + propTypes: { + actions: PropTypes.object, + data: PropTypes.string + }, + + getInitialState: function () { + return {}; + }, + + render: function () { + return ( + div({className: "textPanelBox"}, + TextToolbar({actions: this.props.actions}), + div({className: "panelContent"}, + pre({className: "data"}, + this.props.data + ) + ) + ) + ); + } + }); + + /** + * This object represents a toolbar displayed within the + * 'Raw Data' panel. + */ + let TextToolbar = createFactory(createClass({ + displayName: "TextToolbar", + + propTypes: { + actions: PropTypes.object, + }, + + // Commands + + onPrettify: function (event) { + this.props.actions.onPrettify(); + }, + + onSave: function (event) { + this.props.actions.onSaveJson(); + }, + + onCopy: function (event) { + this.props.actions.onCopyJson(); + }, + + render: function () { + return ( + Toolbar({}, + ToolbarButton({ + className: "btn save", + onClick: this.onSave}, + Locale.$STR("jsonViewer.Save") + ), + ToolbarButton({ + className: "btn copy", + onClick: this.onCopy}, + Locale.$STR("jsonViewer.Copy") + ), + ToolbarButton({ + className: "btn prettyprint", + onClick: this.onPrettify}, + Locale.$STR("jsonViewer.PrettyPrint") + ) + ) + ); + }, + })); + + // Exports from this module + exports.TextPanel = TextPanel; +}); |